diff --git a/README.md b/README.md index 02413cbdb2..da612ac25a 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Follow [the job submission tutorial](docs/user/job_submission.md) to learn more ### Client tool -[OpenPAI VS Code Client](contrib/pai_vscode/VSCodeExt.md) is a friendly, GUI based client tool of OpenPAI, and it's highly recommended. It's an extension of Visual Studio Code. It can submit job, simulate jobs locally, manage multiple OpenPAI environments, and so on. +[OpenPAI VS Code Client](https://github.com/Microsoft/openpaivscode/blob/master/VSCodeExt.md) is a friendly, GUI based client tool of OpenPAI, and it's highly recommended. It's an extension of Visual Studio Code. It can submit job, simulate jobs locally, manage multiple OpenPAI environments, and so on. ### Troubleshooting job failure @@ -143,7 +143,7 @@ Refer to [here](docs/user/troubleshooting_job.md) for more information about tro ### Users -* [Client tool](contrib/pai_vscode/VSCodeExt.md) +* [Client tool](https://github.com/Microsoft/openpaivscode/blob/master/VSCodeExt.md) * [Use Storage](docs/user/storage.md) * [Job configuration](docs/job_tutorial.md) * [RESTful API](docs/rest-server/API.md) diff --git a/README_zh_CN.md b/README_zh_CN.md index 35b9ef973a..16a8a87949 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -119,7 +119,7 @@ OpenPAI 的一般用法是提交 Job 请求,等到 Job 获得计算资源后 ### 客户端 -[OpenPAI VS Code Client](contrib/pai_vscode/VSCodeExt_zh_CN.md) 是推荐的 OpenPAI 客户端工具,其基于图形界面,易于使用。 它是 Visual Studio Code 的扩展。 支持提交 Job,在本地模拟运行 Job,管理多个 OpenPAI 环境等等。 +[OpenPAI VS Code Client](https://github.com/microsoft/openpaivscode/blob/master/VSCodeExt_zh_CN.md) 是推荐的 OpenPAI 客户端工具,其基于图形界面,易于使用。 它是 Visual Studio Code 的扩展。 支持提交 Job,在本地模拟运行 Job,管理多个 OpenPAI 环境等等。 ### 调研 Job 错误 @@ -137,7 +137,7 @@ Web 界面和 Job 日志有助于分析错误,OpenPAI 也支持通过 SSH 登 ### 用户 -* [客户端](contrib/pai_vscode/VSCodeExt_zh_CN.md) +* [客户端](https://github.com/microsoft/openpaivscode/blob/master/VSCodeExt_zh_CN.md) * [使用存储](docs/zh_CN/user/storage.md) * [Job 配置](docs/zh_CN/job_tutorial.md) * [RESTful API](docs/zh_CN/rest-server/API.md) @@ -176,4 +176,4 @@ Web 界面和 Job 日志有助于分析错误,OpenPAI 也支持通过 SSH 登 OpenPAI 的一个重要目标是支持学术界和工业界非常多样化的需求。 OpenPAI 是完全开放的:它采用了 MIT 许可证。 这使得 PAI 特别适合用来探索各种研究想法,例如[这些模块](./docs/zh_CN/research_education.md)。 -OpenPAI 采用开放的形式来合作。 由[微软研究院(MSR)](https://www.microsoft.com/en-us/research/group/systems-research-group-asia/)和[微软互联网工程院](https://www.microsoft.com/en-us/ard/company/introduction.aspx) AI 平台团队联合设计开发。 很高兴能有北京大学、西安交通大学、浙江大学、中国科学技术大学等高校加入平台开发。 无论是来自从学术界还是工业界的贡献,都非常欢迎。 \ No newline at end of file +OpenPAI 采用开放的形式来合作。 由[微软研究院(MSR)](https://www.microsoft.com/en-us/research/group/systems-research-group-asia/)和[微软互联网工程院](https://www.microsoft.com/en-us/ard/company/introduction.aspx) AI 平台团队联合设计开发。 很高兴能有北京大学、西安交通大学、浙江大学、中国科学技术大学等高校加入平台开发。 无论是来自从学术界还是工业界的贡献,都非常欢迎。 diff --git a/RELEASE_NOTE.md b/RELEASE_NOTE.md index 7515727e82..a3dad20d2b 100644 --- a/RELEASE_NOTE.md +++ b/RELEASE_NOTE.md @@ -7,7 +7,7 @@ Welcome to the July 2019 release of OpenPAI. There are a number of updates in th - [New webportal job submission experience](./docs/user/job_submission.md) - Update submit job UI to version 2. - [Python sdk of openpai is now ready!](https://github.com/microsoft/pai/tree/master/contrib/python-sdk) - You can config, submit and debug your job easily with python sdk. - [New yarn schedular to improve resource efficiency](./docs/tools/dedicated_vc.md) - Admin can bind dedicated Virtual Cluster to 1 or more physical nodes. -- [vscode extension now supports submitting v2 job](https://github.com/microsoft/pai/tree/master/contrib/pai_vscode). +- [vscode extension now supports submitting v2 job](https://github.com/microsoft/openpaivscode/tree/master). - [Provide team storage plugin to manage data shared by team](https://github.com/microsoft/pai/tree/master/contrib/storage_plugin). - [How to upgrade to OpenPAI v-0.14.0?](./docs/upgrade/upgrade_to_v0.14.0.md) diff --git a/RELEASE_NOTE_zh_CN.md b/RELEASE_NOTE_zh_CN.md index a6d10f2174..a3dad20d2b 100644 --- a/RELEASE_NOTE_zh_CN.md +++ b/RELEASE_NOTE_zh_CN.md @@ -7,7 +7,7 @@ Welcome to the July 2019 release of OpenPAI. There are a number of updates in th - [New webportal job submission experience](./docs/user/job_submission.md) - Update submit job UI to version 2. - [Python sdk of openpai is now ready!](https://github.com/microsoft/pai/tree/master/contrib/python-sdk) - You can config, submit and debug your job easily with python sdk. - [New yarn schedular to improve resource efficiency](./docs/tools/dedicated_vc.md) - Admin can bind dedicated Virtual Cluster to 1 or more physical nodes. -- [vscode extension now supports submitting v2 job](https://github.com/microsoft/pai/tree/master/contrib/pai_vscode). +- [vscode extension now supports submitting v2 job](https://github.com/microsoft/openpaivscode/tree/master). - [Provide team storage plugin to manage data shared by team](https://github.com/microsoft/pai/tree/master/contrib/storage_plugin). - [How to upgrade to OpenPAI v-0.14.0?](./docs/upgrade/upgrade_to_v0.14.0.md) @@ -114,4 +114,4 @@ For more details about this release, please refer to [detailed release note](htt - New UI for user management: Now the console for administrators to manage PAI users has got a new UI. - Documentation: Significant changes on documents -- more comprehensive, more structured, and easier to follow. -For more details about this release, please refer to [detailed release note](https://github.com/microsoft/pai/releases/tag/v0.6.1). \ No newline at end of file +For more details about this release, please refer to [detailed release note](https://github.com/microsoft/pai/releases/tag/v0.6.1). diff --git a/contrib/pai_vscode/.gitattributes b/contrib/pai_vscode/.gitattributes deleted file mode 100644 index f073758cb7..0000000000 --- a/contrib/pai_vscode/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Set default behavior to automatically normalize line endings. -* text=auto -*.ts text eol=lf diff --git a/contrib/pai_vscode/.gitignore b/contrib/pai_vscode/.gitignore deleted file mode 100644 index ec0e293215..0000000000 --- a/contrib/pai_vscode/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -out -node_modules -.vscode-test/ -*.vsix -*.log -!.vscode/ -.vscode/extensions.json -.vscode/settings.json diff --git a/contrib/pai_vscode/.vscode/launch.json b/contrib/pai_vscode/.vscode/launch.json deleted file mode 100644 index c174db304c..0000000000 --- a/contrib/pai_vscode/.vscode/launch.json +++ /dev/null @@ -1,36 +0,0 @@ -// A launch configuration that compiles the extension and then opens it inside a new window -// Use IntelliSense to learn about possible attributes. -// Hover to view descriptions of existing attributes. -// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Extension", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "npm: watch" - }, - { - "name": "Extension Tests", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test" - ], - "outFiles": [ - "${workspaceFolder}/out/test/**/*.js" - ], - "preLaunchTask": "npm: watch" - } - ] -} diff --git a/contrib/pai_vscode/.vscode/tasks.json b/contrib/pai_vscode/.vscode/tasks.json deleted file mode 100644 index 604e38f5ad..0000000000 --- a/contrib/pai_vscode/.vscode/tasks.json +++ /dev/null @@ -1,20 +0,0 @@ -// See https://go.microsoft.com/fwlink/?LinkId=733558 -// for the documentation about the tasks.json format -{ - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - } - ] -} \ No newline at end of file diff --git a/contrib/pai_vscode/.vscodeignore b/contrib/pai_vscode/.vscodeignore deleted file mode 100644 index 8557178967..0000000000 --- a/contrib/pai_vscode/.vscodeignore +++ /dev/null @@ -1,9 +0,0 @@ -.vscode/** -.vscode-test/** -out/test/** -out/**/*.map -src/** -.gitignore -tsconfig.json -vsc-extension-quickstart.md -tslint.json \ No newline at end of file diff --git a/contrib/pai_vscode/CHANGELOG.md b/contrib/pai_vscode/CHANGELOG.md deleted file mode 100644 index 509ac2cf91..0000000000 --- a/contrib/pai_vscode/CHANGELOG.md +++ /dev/null @@ -1,60 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [0.1.0] - 2019-01 - -### Added - -- Open job pages and dashboard pages in VS Code -- Submit job to PAI cluster from VS Code -- Open PAI's hdfs as a workspace folder - -## [0.2.0] - 2019-03 - -### Added - -- Generate jsonc job config by default -- Add a PAI view container (sidebar), includes - - Job list view - - Auto refresh enabled - - HDFS explorer - - You can choose where hdfs explorer will be shown (view container or workspace folder) - -## [0.2.1] - 2019-06 - -### Added - -- Generate YAML job config file for Protocol V2 - - Submit YAML job file - - Snippets and autocomplete for YAML job config - -## [0.2.2] - 2019-11 - -### Added - -- Support AAD login to OpenPAI cluster - - User can use access token instead of password in cluster config file. - -## [0.2.3] - 2019-12 - -### Added - -- Add local simulation for YAML job config file -- Add https in cluster config - - User can use https connect to OpenPAI cluster - -## [0.3.0] - 2020-03 - -### Added - -- Add storage explorer - - User can use storage explorer to manage data in OpenPAI teamwise storage - - User can add personal storage to storage explorer -- Add auto upload feature to teamwise storage and personal storage -- New job list auto refresh rule - - Job list auto refresh will be trigger only when the `PAI JOB LIST` view is visible. -- Add notification to website after AAD login success diff --git a/contrib/pai_vscode/CHANGELOG_zh_CN.md b/contrib/pai_vscode/CHANGELOG_zh_CN.md deleted file mode 100644 index d44e14dabc..0000000000 --- a/contrib/pai_vscode/CHANGELOG_zh_CN.md +++ /dev/null @@ -1,51 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [0.1.0] - 2019-01 - -### Added - -- Open job pages and dashboard pages in VS Code -- Submit job to PAI cluster from VS Code -- Open PAI's hdfs as a workspace folder - -## [0.2.0] - 2019-03 - -### Added - -- Generate jsonc job config by default -- Add a PAI view container (sidebar), includes - - Job list view - - Auto refresh enabled - - HDFS explorer - - You can choose where hdfs explorer will be shown (view container or workspace folder) - -## [0.2.1] - 2019-06 - -### Added - -- Generate YAML job config file for Protocol V2 - - Submit YAML job file - - Snippets and autocomplete for YAML job config - -## [0.2.2] - 2019-11 - -### Added - -- Support AAD login to OpenPAI cluster - - User can use access token instead of password in cluster config file. - -## [0.3.0] - 2020-03 - -### Added - -- Add storage explorer - - User can use storage explorer to manage data in OpenPAI teamwise storage - - User can add personal storage to storage explorer -- Add auto upload feature to teamwise storage and personal storage -- New job list auto refresh rule - - Job list auto refresh will be trigger only when the `PAI JOB LIST` view is visible. -- Add notification to website after AAD login success diff --git a/contrib/pai_vscode/LICENSE b/contrib/pai_vscode/LICENSE deleted file mode 100644 index 21071075c2..0000000000 --- a/contrib/pai_vscode/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE diff --git a/contrib/pai_vscode/README.md b/contrib/pai_vscode/README.md deleted file mode 100644 index be9dca089e..0000000000 --- a/contrib/pai_vscode/README.md +++ /dev/null @@ -1,206 +0,0 @@ -# OpenPAI VS Code Client - -OpenPAI VS Code Client is an extension to connect OpenPAI clusters, submit AI jobs, simulate jobs locally, manage files, and so on. - -- [OpenPAI VS Code Client](#openpai-vs-code-client) - - [Connect to an OpenPAI cluster](#connect-to-an-openpai-cluster) - - [Basic login](#basic-login) - - [AAD login](#aad-login) - - [Submit job](#submit-job) - - [Local simulation](#local-simulation) - - [Prerequisites](#prerequisites) - - [Steps](#steps) - - [Limitations](#limitations) - - [Source code auto upload](#source-code-auto-upload) - - [Reference](#reference) - - [GUI](#gui) - - [Command Palette](#command-palette) - - [PAI Cluster Explorer](#pai-cluster-explorer) - - [Settings](#settings) - - [Issue and suggestions](#issue-and-suggestions) - - [Contribution](#contribution) - - [License](#license) - -## Connect to an OpenPAI cluster - -Before using OpenPAI VS Code Client, follow below steps connecting to an OpenPAI cluster. - -### Basic login - -Notice, the version of OpenPAI cluster must equal or greater than 0.8.0, and the `authn_type` of the cluster should be `basic`. - -1. Use shortcut key Ctrl+Shift+P to open command palette. -2. Input and look for *PAI: Add PAI Cluster* as below. - - ![add cluster](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/add_cluster.png) - -3. Press Enter, and input the host of an OpenPAI cluster. It can be domain name or IP Address. After that, press Enter again. - - ![add cluster host](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/add_cluster_host.png) - -4. A configuration file is opened, and username and password fields are needed at least. Once it completes, click *Finish* button at right bottom corner. Notice, it won't be effect, if you save and close the file directly. - - ![add cluster configuration](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/add-cluster-finish.png) - -If there are multiple OpenPAI clusters, you can follow above steps again to connect with them. - -### AAD login - -Notice, the version of OpenPAI cluster must equal or greater than 0.14.0, and the `authn_type` of the cluster should be `OIDC`. - -1. Use shortcut key Ctrl+Shift+P to open command palette. -2. Input and look for *PAI: Add PAI Cluster* as below. - - ![add cluster](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/add_cluster.png) - -3. Press Enter, and input the host of an OpenPAI cluster. It can be domain name or IP Address. After that, press Enter again. - - ![add cluster host](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/add_cluster_host.png) - -4. If the `authn_type` of the cluster is `OIDC`, a webside will be open and ask you to login, after that a configuration file is opened, and if your login was successful the username and token fields are auto filled, you can change it if needed. Once it completes, click *Finish* button at right bottom corner. Notice, it won't be effect, if you save and close the file directly. - - ![add cluster configuration](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/add_aad_cluster.gif) - -If there are multiple OpenPAI clusters, you can follow above steps again to connect with them. - -## Submit job - -There is a tutorial for OpenPAI job submission, please refer to [Submit job to OpenPAI](documentation/submit_job.md). -After added a cluster configuration, you can find the cluster in *PAI CLUSTER EXPLORER* pane as below. - -![pai cluster explorer](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/pai_cluster_explorer.png) - -Submit V2 job (For OpenPAI v0.13.0 and above): - -You can create a job v2 configuration and submit to OpenPAI as below steps. - -1. Create job config file: - 1. Double click `Create Job Config...` in OpenPAI cluster Explorer, and then specify file name and location to create a job configuration file (Make sure the value of `protocol_version` property in cluster configuration is `'2'`). - 2. Right click python or cntk file in VSCode Explorer and select `Create PAI Job Config V2`, and then specify file name and location to create a job configuration file. -2. Update job configuration as needed. If you are not familiar with this configuration file, learn from [here](https://github.com/microsoft/pai/blob/master/docs/marketplace-and-submit-job-v2/marketplace-and-submit-job-v2.md#introduction-to-yaml-file). -3. Right click on the created job v2 configuration file, then click on `Submit Job to PAI Cluster`. The client will upload files to OpenPAI and create a job. Once it's done, there is a notification at right bottom corner, you can click to open the job detail page. - - If there are multiple OpenPAI clusters, you need to choose one. - - This animation shows above steps. - ![submit job](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/submit-job-v2.gif) - -Submit V1 Job (deprecating, only for OpenPAI version under 0.13.0): - -You can create a job configuration and submit to OpenPAI as below steps. - -1. Create job config file: - 1. Double click `Create Job Config...` in OpenPAI cluster Explorer, and then specify file name and location to create a job configuration file (Make sure the value of `protocol_version` property in cluster configuration is `'1'`). - 2. Right click python or cntk file in VSCode Explorer and select `Create PAI Job Config V1`, and then specify file name and location to create a job configuration file. -2. Update job configuration as needed. If you are not familiar with this configuration file, learn from [here](https://github.com/Microsoft/pai/blob/master/docs/user/training.md#learn-hello-world-job). -3. Right click on the created job configuration file, then click on `Submit Job to PAI Cluster`. The client will upload files to OpenPAI and create a job. Once it's done, there is a notification at right bottom corner, you can click to open the job detail page. - - If there are multiple OpenPAI clusters, you need to choose one. - - This animation shows above steps. - ![submit job](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/submit-job.gif) - -## Local simulation - -As it needs sometime to wait job starting in OpenPAI cluster, local simulation can help identifying and debugging most code, environment and configuration issues quickly. - -### Prerequisites - -[Docker](https://docs.docker.com/install/) MUST be installed to use local simulation. - -### Steps - -1. As submit a job, you can right click a configuration file to find local simulation. -2. Click *Simulate PAI Job Running*, after a while below notification shows. - - ![simulate running](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/simulate_running.png) - -3. you can click on *Simulate first task in VS Code terminal* to simulate directly, or *Reveal in Explorer* to view created docker files and start simulation manually. - -This animation shows above steps. -![simulate job](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/simulate-job.gif) - -### Limitations - -As local simulation is a close but still different environment with OpenPAI cluster, there are some issues cannot be found by simulation. Some examples, - -- The job may need much more memory or distributed environments. It cannot be simulated locally. -- The job may need GPU, but local computer may not have one. It may need code logic to handle this situation. It also needs a different docker image if you are using TensorFlow. As TensorFlow has different package for GPU and non-GPU runtime. -- The job may run much more time locally. In most case, the computing power of local computer is much lower than servers in the OpenPAI cluster. If you need to simulate a job end-to-end, it may need to reduce iterations to get result faster. -- Local machine may not be able to access some storage. The OpenPAI cluster may be deployed in a private environment, so that local computer may not able to access resource of cluster. -- Local simulated can't support OpenPAI cluster runtime plugin (e.g. SSH plugin, teamwise storage plugin, tensorboard plugin), user should install them manully. - -## Source code auto upload - -Please refer to [Auto Upload](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/documentation/storage_explorer_and_auto_upload.md#Auto-Upload). - -## Reference - -### GUI - -The client has two GUI parts. First is the *PAI CLUSTER EXPLORER* in explorer and used in above introduction. Second can be opened by the icon in activity bar. - -![activity bar](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/activity_bar.png) - -There are three parts in the side bar. - -- Storage Explorer (For PAI > 0.14.0) - - Please refer to [Storage Explorer](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/documentation/storage_explorer_and_auto_upload.md#Storage-Explorer). - -- HDFS Explorer (For PAI <= 0.14.0) - - You can view, upload and download folder and files of the OpenPAI cluster storage. - -- Job List - - You can view jobs in OpenPAI cluster. The lists refresh periodically, and the icon shows the status of each job. You can open a job in browser with double clicking it. - -![job list](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/job-list.png) - -### Command Palette - -| Name | Description | -| ------------------------------- | ----------------------------------------- | -| PAI: Add PAI Cluster | Add a new OpenPAI cluster | -| PAI: Open Website | View OpenPAI cluster in browser | -| PAI: Submit Job to PAI Cluster | Submit an OpenPAI job | -| PAI: Create PAI Job Config File | Create an OpenPAI configuration file | -| PAI: Simulate PAI Job Running | Generate Docker file for local simulation | - -### PAI Cluster Explorer - -| Name | Description | -| ----------------------- | --------------------------------------------- | -| Open Web Portal... | Browse to OpenPAI's web portal | -| List Jobs... | Open PAI's job list page in VS Code | -| Create Job Config... | Create an OpenPAI configuration file | -| Submit Job... | Submit an OpenPAI job | -| Simulate Job Running... | Generate Docker file for local simulation | -| Edit Configuration... | Edit OpenPAI cluster configuration | -| Open HDFS... | Open HDFS storage explorer of OpenPAI cluster | - -### Settings - -| ID | Description | -| -------------------------------- | ------------------------------------------------------- | -| pai.job.upload.enabled | Whether will upload files to codeDir of configuration | -| pai.job.upload.exclude | Excluded files and folders for uploading | -| pai.job.upload.include | Included files and folders for uploading | -| pai.job.generateJobName.enabled | Whether add a random suffix to job name when submitting | -| pai.job.jobList.recentJobsLength | The number in *Recent Submitted Jobs from VS Code* | -| pai.job.jobList.allJobsPageSize | The page size of the *All Jobs* list | -| pai.job.jobList.refreshInterval | The refresh interval of job list (in seconds) | -| pai.hdfs.location | Where HDFS storage will be shown | - -## Issue and suggestions - -Submit at [GitHub](https://github.com/Microsoft/pai/issues) - -## Contribution - -https://github.com/Microsoft/pai#how-to-contribute - -## License - -MIT diff --git a/contrib/pai_vscode/README_zh_CN.md b/contrib/pai_vscode/README_zh_CN.md deleted file mode 100644 index 8999816f63..0000000000 --- a/contrib/pai_vscode/README_zh_CN.md +++ /dev/null @@ -1,185 +0,0 @@ -# OpenPAI VS Code Client - -OpenPAI VS Code Client 是一个 Visual Studio Code 的扩展组件,可以连接 OpenPAI 集群,提交 Job,在本地模拟运行 Job,管理文件等等。 - -- [OpenPAI VS Code Client](#openpai-vs-code-client) - - [连接到 OpenPAI 集群](#%e8%bf%9e%e6%8e%a5%e5%88%b0-openpai-%e9%9b%86%e7%be%a4) - - [提交 Job](#%e6%8f%90%e4%ba%a4-job) - - [本机模拟](#%e6%9c%ac%e6%9c%ba%e6%a8%a1%e6%8b%9f) - - [先决条件](#%e5%85%88%e5%86%b3%e6%9d%a1%e4%bb%b6) - - [步骤](#%e6%ad%a5%e9%aa%a4) - - [局限性](#%e5%b1%80%e9%99%90%e6%80%a7) - - [任务代码自动上传](#%e4%bb%bb%e5%8a%a1%e4%bb%a3%e7%a0%81%e8%87%aa%e5%8a%a8%e4%b8%8a%e4%bc%a0) - - [参考](#%e5%8f%82%e8%80%83) - - [GUI](#gui) - - [命令面板](#%e5%91%bd%e4%bb%a4%e9%9d%a2%e6%9d%bf) - - [PAI 集群浏览器](#pai-%e9%9b%86%e7%be%a4%e6%b5%8f%e8%a7%88%e5%99%a8) - - [设置](#%e8%ae%be%e7%bd%ae) - - [问题和建议](#%e9%97%ae%e9%a2%98%e5%92%8c%e5%bb%ba%e8%ae%ae) - - [贡献](#%e8%b4%a1%e7%8c%ae) - - [许可证](#%e8%ae%b8%e5%8f%af%e8%af%81) - -## 连接到 OpenPAI 集群 - -使用 OpenPAI VS Code Client 之前,按照以下步骤连接到 OpenPAI 集群。 - -注意, OpenPAI 集群的版本必须大于或等于 0.8.0。 - -1. 使用快捷键 Ctrl+Shift+P 打开命令面板。 -2. 如下输入并查找 *PAI: 添加 PAI 集群*。 - - ![添加集群](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/add_cluster.png) - - -3. 按下 Enter,并输入 OpenPAI 集群的地址。 可以是域名或者 IP 地址。 然后,再次按下 Enter。 - - ![添加集群](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/add_cluster.png) - - -4. 配置文件将会被打开,至少需要填入 username 和 password 字段。 完成后,点击右下角的 *完成* 按钮。 注意,如果直接保存并关闭文件,则无法生效。 - - ![添加集群配置](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/add-cluster-finish.png) - - -如果有多个 OpenPAI 集群,可以多次按照上述步骤进行。 - -## 提交 Job - -添加完集群配置后,可以在*PAI 集群浏览器* 面板找到该集群。 - -![pai cluster explorer](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/pai_cluster_explorer.png) - -提交 v2 Job (OpenPAI >= v0.13.0): - -可通过以下步骤创建 v2 Job 配置,并提交到 OpenPAI。 - -1. 创建 Job 配置文件: - 1. 在 `PAI 集群浏览器` 中双击 `创建任务配置文件...`, 并指定文件名和路径来创建 Job 配置文件(请确保集群配置中的 `protocol_version` 属性的值为 `'2'`)。 - 2. 在 `VSCode 资源管理器` 中右击 python 或 cntk 文件,并选取 `创建 PAI 任务配置文件 V2`, 并指定文件名和路径来创建 Job 配置文件。 -2. 根据需要更新 Job 配置。 如果不熟悉配置文件,可参考[这里](https://github.com/microsoft/pai/blob/master/docs/zh_CN/marketplace-and-submit-job-v2/marketplace-and-submit-job-v2.md#introduction-to-yaml-file)。 -3. 右击创建的 Job v2 配置文件,然后点击 `在 PAI 集群上提交任务`。 客户端会将文件上传到 OpenPAI 并创建 Job。 完成后,在右下角会有通知,可点击打开 Job 详情页面。 - - 如果有多个 OpenPAI 集群,需要选择其中一个。 - - 此动画显示了上述步骤。 - ![提交 Job](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/submit-job-v2.gif) - -提交 v1 Job (deprecating, OpenPAI < 0.13.0): - -可通过以下步骤创建 Job 配置,并提交到 OpenPAI。 - -1. 创建 Job 配置文件: - 1. 在 `PAI 集群浏览器` 中双击 `创建任务配置文件...`, 并指定文件名和路径来创建 Job 配置文件(请确保集群配置中的 `protocol_version` 属性的值为 `'1'`)。 - 2. 在 `VSCode 资源管理器` 中右击 python 或 cntk 文件,并选取 `创建 PAI 任务配置文件 V1`, 并指定文件名和路径来创建 Job 配置文件。 -2. 根据需要更新 Job 配置。 如果不熟悉配置文件,可参考[这里](https://github.com/Microsoft/pai/blob/master/docs/zh_CN/user/training.md)。 -3. 右击创建的 Job 配置文件,然后点击 `Submit Job to PAI Cluster`。 客户端会将文件上传到 OpenPAI 并创建 Job。 完成后,在右下角会有通知,可点击打开 Job 详情页面。 - - 如果有多个 OpenPAI 集群,需要选择其中一个。 - - 此动画显示了上述步骤。 - ![提交 Job](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/submit-job.gif) - - -## 本机模拟 - -在 OpenPAI 集群中运行 Job 需要额外花费一些时间,因此在本机模拟可以更快的找到代码,以及环境和配置的问题。 - -### 先决条件 - -必须安装 [Docker](https://docs.docker.com/install/) 才能使用本机模拟。 - -### 步骤 - -1. 与提交 Job 一样,可右击配置文件来找到本机模拟功能。 -2. 点击 *Simulate PAI Job Running*,过一小会儿,就会看到如下的通知。 - - ![simulate running](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/simulate_running.png) - - -3. 可点击 *Simulate first task in VS Code terminal* 直接模拟运行,或点击 *Reveal in Explorer* 来查看创建的 Docker 文件,并手动运行模拟。 - -此动画显示了上述步骤。 ![simulate job](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/simulate-job.gif) - -### 局限性 - -本机模拟与在 OpenPAI 集群中运行相近,但仍有些区别,因此有些问题无法通过模拟来发现。 比如: - -- Job 可能需要大量的内存或分布式的环境。 无法在本机进行模拟。 -- Job 可能需要 GPU,但本机可能没有。 同时,可能需要更多的代码逻辑来处理这种情况。 如果使用 TensorFlow,可能还需要不同的 Docker 映像。 这是因为 TensorFlow 在 GPU 和非 GPU 场景下需要不同的运行包。 -- Job 可能会在本地运行很长的时间。 在大多数情况下,本机的算力都远低于 OpenPAI 集群中的服务器。 如果需要端到端的模拟 Job,则需要减少迭代次数来更快的获得结果。 -- 本机可能无法访问一些存储。 OpenPAI 集群有可能部署在私有环境中,因此本机可能无法访问一些集群的存储。 - -## 任务代码自动上传 - -请参考 [Auto Upload](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/documentation/storage_explorer_and_auto_upload.md#Auto-Upload). - -## 参考 - -### GUI - -客户端有两部分用户界面。 首先是资源管理器中的 *PAI CLUSTER EXPLORER*,在上述章节已介绍过。 可通过活动栏中图标打开第二部分。 - -![activity bar](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/activity_bar.png) - -打开后可看到两个部分。 - -- 存储浏览器 (PAI > 0.14.0) - - 请参考 [Storage Explorer](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/documentation/storage_explorer_and_auto_upload.md#Storage-Explorer). - -- HDFS 浏览器 (PAI <= 0.14.0) - - 可查看、上传或下载 OpenPAI 集群存储中的文件。 - -- PAI 任务列表 - - 可查看 OpenPAI 集群中的 Job。 列表会定期刷新,前面的图标显示了 Job 的状态。 可双击 Job 在浏览器中查看。 - -![job list](https://raw.githubusercontent.com/Microsoft/pai/master/contrib/pai_vscode/assets/job-list.png) - -### 命令面板 - -| 名称 | 说明 | -| ------------------------------- | ------------------- | -| PAI: Add PAI Cluster | 添加新的 OpenPAI 集群 | -| PAI: Open Website | 在浏览器中查看 OpenPAI 集群 | -| PAI: Submit Job to PAI Cluster | 提交 OpenPAI Job | -| PAI: Create PAI Job Config File | 创建 OpenPAI 配置文件 | -| PAI: Simulate PAI Job Running | 生成 Docker 文件并进行本机模拟 | - -### PAI 集群浏览器 - -| 名称 | 说明 | -| ----------------------- | ------------------------- | -| Open Web Portal... | 浏览 OpenPAI 的门户网站 | -| List Jobs... | 列出 Job | -| Create Job Config... | 创建 OpenPAI 配置文件 | -| Submit Job... | 提交 OpenPAI Job | -| Simulate Job Running... | 生成 Docker 文件并进行本机模拟 | -| Edit Configuration... | 编辑 OpenPAI 集群配置 | -| Open HDFS... | 打开 OpenPAI 集群的 HDFS 存储管理器 | - -### 设置 - -| 标识 | 说明 | -| -------------------------------- | ------------------------------------------ | -| pai.job.upload.enabled | 是否将文件上载到配置的 codeDir | -| pai.job.upload.exclude | 上载时排除的文件和文件夹 | -| pai.job.upload.include | 上载时包含的文件和文件夹 | -| pai.job.generateJobName.enabled | 是否在提交时为 Job 名称添加随机后缀 | -| pai.job.jobList.recentJobsLength | *Recent Submitted Jobs from VS Code* 显示的数量 | -| pai.job.jobList.allJobsPageSize | *All Jobs* 的页面条数 | -| pai.job.jobList.refreshInterval | Job 列表的刷新间隔(秒) | -| pai.hdfs.location | 显示 HDFS 存储的位置 | - -## 问题和建议 - -提交到 [GitHub](https://github.com/Microsoft/pai/issues) - -## 贡献 - -https://github.com/microsoft/pai/blob/master/README_zh_CN.md#参与贡献 - -## 许可证 - -MIT diff --git a/contrib/pai_vscode/VSCodeExt.md b/contrib/pai_vscode/VSCodeExt.md deleted file mode 100644 index 046beb366d..0000000000 --- a/contrib/pai_vscode/VSCodeExt.md +++ /dev/null @@ -1,21 +0,0 @@ -# OpenPAI VS Code Client - -OpenPAI VS Code Client is the dedicated client tool for OpenPAI. It's an extension of [Visual Studio Code](https://code.visualstudio.com/). Visual Studio Code is a lightweight but powerful source code editor which runs on your desktop and is available for Windows, macOS and Linux. - -With OpenPAI VS Code Client, you can submit jobs, simulate jobs locally, manage files, and etc. OpenPAI VS Code Client supports Windows, macOS, and Linux like Visual Studio Code. - -## Installation - -1. Install and launch [Visual Studio Code](https://code.visualstudio.com). Click green button to download and install Visual Studio Code if it's not installed yet. - - ![Download vs code](assets/download_vscode.png) - -1. Click the *Extensions* icon on left side, after Visual Studio Code launched. And input *openpai* to search OpenPAI VS Code Client. - - ![Extension](assets/ext-install-1.png) - -1. Click the **Install** button, and wait installation completes. - -## How-to - -Learn how to [use OpenPAI VS Code Client](./README.md). diff --git a/contrib/pai_vscode/VSCodeExt_zh_CN.md b/contrib/pai_vscode/VSCodeExt_zh_CN.md deleted file mode 100644 index 64cda69156..0000000000 --- a/contrib/pai_vscode/VSCodeExt_zh_CN.md +++ /dev/null @@ -1,21 +0,0 @@ -# OpenPAI VS Code 客户端 - -OpenPAI VS Code Client 是专用于 OpenPAI 的客户端工具。 它是 [Visual Studio Code](https://code.visualstudio.com/) 的扩展组件。 Visual Studio Code 是一个轻量级但功能强大的源代码编辑器,可在主流桌面平台上运行,支持 Windows,macOS 和 Linux。 - -使用 OpenPAI VS Code Client,可以提交作业,在本地模拟作业,管理文件等。 OpenPAI VS Code Client 与 Visual Studio Code 一样,支持 Windows,macOS 和 Linux。 - -## 安装 - -1. 安装并运行 [Visual Studio Code](https://code.visualstudio.com)。 如果未安装,单击绿色按钮以下载并安装 Visual Studio Code。 - - ![下载 VS Code](assets/download_vscode.png) - -2. 启动 Visual Studio Code 后,单击左侧的 *Extensions* 图标。 并输入 *openpai* 来搜索 OpenPAI VS Code Client。 - - ![扩展](assets/ext-install-1.png) - -3. 点击 **Install** 按钮,并等待完成安装。 - -## 入门 - -了解如何[使用 OpenPAI VS Code Client](./README.md)。 \ No newline at end of file diff --git a/contrib/pai_vscode/assets/activity_bar.png b/contrib/pai_vscode/assets/activity_bar.png deleted file mode 100644 index b9f7f1cd68..0000000000 Binary files a/contrib/pai_vscode/assets/activity_bar.png and /dev/null differ diff --git a/contrib/pai_vscode/assets/add-cluster-finish.png b/contrib/pai_vscode/assets/add-cluster-finish.png deleted file mode 100644 index 8f01948440..0000000000 Binary files a/contrib/pai_vscode/assets/add-cluster-finish.png and /dev/null differ diff --git a/contrib/pai_vscode/assets/add_aad_cluster.gif b/contrib/pai_vscode/assets/add_aad_cluster.gif deleted file mode 100644 index 7cec7b6048..0000000000 Binary files a/contrib/pai_vscode/assets/add_aad_cluster.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/add_cluster.png b/contrib/pai_vscode/assets/add_cluster.png deleted file mode 100644 index cc946e5065..0000000000 Binary files a/contrib/pai_vscode/assets/add_cluster.png and /dev/null differ diff --git a/contrib/pai_vscode/assets/add_cluster_host.png b/contrib/pai_vscode/assets/add_cluster_host.png deleted file mode 100644 index 144434ad8d..0000000000 Binary files a/contrib/pai_vscode/assets/add_cluster_host.png and /dev/null differ diff --git a/contrib/pai_vscode/assets/add_personal_storage.gif b/contrib/pai_vscode/assets/add_personal_storage.gif deleted file mode 100644 index 7aa6955ed5..0000000000 Binary files a/contrib/pai_vscode/assets/add_personal_storage.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/auto_completion_code_snippets.gif b/contrib/pai_vscode/assets/auto_completion_code_snippets.gif deleted file mode 100644 index 4dc0011057..0000000000 Binary files a/contrib/pai_vscode/assets/auto_completion_code_snippets.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/auto_completion_property.gif b/contrib/pai_vscode/assets/auto_completion_property.gif deleted file mode 100644 index f2d31836ee..0000000000 Binary files a/contrib/pai_vscode/assets/auto_completion_property.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/auto_completion_runtime_plugin_right_click.gif b/contrib/pai_vscode/assets/auto_completion_runtime_plugin_right_click.gif deleted file mode 100644 index 5943d3519d..0000000000 Binary files a/contrib/pai_vscode/assets/auto_completion_runtime_plugin_right_click.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/auto_completion_runtime_plugin_snippet.gif b/contrib/pai_vscode/assets/auto_completion_runtime_plugin_snippet.gif deleted file mode 100644 index 1c03305fa8..0000000000 Binary files a/contrib/pai_vscode/assets/auto_completion_runtime_plugin_snippet.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/auto_completion_snippet_right_click.gif b/contrib/pai_vscode/assets/auto_completion_snippet_right_click.gif deleted file mode 100644 index 28ae732203..0000000000 Binary files a/contrib/pai_vscode/assets/auto_completion_snippet_right_click.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/connect.gif b/contrib/pai_vscode/assets/connect.gif deleted file mode 100644 index c99eb0fa3a..0000000000 Binary files a/contrib/pai_vscode/assets/connect.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/create_job.gif b/contrib/pai_vscode/assets/create_job.gif deleted file mode 100644 index 0419165fb9..0000000000 Binary files a/contrib/pai_vscode/assets/create_job.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/create_job_config_command.gif b/contrib/pai_vscode/assets/create_job_config_command.gif deleted file mode 100644 index 6b9eb7b0dd..0000000000 Binary files a/contrib/pai_vscode/assets/create_job_config_command.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/create_job_config_double_click.gif b/contrib/pai_vscode/assets/create_job_config_double_click.gif deleted file mode 100644 index 2b236ae1b2..0000000000 Binary files a/contrib/pai_vscode/assets/create_job_config_double_click.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/create_job_config_right_click.gif b/contrib/pai_vscode/assets/create_job_config_right_click.gif deleted file mode 100644 index c6613993f4..0000000000 Binary files a/contrib/pai_vscode/assets/create_job_config_right_click.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/download_vscode.png b/contrib/pai_vscode/assets/download_vscode.png deleted file mode 100644 index ad1e788ce7..0000000000 Binary files a/contrib/pai_vscode/assets/download_vscode.png and /dev/null differ diff --git a/contrib/pai_vscode/assets/ext-install-1.png b/contrib/pai_vscode/assets/ext-install-1.png deleted file mode 100644 index 0e0949dfe2..0000000000 Binary files a/contrib/pai_vscode/assets/ext-install-1.png and /dev/null differ diff --git a/contrib/pai_vscode/assets/install.png b/contrib/pai_vscode/assets/install.png deleted file mode 100644 index 66871eabdd..0000000000 Binary files a/contrib/pai_vscode/assets/install.png and /dev/null differ diff --git a/contrib/pai_vscode/assets/job-list.png b/contrib/pai_vscode/assets/job-list.png deleted file mode 100644 index 19c7d43c9c..0000000000 Binary files a/contrib/pai_vscode/assets/job-list.png and /dev/null differ diff --git a/contrib/pai_vscode/assets/pai_cluster_explorer.png b/contrib/pai_vscode/assets/pai_cluster_explorer.png deleted file mode 100644 index 486e66f1e6..0000000000 Binary files a/contrib/pai_vscode/assets/pai_cluster_explorer.png and /dev/null differ diff --git a/contrib/pai_vscode/assets/pai_logo.png b/contrib/pai_vscode/assets/pai_logo.png deleted file mode 100644 index a8763cb33d..0000000000 Binary files a/contrib/pai_vscode/assets/pai_logo.png and /dev/null differ diff --git a/contrib/pai_vscode/assets/simulate-job.gif b/contrib/pai_vscode/assets/simulate-job.gif deleted file mode 100644 index 665d619ee9..0000000000 Binary files a/contrib/pai_vscode/assets/simulate-job.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/simulate_running.png b/contrib/pai_vscode/assets/simulate_running.png deleted file mode 100644 index 4b5088d410..0000000000 Binary files a/contrib/pai_vscode/assets/simulate_running.png and /dev/null differ diff --git a/contrib/pai_vscode/assets/source_code_auto_upload.gif b/contrib/pai_vscode/assets/source_code_auto_upload.gif deleted file mode 100644 index 2521ad94e7..0000000000 Binary files a/contrib/pai_vscode/assets/source_code_auto_upload.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/storage.gif b/contrib/pai_vscode/assets/storage.gif deleted file mode 100644 index 54e49c0bcf..0000000000 Binary files a/contrib/pai_vscode/assets/storage.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/storage_active_bar.gif b/contrib/pai_vscode/assets/storage_active_bar.gif deleted file mode 100644 index 2f9692baa1..0000000000 Binary files a/contrib/pai_vscode/assets/storage_active_bar.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/storage_delete_file.gif b/contrib/pai_vscode/assets/storage_delete_file.gif deleted file mode 100644 index 6f7450400a..0000000000 Binary files a/contrib/pai_vscode/assets/storage_delete_file.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/storage_download_file.gif b/contrib/pai_vscode/assets/storage_download_file.gif deleted file mode 100644 index 69dda7670c..0000000000 Binary files a/contrib/pai_vscode/assets/storage_download_file.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/storage_new_folder.gif b/contrib/pai_vscode/assets/storage_new_folder.gif deleted file mode 100644 index 15b9bd1baa..0000000000 Binary files a/contrib/pai_vscode/assets/storage_new_folder.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/storage_remote_edit.gif b/contrib/pai_vscode/assets/storage_remote_edit.gif deleted file mode 100644 index 7bbe8e6060..0000000000 Binary files a/contrib/pai_vscode/assets/storage_remote_edit.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/storage_setup_auto_upload.gif b/contrib/pai_vscode/assets/storage_setup_auto_upload.gif deleted file mode 100644 index 5dc1e2d8ee..0000000000 Binary files a/contrib/pai_vscode/assets/storage_setup_auto_upload.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/storage_upload_file.gif b/contrib/pai_vscode/assets/storage_upload_file.gif deleted file mode 100644 index abf97af256..0000000000 Binary files a/contrib/pai_vscode/assets/storage_upload_file.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/submit-job-v2.gif b/contrib/pai_vscode/assets/submit-job-v2.gif deleted file mode 100644 index 53e4f50068..0000000000 Binary files a/contrib/pai_vscode/assets/submit-job-v2.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/submit-job.gif b/contrib/pai_vscode/assets/submit-job.gif deleted file mode 100644 index c9a9169f1e..0000000000 Binary files a/contrib/pai_vscode/assets/submit-job.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/submit.gif b/contrib/pai_vscode/assets/submit.gif deleted file mode 100644 index 16e29ca06d..0000000000 Binary files a/contrib/pai_vscode/assets/submit.gif and /dev/null differ diff --git a/contrib/pai_vscode/assets/yaml_validation_incorrect_type.gif b/contrib/pai_vscode/assets/yaml_validation_incorrect_type.gif deleted file mode 100644 index 3a5e480a7d..0000000000 Binary files a/contrib/pai_vscode/assets/yaml_validation_incorrect_type.gif and /dev/null differ diff --git a/contrib/pai_vscode/documentation/edit_yaml_job_config.md b/contrib/pai_vscode/documentation/edit_yaml_job_config.md deleted file mode 100644 index 455bb8b0dd..0000000000 --- a/contrib/pai_vscode/documentation/edit_yaml_job_config.md +++ /dev/null @@ -1,129 +0,0 @@ -# OpenPAI job config file edit features - -In OpenPAI, all jobs are represented by YAML, a markup language. -Base on VSCode editor [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense) and [YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml), OpenPAI VS Code Client support some features to improve user experience for editing job config file. -For more details about the protocol of OpenPAI job, please refer to [PAI Job Protocol](https://github.com/microsoft/openpai-protocol/blob/master/schemas/v2/schema.yaml). - -- [OpenPAI job config file edit features](#openpai-job-config-file-edit-features) - - [Create job config file](#create-job-config-file) - - [Create in PAI CLUSTER EXPLORER](#create-in-pai-cluster-explorer) - - [Create in VSCode EXPLORER](#create-in-vscode-explorer) - - [Create in command palette](#create-in-command-palette) - - [YAML validation](#yaml-validation) - - [Whitch YAML file is a PAI job config](#whitch-yaml-file-is-a-pai-job-config) - - [Auto completion](#auto-completion) - - [1. Property auto complete](#1-property-auto-complete) - - [2. Code snippets](#2-code-snippets) - - [Trigger by typing](#trigger-by-typing) - - [Right click to insert snippet](#right-click-to-insert-snippet) - - [Insert OpenPAI Runtime Plugin](#insert-openpai-runtime-plugin) - - [Insert by code snippet](#insert-by-code-snippet) - - [Right click to insert](#right-click-to-insert) - - [Reference](#reference) - -## Create job config file - -User can create a simple job config YAML file by below ways: - -### Create in PAI CLUSTER EXPLORER - - Double click `Create Job Config...` in `PAI CLUSTER EXPLORER` - - ![Create Job Config](../assets/create_job_config_double_click.gif) - -### Create in VSCode EXPLORER - - Right click an existing python/cntk file in vscode EXPLORER and select `Create PAI Job Config V2` - - ![Create Job Config](../assets/create_job_config_right_click.gif) - -### Create in command palette - - Press `Ctrl + Shift + P` or `View -> Command Palette...` and enter `PAI: Create PAI Job Config` - - ![Create Job Config](../assets/create_job_config_command.gif) - -## YAML validation - -Use [PAI Job Protocol](https://github.com/microsoft/openpai-protocol/blob/master/schemas/v2/schema.yaml) to do the validation for job config file. - -### Whitch YAML file is a PAI job config - -There's two way to notice the client the YAML file is a PAI job config: - -1. File name ends with `.pai.yaml` -2. Contains `protocolVersion: 2` in the YAML root section - -The validation will detect errors during your editing, such as: - -- Missing property -- Incorrect type -- Duplicate key - -![YAML validation](../assets/yaml_validation_incorrect_type.gif) - -## Auto completion - -To improve user experience in editing job config file, we provide some code auto completion and generation features: - -### 1. Property auto complete - -Base on VSCode editor [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense) and [YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml), user can get suggestions during editing job config. - -![Property auto complete](../assets/auto_completion_property.gif) - -### 2. Code snippets - -We provide several code snippets for VSCode YAML editor, user can use it to form their job config easily. -It could be trigger by typing or right click in the editor and select `OpenPAI: Insert job config` - -#### Trigger by typing - -![Trigger by typing](../assets/auto_completion_code_snippets.gif) - -#### Right click to insert snippet - -![Right click to insert snippet](../assets/auto_completion_snippet_right_click.gif) - -## Insert OpenPAI Runtime Plugin - -OpenPAI support some runtime plugins, such as SSH plugin, Storage plugin and Tensorboard plugin, user can config it in their job and setup the service. -Here is an example of runtime plugin config: - -```yaml -extras: - com.microsoft.pai.runtimeplugin: - - plugin: ssh - parameters: - jobssh: true - - plugin: teamwise_storage - parameters: - storageConfigNames: - - STORAGE_AZUREBLOB - - plugin: tensorboard - parameters: - port: 11449 - logdir: - path: /mnt/tensorboard -``` - -We provide several ways to help user insert the plugin config in YAML file. - -### Insert by code snippet - -The snippet `OpenPAI Runtime Plugin` will include `"com.microsoft.pai.runtimeplugin:"` line, and will ask user to select the first plugin type and generate it. -Typing `"- plugin:"` will trigger `OpenPAI: Insert a runtime plugin config`, it will help user to add other plugins. - -![Insert by code snippet](../assets/auto_completion_runtime_plugin_snippet.gif) - -### Right click to insert - -Right click the editor and select `OpenPAI: Insert job config`, and select `OpenPAI Runtime Plugin` or `OpenPAI Runtime Plugin Item` will help user generate their plugin config and insert it. - -![Right click to insert](../assets/auto_completion_runtime_plugin_right_click.gif) - -## Reference - -[PAI Job Protocol](https://github.com/microsoft/openpai-protocol/blob/master/schemas/v2/schema.yaml) -[Submit Jobs on OpenPAI](https://github.com/microsoft/pai/blob/master/docs/user/job_submission.md#job-workflow) -[Troubleshoot jobs](https://github.com/microsoft/pai/blob/master/docs/user/troubleshooting_job.md) diff --git a/contrib/pai_vscode/documentation/storage_explorer_and_auto_upload.md b/contrib/pai_vscode/documentation/storage_explorer_and_auto_upload.md deleted file mode 100644 index 99af36b48d..0000000000 --- a/contrib/pai_vscode/documentation/storage_explorer_and_auto_upload.md +++ /dev/null @@ -1,161 +0,0 @@ -# Storage Explorer and Auto Upload - -- [Storage Explorer and Auto Upload](#storage-explorer-and-auto-upload) - - [Storage Explorer](#storage-explorer) - - [Cluster Share Storage](#cluster-share-storage) - - [Personal Storage](#personal-storage) - - [Add a personal storage](#add-a-personal-storage) - - [Config personal storage](#config-personal-storage) - - [Storage Operations](#storage-operations) - - [Create folder](#create-folder) - - [File upload/download](#file-uploaddownload) - - [Edit remote file](#edit-remote-file) - - [Auto Upload](#auto-upload) - - [Setup auto upload](#setup-auto-upload) - - [Advance config](#advance-config) - - [Reference](#reference) - -## Storage Explorer - -OpenPAI admin can define Team-wise storage through [Storage Plugin](https://github.com/microsoft/pai/tree/master/contrib/storage_plugin). -`Storage Explorer` in OpenPAI VS Code Client provide a way to let user manage their data in teamwise storage easily. -The explorer can be opened by the icon in activity bar: - -![Activity bar](../assets/storage_active_bar.gif) - -There are two root nodes in the `Storage Explorer`, "Cluster share storage" and "Personal Storage". - -### Cluster Share Storage - -Under `Cluster share storage` node, is storage node, will display the sotrages that config by the cluster admin. -And the children of the storage node is the mount points, whitch means it could be mount into the job container, the description of each mount point is the path in container. - -### Personal Storage - -For some cluster don't have teamwise storage, or users don't want to upload their data to cluster storag, we provide `Personal Storage` to support `Auto Upload` feature, and data management. -Notice that the personal storage can't be mount into job container by the `Storage runtime plugin`, should access it in code or commands manully. - -#### Add a personal storage - -1. Right click the `Personal storage` node, and select `Add Personal Storage`. -2. Input your storage display name, and press `Enter`. -3. A storage config json file will be open, finish it and click `Finish` button at right bottom corner. Notice, it won't be effect, if you save and close the file directly. About how to config personal storage, please refer to next session. - -![Add personal storage](../assets/add_personal_storage.gif) - -#### Config personal storage - -Here is a example about personal storage config: - -```json -{ - // storage server display name - "spn": "test", - "type": "azureblob", - - // Azure blob - "data": { - "dataStore": "dataStore", - "containerName": "test", - "accountName": "test", - "key": "key" - }, - "extension": {} -} -``` - -The personal storage config is same as the teamwise storage server, please refer to [Team wise storage data structures](https://github.com/microsoft/pai/tree/master/contrib/storage_plugin#team-wise-storage-data-structures-). - -### Storage Operations - -Right click on the storage explorer, user can do some operation for the storages. - -#### Create folder - -Right click on `Mount Point` or folder, select `New folder` and input the folder name. - -![Create folder](../assets/storage_new_folder.gif) - -#### File upload/download - -Upload: - -![Upload](../assets/storage_upload_file.gif) - -Download: - -![Download](../assets/storage_download_file.gif) - -#### Edit remote file - -Double click file on `Storage Explorer`, will auto download and open it in VSCode editor, edit the file and save will ask to upload it to remote. - -![Remote Edit](../assets/storage_remote_edit.gif) - -## Auto Upload - -OpenPAI VS Code Client provide auto upload feature, could auto upload user's file to storage before submit job. - -### Setup auto upload - -The first time user click `Create Job Config...` or `Submit Job...`, the client will ask user to setup auto upload. - -1. Click `Yes` when asking "Enable auto uploading of code?" -2. Select a cluster if you have multiple cluster. -3. Select a storage and mount point as the destiniation you want to upload. - -![Setup auto upload](../assets/storage_setup_auto_upload.gif) - -### Advance config - -You can change your auto upload config in `".vscode/settings.json"` file. -Here is an example for the `settings.json`: - -```json -{ - "pai.job.generateJobName.enabled": true, - "pai.job.v2.upload": { - "cluster_1": { - "enable": true, - "include": [ - "**/*.py", - "**/*.txt" - ], - "exclude": [], - "storageType": "cluster", - "storageName": "confignfs", - "storageMountPoint": "/home" - }, - "cluster_2": { - "enable": true, - "include": [ - "**/*.py" - ], - "exclude": [], - "storageType": "cluster", - "storageName": "confignfs", - "storageMountPoint": "/data" - } - } -} -``` - -The field `"pai.job.v2.upload"` is the auto upload config, contains objects as: - -```json -"": { - "enable": true, // true means enable the auto upload - "include": [ // filter files to upload - "**/*.py" - ], - "exclude": [], // filter files not to upload - "storageType": "cluster", // "cluster" or "personal" - "storageName": "", // storage name - "storageMountPoint": "/home" // storage mount point, ignore it if storageType is "personal" -} -``` - -By default the auto upload will only upload `".py"` files, user can change the `"include"` field and `"exclude"` field to setting the upload filter. - -## Reference -[Storage Plugin](https://github.com/microsoft/pai/tree/master/contrib/storage_plugin) diff --git a/contrib/pai_vscode/documentation/submit_job.md b/contrib/pai_vscode/documentation/submit_job.md deleted file mode 100644 index 0ab430a9ae..0000000000 --- a/contrib/pai_vscode/documentation/submit_job.md +++ /dev/null @@ -1,118 +0,0 @@ -# Submit job to OpenPAI by VSCode Extension - -This document is a tutorial for OpenPAI job submission on VSCode Extension. -Before learning this document, make sure you have an OpenPAI cluster already, and already install VSCode. - -- [Submit job to OpenPAI by VSCode Extension](#submit-job-to-openpai-by-vscode-extension) - - [Install OpenPAI VSCode Client](#install-openpai-vscode-client) - - [Connect to OpenPAI Cluster](#connect-to-openpai-cluster) - - [Submit a Hello World Job](#submit-a-hello-world-job) - - [Create a job config file](#create-a-job-config-file) - - [Edit the config file](#edit-the-config-file) - - [Submit it](#submit-it) - - [Manage Your Data](#manage-your-data) - - [Teamwise Storage](#teamwise-storage) - - [Storage explorer](#storage-explorer) - - [Source code auto upload](#source-code-auto-upload) - - [Reference](#reference) - -## Install OpenPAI VSCode Client - -In VSCode Extension Marketplace search [OpenPAI VS Code Client](https://marketplace.visualstudio.com/items?itemName=OpenPAIVSCodeClient.pai-vscode), and click Install. - -![Install](../assets/install.png) - -## Connect to OpenPAI Cluster - -1. Click the '+' button in `PAI CLUSTER EXPLORER`, or press `Ctrl+Shift+P` to open command palette look for `"PAI: Add PAI Cluster"` command and select it. -2. Input input the host of an OpenPAI cluster. It can be domain name or IP Address. After that, press `Enter`. -3. A configuration file is opened, you can config it. Once it completes, click `Finish` button at right bottom corner. Notice, it won't be effect, if you save and close the file directly. -4. If there are multiple OpenPAI clusters, you can follow above steps again to connect with them. - -![Connect](../assets/connect.gif) - -## Submit a Hello World Job - -The job of OpenPAI defines how to execute code(s) and command(s) in specified environment(s). A job can be run on single node or distributedly. -The following process submits a model training job implemented by TensorFlow on CIFAR-10 dataset. It downloads data and code from internet and helps getting started with OpenPAI. - -### Create a job config file - -1. Create a new folder name `'hello world job'` and open it in VSCode. -2. Double click the `Create Job Config...` button in `PAI CLUSTER EXPLORER`, the first time you click, it may ask you to config the `Auto Upload` and `Add job name suffix` feature, in this hello world job, we can select `No` for `Auto Upload` and `Yes` for `Add job name suffix`. -3. Save the job config file in your vscode workspace. - -![Create job](../assets/create_job.gif) - -For other ways to create job config file, refer to [Create job config file](edit_yaml_job_config.md#Create-job-config-file). - -### Edit the config file - -Change the config file to this: - -```yaml -protocolVersion: 2 -name: hello_world_job -type: job -prerequisites: - - name: image - type: dockerimage - uri: tensorflow/tensorflow:1.15.2-gpu -taskRoles: - train: - instances: 1 - dockerImage: image - resourcePerInstance: - cpu: 1 - memoryMB: 16384 - gpu: 1 - commands: - - apt update - - apt install -y git - - git clone https://github.com/tensorflow/models - - cd models/research/slim - - pip install Pillow - - pip install contextlib2 - - python download_and_convert_data.py --dataset_name=cifar10 --dataset_dir=/tmp/data - - python train_image_classifier.py --dataset_name=cifar10 --dataset_dir=/tmp/data --max_number_of_steps=1000 -``` - -The `OpenPAI VS Code Client` support some features to improve user experience for editing job config file, please refer to [OpenPAI job config file edit features](edit_yaml_job_config.md). -To learn more about this job, please refer to [Learn the Hello World Job](https://github.com/microsoft/pai/blob/master/docs/user/job_submission.md#Learn-the-Hello-World-Job) - -### Submit it - -Finish editing the config file, save it and right click on the editor and select `Submit Job to PAI Cluster`. -After the information `Successfully submitted job.` pop up, you can click the `Open job page` button at right bottom corner, and view you job on website. - -![Submit](../assets/submit.gif) - -## Manage Your Data - -Most model training and other kinds of jobs need to transfer files between running environments and outside. Files include dataset, code, scripts, trained model, and so on. - -### Teamwise Storage - -OpenPAI admin can define Team-wise storage through [Storage Plugin](https://github.com/microsoft/pai/tree/master/contrib/storage_plugin). -User's job container can mount to the storage if user add it in job config file, for how to insert storage plugin into job config, please refer to [Insert OpenPAI Runtime Plugin](edit_yaml_job_config.md#Insert-OpenPAI-Runtime-Plugin). - -### Storage explorer - -To manage user's data in Team-wise storage, `OpenPAI VS Code Client` support a `STORAGE EXPLORER` in vscode, User can manage data in the explorer. -We also support an `Auto Upload` feature in VSCode, the client will auto upload user's project file to the storage before submit job. -For more detail, refer to [Storage Explorer and Auto Upload](storage_explorer_and_auto_upload.md) - -![Storage Explorer](../assets/storage.gif) - -### Source code auto upload - -VSCode is a very powerful editor, user can use it to edit their source code, the auto upload feature make the source code to PAI job easily. -For more detail, refer to [Storage Explorer and Auto Upload](storage_explorer_and_auto_upload.md#Auto-Upload) - -![Source code auto upload](../assets/source_code_auto_upload.gif) - -## Reference - -[PAI Job Protocol](https://github.com/microsoft/openpai-protocol/blob/master/schemas/v2/schema.yaml) -[Submit Jobs on OpenPAI](https://github.com/microsoft/pai/blob/master/docs/user/job_submission.md#job-workflow) -[Troubleshoot jobs](https://github.com/microsoft/pai/blob/master/docs/user/troubleshooting_job.md) diff --git a/contrib/pai_vscode/i18n/cldr/likelySubtags.json b/contrib/pai_vscode/i18n/cldr/likelySubtags.json deleted file mode 100644 index b844861a7a..0000000000 --- a/contrib/pai_vscode/i18n/cldr/likelySubtags.json +++ /dev/null @@ -1,1810 +0,0 @@ -{ - "supplemental": { - "version": { - "_number": "$Revision: 12932 $", - "_unicodeVersion": "9.0.0", - "_cldrVersion": "30.0.3" - }, - "likelySubtags": { - "aa": "aa-Latn-ET", - "aai": "aai-Latn-ZZ", - "aak": "aak-Latn-ZZ", - "aau": "aau-Latn-ZZ", - "ab": "ab-Cyrl-GE", - "abi": "abi-Latn-ZZ", - "abr": "abr-Latn-GH", - "abt": "abt-Latn-ZZ", - "aby": "aby-Latn-ZZ", - "acd": "acd-Latn-ZZ", - "ace": "ace-Latn-ID", - "ach": "ach-Latn-UG", - "ada": "ada-Latn-GH", - "ade": "ade-Latn-ZZ", - "adj": "adj-Latn-ZZ", - "ady": "ady-Cyrl-RU", - "adz": "adz-Latn-ZZ", - "ae": "ae-Avst-IR", - "aeb": "aeb-Arab-TN", - "aey": "aey-Latn-ZZ", - "af": "af-Latn-ZA", - "agc": "agc-Latn-ZZ", - "agd": "agd-Latn-ZZ", - "agg": "agg-Latn-ZZ", - "agm": "agm-Latn-ZZ", - "ago": "ago-Latn-ZZ", - "agq": "agq-Latn-CM", - "aha": "aha-Latn-ZZ", - "ahl": "ahl-Latn-ZZ", - "aho": "aho-Ahom-IN", - "ajg": "ajg-Latn-ZZ", - "ak": "ak-Latn-GH", - "akk": "akk-Xsux-IQ", - "ala": "ala-Latn-ZZ", - "ali": "ali-Latn-ZZ", - "aln": "aln-Latn-XK", - "alt": "alt-Cyrl-RU", - "am": "am-Ethi-ET", - "amm": "amm-Latn-ZZ", - "amn": "amn-Latn-ZZ", - "amo": "amo-Latn-NG", - "amp": "amp-Latn-ZZ", - "anc": "anc-Latn-ZZ", - "ank": "ank-Latn-ZZ", - "ann": "ann-Latn-ZZ", - "any": "any-Latn-ZZ", - "aoj": "aoj-Latn-ZZ", - "aom": "aom-Latn-ZZ", - "aoz": "aoz-Latn-ID", - "apc": "apc-Arab-ZZ", - "apd": "apd-Arab-TG", - "ape": "ape-Latn-ZZ", - "apr": "apr-Latn-ZZ", - "aps": "aps-Latn-ZZ", - "apz": "apz-Latn-ZZ", - "ar": "ar-Arab-EG", - "arc": "arc-Armi-IR", - "arc-Nbat": "arc-Nbat-JO", - "arc-Palm": "arc-Palm-SY", - "arh": "arh-Latn-ZZ", - "arn": "arn-Latn-CL", - "aro": "aro-Latn-BO", - "arq": "arq-Arab-DZ", - "ary": "ary-Arab-MA", - "arz": "arz-Arab-EG", - "as": "as-Beng-IN", - "asa": "asa-Latn-TZ", - "ase": "ase-Sgnw-US", - "asg": "asg-Latn-ZZ", - "aso": "aso-Latn-ZZ", - "ast": "ast-Latn-ES", - "ata": "ata-Latn-ZZ", - "atg": "atg-Latn-ZZ", - "atj": "atj-Latn-CA", - "auy": "auy-Latn-ZZ", - "av": "av-Cyrl-RU", - "avl": "avl-Arab-ZZ", - "avn": "avn-Latn-ZZ", - "avt": "avt-Latn-ZZ", - "avu": "avu-Latn-ZZ", - "awa": "awa-Deva-IN", - "awb": "awb-Latn-ZZ", - "awo": "awo-Latn-ZZ", - "awx": "awx-Latn-ZZ", - "ay": "ay-Latn-BO", - "ayb": "ayb-Latn-ZZ", - "az": "az-Latn-AZ", - "az-Arab": "az-Arab-IR", - "az-IQ": "az-Arab-IQ", - "az-IR": "az-Arab-IR", - "az-RU": "az-Cyrl-RU", - "ba": "ba-Cyrl-RU", - "bal": "bal-Arab-PK", - "ban": "ban-Latn-ID", - "bap": "bap-Deva-NP", - "bar": "bar-Latn-AT", - "bas": "bas-Latn-CM", - "bav": "bav-Latn-ZZ", - "bax": "bax-Bamu-CM", - "bba": "bba-Latn-ZZ", - "bbb": "bbb-Latn-ZZ", - "bbc": "bbc-Latn-ID", - "bbd": "bbd-Latn-ZZ", - "bbj": "bbj-Latn-CM", - "bbp": "bbp-Latn-ZZ", - "bbr": "bbr-Latn-ZZ", - "bcf": "bcf-Latn-ZZ", - "bch": "bch-Latn-ZZ", - "bci": "bci-Latn-CI", - "bcm": "bcm-Latn-ZZ", - "bcn": "bcn-Latn-ZZ", - "bco": "bco-Latn-ZZ", - "bcq": "bcq-Ethi-ZZ", - "bcu": "bcu-Latn-ZZ", - "bdd": "bdd-Latn-ZZ", - "be": "be-Cyrl-BY", - "bef": "bef-Latn-ZZ", - "beh": "beh-Latn-ZZ", - "bej": "bej-Arab-SD", - "bem": "bem-Latn-ZM", - "bet": "bet-Latn-ZZ", - "bew": "bew-Latn-ID", - "bex": "bex-Latn-ZZ", - "bez": "bez-Latn-TZ", - "bfd": "bfd-Latn-CM", - "bfq": "bfq-Taml-IN", - "bft": "bft-Arab-PK", - "bfy": "bfy-Deva-IN", - "bg": "bg-Cyrl-BG", - "bgc": "bgc-Deva-IN", - "bgn": "bgn-Arab-PK", - "bgx": "bgx-Grek-TR", - "bhb": "bhb-Deva-IN", - "bhg": "bhg-Latn-ZZ", - "bhi": "bhi-Deva-IN", - "bhk": "bhk-Latn-PH", - "bhl": "bhl-Latn-ZZ", - "bho": "bho-Deva-IN", - "bhy": "bhy-Latn-ZZ", - "bi": "bi-Latn-VU", - "bib": "bib-Latn-ZZ", - "big": "big-Latn-ZZ", - "bik": "bik-Latn-PH", - "bim": "bim-Latn-ZZ", - "bin": "bin-Latn-NG", - "bio": "bio-Latn-ZZ", - "biq": "biq-Latn-ZZ", - "bjh": "bjh-Latn-ZZ", - "bji": "bji-Ethi-ZZ", - "bjj": "bjj-Deva-IN", - "bjn": "bjn-Latn-ID", - "bjo": "bjo-Latn-ZZ", - "bjr": "bjr-Latn-ZZ", - "bjz": "bjz-Latn-ZZ", - "bkc": "bkc-Latn-ZZ", - "bkm": "bkm-Latn-CM", - "bkq": "bkq-Latn-ZZ", - "bku": "bku-Latn-PH", - "bkv": "bkv-Latn-ZZ", - "blt": "blt-Tavt-VN", - "bm": "bm-Latn-ML", - "bmh": "bmh-Latn-ZZ", - "bmk": "bmk-Latn-ZZ", - "bmq": "bmq-Latn-ML", - "bmu": "bmu-Latn-ZZ", - "bn": "bn-Beng-BD", - "bng": "bng-Latn-ZZ", - "bnm": "bnm-Latn-ZZ", - "bnp": "bnp-Latn-ZZ", - "bo": "bo-Tibt-CN", - "boj": "boj-Latn-ZZ", - "bom": "bom-Latn-ZZ", - "bon": "bon-Latn-ZZ", - "bpy": "bpy-Beng-IN", - "bqc": "bqc-Latn-ZZ", - "bqi": "bqi-Arab-IR", - "bqp": "bqp-Latn-ZZ", - "bqv": "bqv-Latn-CI", - "br": "br-Latn-FR", - "bra": "bra-Deva-IN", - "brh": "brh-Arab-PK", - "brx": "brx-Deva-IN", - "brz": "brz-Latn-ZZ", - "bs": "bs-Latn-BA", - "bsj": "bsj-Latn-ZZ", - "bsq": "bsq-Bass-LR", - "bss": "bss-Latn-CM", - "bst": "bst-Ethi-ZZ", - "bto": "bto-Latn-PH", - "btt": "btt-Latn-ZZ", - "btv": "btv-Deva-PK", - "bua": "bua-Cyrl-RU", - "buc": "buc-Latn-YT", - "bud": "bud-Latn-ZZ", - "bug": "bug-Latn-ID", - "buk": "buk-Latn-ZZ", - "bum": "bum-Latn-CM", - "buo": "buo-Latn-ZZ", - "bus": "bus-Latn-ZZ", - "buu": "buu-Latn-ZZ", - "bvb": "bvb-Latn-GQ", - "bwd": "bwd-Latn-ZZ", - "bwr": "bwr-Latn-ZZ", - "bxh": "bxh-Latn-ZZ", - "bye": "bye-Latn-ZZ", - "byn": "byn-Ethi-ER", - "byr": "byr-Latn-ZZ", - "bys": "bys-Latn-ZZ", - "byv": "byv-Latn-CM", - "byx": "byx-Latn-ZZ", - "bza": "bza-Latn-ZZ", - "bze": "bze-Latn-ML", - "bzf": "bzf-Latn-ZZ", - "bzh": "bzh-Latn-ZZ", - "bzw": "bzw-Latn-ZZ", - "ca": "ca-Latn-ES", - "can": "can-Latn-ZZ", - "cbj": "cbj-Latn-ZZ", - "cch": "cch-Latn-NG", - "ccp": "ccp-Beng-IN", - "ccp-Cakm": "ccp-Cakm-BD", - "ce": "ce-Cyrl-RU", - "ceb": "ceb-Latn-PH", - "cfa": "cfa-Latn-ZZ", - "cgg": "cgg-Latn-UG", - "ch": "ch-Latn-GU", - "chk": "chk-Latn-FM", - "chm": "chm-Cyrl-RU", - "cho": "cho-Latn-US", - "chp": "chp-Latn-CA", - "chr": "chr-Cher-US", - "cja": "cja-Arab-KH", - "cjm": "cjm-Cham-VN", - "cjv": "cjv-Latn-ZZ", - "ckb": "ckb-Arab-IQ", - "ckl": "ckl-Latn-ZZ", - "cko": "cko-Latn-ZZ", - "cky": "cky-Latn-ZZ", - "cla": "cla-Latn-ZZ", - "cme": "cme-Latn-ZZ", - "co": "co-Latn-FR", - "cop": "cop-Copt-EG", - "cps": "cps-Latn-PH", - "cr": "cr-Cans-CA", - "crj": "crj-Cans-CA", - "crk": "crk-Cans-CA", - "crl": "crl-Cans-CA", - "crm": "crm-Cans-CA", - "crs": "crs-Latn-SC", - "cs": "cs-Latn-CZ", - "csb": "csb-Latn-PL", - "csw": "csw-Cans-CA", - "ctd": "ctd-Pauc-MM", - "cu": "cu-Cyrl-RU", - "cu-Glag": "cu-Glag-BG", - "cv": "cv-Cyrl-RU", - "cy": "cy-Latn-GB", - "da": "da-Latn-DK", - "dad": "dad-Latn-ZZ", - "daf": "daf-Latn-ZZ", - "dag": "dag-Latn-ZZ", - "dah": "dah-Latn-ZZ", - "dak": "dak-Latn-US", - "dar": "dar-Cyrl-RU", - "dav": "dav-Latn-KE", - "dbd": "dbd-Latn-ZZ", - "dbq": "dbq-Latn-ZZ", - "dcc": "dcc-Arab-IN", - "ddn": "ddn-Latn-ZZ", - "de": "de-Latn-DE", - "ded": "ded-Latn-ZZ", - "den": "den-Latn-CA", - "dga": "dga-Latn-ZZ", - "dgh": "dgh-Latn-ZZ", - "dgi": "dgi-Latn-ZZ", - "dgl": "dgl-Arab-ZZ", - "dgr": "dgr-Latn-CA", - "dgz": "dgz-Latn-ZZ", - "dia": "dia-Latn-ZZ", - "dje": "dje-Latn-NE", - "dnj": "dnj-Latn-CI", - "dob": "dob-Latn-ZZ", - "doi": "doi-Arab-IN", - "dop": "dop-Latn-ZZ", - "dow": "dow-Latn-ZZ", - "dri": "dri-Latn-ZZ", - "drs": "drs-Ethi-ZZ", - "dsb": "dsb-Latn-DE", - "dtm": "dtm-Latn-ML", - "dtp": "dtp-Latn-MY", - "dts": "dts-Latn-ZZ", - "dty": "dty-Deva-NP", - "dua": "dua-Latn-CM", - "duc": "duc-Latn-ZZ", - "dud": "dud-Latn-ZZ", - "dug": "dug-Latn-ZZ", - "dv": "dv-Thaa-MV", - "dva": "dva-Latn-ZZ", - "dww": "dww-Latn-ZZ", - "dyo": "dyo-Latn-SN", - "dyu": "dyu-Latn-BF", - "dz": "dz-Tibt-BT", - "dzg": "dzg-Latn-ZZ", - "ebu": "ebu-Latn-KE", - "ee": "ee-Latn-GH", - "efi": "efi-Latn-NG", - "egl": "egl-Latn-IT", - "egy": "egy-Egyp-EG", - "eky": "eky-Kali-MM", - "el": "el-Grek-GR", - "ema": "ema-Latn-ZZ", - "emi": "emi-Latn-ZZ", - "en": "en-Latn-US", - "en-Shaw": "en-Shaw-GB", - "enn": "enn-Latn-ZZ", - "enq": "enq-Latn-ZZ", - "eo": "eo-Latn-001", - "eri": "eri-Latn-ZZ", - "es": "es-Latn-ES", - "esu": "esu-Latn-US", - "et": "et-Latn-EE", - "etr": "etr-Latn-ZZ", - "ett": "ett-Ital-IT", - "etu": "etu-Latn-ZZ", - "etx": "etx-Latn-ZZ", - "eu": "eu-Latn-ES", - "ewo": "ewo-Latn-CM", - "ext": "ext-Latn-ES", - "fa": "fa-Arab-IR", - "faa": "faa-Latn-ZZ", - "fab": "fab-Latn-ZZ", - "fag": "fag-Latn-ZZ", - "fai": "fai-Latn-ZZ", - "fan": "fan-Latn-GQ", - "ff": "ff-Latn-SN", - "ff-Adlm": "ff-Adlm-GN", - "ffi": "ffi-Latn-ZZ", - "ffm": "ffm-Latn-ML", - "fi": "fi-Latn-FI", - "fia": "fia-Arab-SD", - "fil": "fil-Latn-PH", - "fit": "fit-Latn-SE", - "fj": "fj-Latn-FJ", - "flr": "flr-Latn-ZZ", - "fmp": "fmp-Latn-ZZ", - "fo": "fo-Latn-FO", - "fod": "fod-Latn-ZZ", - "fon": "fon-Latn-BJ", - "for": "for-Latn-ZZ", - "fpe": "fpe-Latn-ZZ", - "fqs": "fqs-Latn-ZZ", - "fr": "fr-Latn-FR", - "frc": "frc-Latn-US", - "frp": "frp-Latn-FR", - "frr": "frr-Latn-DE", - "frs": "frs-Latn-DE", - "fub": "fub-Arab-CM", - "fud": "fud-Latn-WF", - "fue": "fue-Latn-ZZ", - "fuf": "fuf-Latn-GN", - "fuh": "fuh-Latn-ZZ", - "fuq": "fuq-Latn-NE", - "fur": "fur-Latn-IT", - "fuv": "fuv-Latn-NG", - "fuy": "fuy-Latn-ZZ", - "fvr": "fvr-Latn-SD", - "fy": "fy-Latn-NL", - "ga": "ga-Latn-IE", - "gaa": "gaa-Latn-GH", - "gaf": "gaf-Latn-ZZ", - "gag": "gag-Latn-MD", - "gah": "gah-Latn-ZZ", - "gaj": "gaj-Latn-ZZ", - "gam": "gam-Latn-ZZ", - "gan": "gan-Hans-CN", - "gaw": "gaw-Latn-ZZ", - "gay": "gay-Latn-ID", - "gbf": "gbf-Latn-ZZ", - "gbm": "gbm-Deva-IN", - "gby": "gby-Latn-ZZ", - "gbz": "gbz-Arab-IR", - "gcr": "gcr-Latn-GF", - "gd": "gd-Latn-GB", - "gde": "gde-Latn-ZZ", - "gdn": "gdn-Latn-ZZ", - "gdr": "gdr-Latn-ZZ", - "geb": "geb-Latn-ZZ", - "gej": "gej-Latn-ZZ", - "gel": "gel-Latn-ZZ", - "gez": "gez-Ethi-ET", - "gfk": "gfk-Latn-ZZ", - "ggn": "ggn-Deva-NP", - "ghs": "ghs-Latn-ZZ", - "gil": "gil-Latn-KI", - "gim": "gim-Latn-ZZ", - "gjk": "gjk-Arab-PK", - "gjn": "gjn-Latn-ZZ", - "gju": "gju-Arab-PK", - "gkn": "gkn-Latn-ZZ", - "gkp": "gkp-Latn-ZZ", - "gl": "gl-Latn-ES", - "glk": "glk-Arab-IR", - "gmm": "gmm-Latn-ZZ", - "gmv": "gmv-Ethi-ZZ", - "gn": "gn-Latn-PY", - "gnd": "gnd-Latn-ZZ", - "gng": "gng-Latn-ZZ", - "god": "god-Latn-ZZ", - "gof": "gof-Ethi-ZZ", - "goi": "goi-Latn-ZZ", - "gom": "gom-Deva-IN", - "gon": "gon-Telu-IN", - "gor": "gor-Latn-ID", - "gos": "gos-Latn-NL", - "got": "got-Goth-UA", - "grc": "grc-Cprt-CY", - "grc-Linb": "grc-Linb-GR", - "grt": "grt-Beng-IN", - "grw": "grw-Latn-ZZ", - "gsw": "gsw-Latn-CH", - "gu": "gu-Gujr-IN", - "gub": "gub-Latn-BR", - "guc": "guc-Latn-CO", - "gud": "gud-Latn-ZZ", - "gur": "gur-Latn-GH", - "guw": "guw-Latn-ZZ", - "gux": "gux-Latn-ZZ", - "guz": "guz-Latn-KE", - "gv": "gv-Latn-IM", - "gvf": "gvf-Latn-ZZ", - "gvr": "gvr-Deva-NP", - "gvs": "gvs-Latn-ZZ", - "gwc": "gwc-Arab-ZZ", - "gwi": "gwi-Latn-CA", - "gwt": "gwt-Arab-ZZ", - "gyi": "gyi-Latn-ZZ", - "ha": "ha-Latn-NG", - "ha-CM": "ha-Arab-CM", - "ha-SD": "ha-Arab-SD", - "hag": "hag-Latn-ZZ", - "hak": "hak-Hans-CN", - "ham": "ham-Latn-ZZ", - "haw": "haw-Latn-US", - "haz": "haz-Arab-AF", - "hbb": "hbb-Latn-ZZ", - "hdy": "hdy-Ethi-ZZ", - "he": "he-Hebr-IL", - "hhy": "hhy-Latn-ZZ", - "hi": "hi-Deva-IN", - "hia": "hia-Latn-ZZ", - "hif": "hif-Latn-FJ", - "hig": "hig-Latn-ZZ", - "hih": "hih-Latn-ZZ", - "hil": "hil-Latn-PH", - "hla": "hla-Latn-ZZ", - "hlu": "hlu-Hluw-TR", - "hmd": "hmd-Plrd-CN", - "hmt": "hmt-Latn-ZZ", - "hnd": "hnd-Arab-PK", - "hne": "hne-Deva-IN", - "hnj": "hnj-Hmng-LA", - "hnn": "hnn-Latn-PH", - "hno": "hno-Arab-PK", - "ho": "ho-Latn-PG", - "hoc": "hoc-Deva-IN", - "hoj": "hoj-Deva-IN", - "hot": "hot-Latn-ZZ", - "hr": "hr-Latn-HR", - "hsb": "hsb-Latn-DE", - "hsn": "hsn-Hans-CN", - "ht": "ht-Latn-HT", - "hu": "hu-Latn-HU", - "hui": "hui-Latn-ZZ", - "hy": "hy-Armn-AM", - "hz": "hz-Latn-NA", - "ia": "ia-Latn-FR", - "ian": "ian-Latn-ZZ", - "iar": "iar-Latn-ZZ", - "iba": "iba-Latn-MY", - "ibb": "ibb-Latn-NG", - "iby": "iby-Latn-ZZ", - "ica": "ica-Latn-ZZ", - "ich": "ich-Latn-ZZ", - "id": "id-Latn-ID", - "idd": "idd-Latn-ZZ", - "idi": "idi-Latn-ZZ", - "idu": "idu-Latn-ZZ", - "ig": "ig-Latn-NG", - "igb": "igb-Latn-ZZ", - "ige": "ige-Latn-ZZ", - "ii": "ii-Yiii-CN", - "ijj": "ijj-Latn-ZZ", - "ik": "ik-Latn-US", - "ikk": "ikk-Latn-ZZ", - "ikt": "ikt-Latn-CA", - "ikw": "ikw-Latn-ZZ", - "ikx": "ikx-Latn-ZZ", - "ilo": "ilo-Latn-PH", - "imo": "imo-Latn-ZZ", - "in": "in-Latn-ID", - "inh": "inh-Cyrl-RU", - "iou": "iou-Latn-ZZ", - "iri": "iri-Latn-ZZ", - "is": "is-Latn-IS", - "it": "it-Latn-IT", - "iu": "iu-Cans-CA", - "iw": "iw-Hebr-IL", - "iwm": "iwm-Latn-ZZ", - "iws": "iws-Latn-ZZ", - "izh": "izh-Latn-RU", - "izi": "izi-Latn-ZZ", - "ja": "ja-Jpan-JP", - "jab": "jab-Latn-ZZ", - "jam": "jam-Latn-JM", - "jbu": "jbu-Latn-ZZ", - "jen": "jen-Latn-ZZ", - "jgk": "jgk-Latn-ZZ", - "jgo": "jgo-Latn-CM", - "ji": "ji-Hebr-UA", - "jib": "jib-Latn-ZZ", - "jmc": "jmc-Latn-TZ", - "jml": "jml-Deva-NP", - "jra": "jra-Latn-ZZ", - "jut": "jut-Latn-DK", - "jv": "jv-Latn-ID", - "jw": "jw-Latn-ID", - "ka": "ka-Geor-GE", - "kaa": "kaa-Cyrl-UZ", - "kab": "kab-Latn-DZ", - "kac": "kac-Latn-MM", - "kad": "kad-Latn-ZZ", - "kai": "kai-Latn-ZZ", - "kaj": "kaj-Latn-NG", - "kam": "kam-Latn-KE", - "kao": "kao-Latn-ML", - "kbd": "kbd-Cyrl-RU", - "kbm": "kbm-Latn-ZZ", - "kbp": "kbp-Latn-ZZ", - "kbq": "kbq-Latn-ZZ", - "kbx": "kbx-Latn-ZZ", - "kby": "kby-Arab-NE", - "kcg": "kcg-Latn-NG", - "kck": "kck-Latn-ZW", - "kcl": "kcl-Latn-ZZ", - "kct": "kct-Latn-ZZ", - "kde": "kde-Latn-TZ", - "kdh": "kdh-Arab-TG", - "kdl": "kdl-Latn-ZZ", - "kdt": "kdt-Thai-TH", - "kea": "kea-Latn-CV", - "ken": "ken-Latn-CM", - "kez": "kez-Latn-ZZ", - "kfo": "kfo-Latn-CI", - "kfr": "kfr-Deva-IN", - "kfy": "kfy-Deva-IN", - "kg": "kg-Latn-CD", - "kge": "kge-Latn-ID", - "kgf": "kgf-Latn-ZZ", - "kgp": "kgp-Latn-BR", - "kha": "kha-Latn-IN", - "khb": "khb-Talu-CN", - "khn": "khn-Deva-IN", - "khq": "khq-Latn-ML", - "khs": "khs-Latn-ZZ", - "kht": "kht-Mymr-IN", - "khw": "khw-Arab-PK", - "khz": "khz-Latn-ZZ", - "ki": "ki-Latn-KE", - "kij": "kij-Latn-ZZ", - "kiu": "kiu-Latn-TR", - "kiw": "kiw-Latn-ZZ", - "kj": "kj-Latn-NA", - "kjd": "kjd-Latn-ZZ", - "kjg": "kjg-Laoo-LA", - "kjs": "kjs-Latn-ZZ", - "kjy": "kjy-Latn-ZZ", - "kk": "kk-Cyrl-KZ", - "kk-AF": "kk-Arab-AF", - "kk-Arab": "kk-Arab-CN", - "kk-CN": "kk-Arab-CN", - "kk-IR": "kk-Arab-IR", - "kk-MN": "kk-Arab-MN", - "kkc": "kkc-Latn-ZZ", - "kkj": "kkj-Latn-CM", - "kl": "kl-Latn-GL", - "kln": "kln-Latn-KE", - "klq": "klq-Latn-ZZ", - "klt": "klt-Latn-ZZ", - "klx": "klx-Latn-ZZ", - "km": "km-Khmr-KH", - "kmb": "kmb-Latn-AO", - "kmh": "kmh-Latn-ZZ", - "kmo": "kmo-Latn-ZZ", - "kms": "kms-Latn-ZZ", - "kmu": "kmu-Latn-ZZ", - "kmw": "kmw-Latn-ZZ", - "kn": "kn-Knda-IN", - "knp": "knp-Latn-ZZ", - "ko": "ko-Kore-KR", - "koi": "koi-Cyrl-RU", - "kok": "kok-Deva-IN", - "kol": "kol-Latn-ZZ", - "kos": "kos-Latn-FM", - "koz": "koz-Latn-ZZ", - "kpe": "kpe-Latn-LR", - "kpf": "kpf-Latn-ZZ", - "kpo": "kpo-Latn-ZZ", - "kpr": "kpr-Latn-ZZ", - "kpx": "kpx-Latn-ZZ", - "kqb": "kqb-Latn-ZZ", - "kqf": "kqf-Latn-ZZ", - "kqs": "kqs-Latn-ZZ", - "kqy": "kqy-Ethi-ZZ", - "krc": "krc-Cyrl-RU", - "kri": "kri-Latn-SL", - "krj": "krj-Latn-PH", - "krl": "krl-Latn-RU", - "krs": "krs-Latn-ZZ", - "kru": "kru-Deva-IN", - "ks": "ks-Arab-IN", - "ksb": "ksb-Latn-TZ", - "ksd": "ksd-Latn-ZZ", - "ksf": "ksf-Latn-CM", - "ksh": "ksh-Latn-DE", - "ksj": "ksj-Latn-ZZ", - "ksr": "ksr-Latn-ZZ", - "ktb": "ktb-Ethi-ZZ", - "ktm": "ktm-Latn-ZZ", - "kto": "kto-Latn-ZZ", - "ku": "ku-Latn-TR", - "ku-Arab": "ku-Arab-IQ", - "ku-LB": "ku-Arab-LB", - "kub": "kub-Latn-ZZ", - "kud": "kud-Latn-ZZ", - "kue": "kue-Latn-ZZ", - "kuj": "kuj-Latn-ZZ", - "kum": "kum-Cyrl-RU", - "kun": "kun-Latn-ZZ", - "kup": "kup-Latn-ZZ", - "kus": "kus-Latn-ZZ", - "kv": "kv-Cyrl-RU", - "kvg": "kvg-Latn-ZZ", - "kvr": "kvr-Latn-ID", - "kvx": "kvx-Arab-PK", - "kw": "kw-Latn-GB", - "kwj": "kwj-Latn-ZZ", - "kwo": "kwo-Latn-ZZ", - "kxa": "kxa-Latn-ZZ", - "kxc": "kxc-Ethi-ZZ", - "kxm": "kxm-Thai-TH", - "kxp": "kxp-Arab-PK", - "kxw": "kxw-Latn-ZZ", - "kxz": "kxz-Latn-ZZ", - "ky": "ky-Cyrl-KG", - "ky-Arab": "ky-Arab-CN", - "ky-CN": "ky-Arab-CN", - "ky-Latn": "ky-Latn-TR", - "ky-TR": "ky-Latn-TR", - "kye": "kye-Latn-ZZ", - "kyx": "kyx-Latn-ZZ", - "kzr": "kzr-Latn-ZZ", - "la": "la-Latn-VA", - "lab": "lab-Lina-GR", - "lad": "lad-Hebr-IL", - "lag": "lag-Latn-TZ", - "lah": "lah-Arab-PK", - "laj": "laj-Latn-UG", - "las": "las-Latn-ZZ", - "lb": "lb-Latn-LU", - "lbe": "lbe-Cyrl-RU", - "lbu": "lbu-Latn-ZZ", - "lbw": "lbw-Latn-ID", - "lcm": "lcm-Latn-ZZ", - "lcp": "lcp-Thai-CN", - "ldb": "ldb-Latn-ZZ", - "led": "led-Latn-ZZ", - "lee": "lee-Latn-ZZ", - "lem": "lem-Latn-ZZ", - "lep": "lep-Lepc-IN", - "leq": "leq-Latn-ZZ", - "leu": "leu-Latn-ZZ", - "lez": "lez-Cyrl-RU", - "lg": "lg-Latn-UG", - "lgg": "lgg-Latn-ZZ", - "li": "li-Latn-NL", - "lia": "lia-Latn-ZZ", - "lid": "lid-Latn-ZZ", - "lif": "lif-Deva-NP", - "lif-Limb": "lif-Limb-IN", - "lig": "lig-Latn-ZZ", - "lih": "lih-Latn-ZZ", - "lij": "lij-Latn-IT", - "lis": "lis-Lisu-CN", - "ljp": "ljp-Latn-ID", - "lki": "lki-Arab-IR", - "lkt": "lkt-Latn-US", - "lle": "lle-Latn-ZZ", - "lln": "lln-Latn-ZZ", - "lmn": "lmn-Telu-IN", - "lmo": "lmo-Latn-IT", - "lmp": "lmp-Latn-ZZ", - "ln": "ln-Latn-CD", - "lns": "lns-Latn-ZZ", - "lnu": "lnu-Latn-ZZ", - "lo": "lo-Laoo-LA", - "loj": "loj-Latn-ZZ", - "lok": "lok-Latn-ZZ", - "lol": "lol-Latn-CD", - "lor": "lor-Latn-ZZ", - "los": "los-Latn-ZZ", - "loz": "loz-Latn-ZM", - "lrc": "lrc-Arab-IR", - "lt": "lt-Latn-LT", - "ltg": "ltg-Latn-LV", - "lu": "lu-Latn-CD", - "lua": "lua-Latn-CD", - "luo": "luo-Latn-KE", - "luy": "luy-Latn-KE", - "luz": "luz-Arab-IR", - "lv": "lv-Latn-LV", - "lwl": "lwl-Thai-TH", - "lzh": "lzh-Hans-CN", - "lzz": "lzz-Latn-TR", - "mad": "mad-Latn-ID", - "maf": "maf-Latn-CM", - "mag": "mag-Deva-IN", - "mai": "mai-Deva-IN", - "mak": "mak-Latn-ID", - "man": "man-Latn-GM", - "man-GN": "man-Nkoo-GN", - "man-Nkoo": "man-Nkoo-GN", - "mas": "mas-Latn-KE", - "maw": "maw-Latn-ZZ", - "maz": "maz-Latn-MX", - "mbh": "mbh-Latn-ZZ", - "mbo": "mbo-Latn-ZZ", - "mbq": "mbq-Latn-ZZ", - "mbu": "mbu-Latn-ZZ", - "mbw": "mbw-Latn-ZZ", - "mci": "mci-Latn-ZZ", - "mcp": "mcp-Latn-ZZ", - "mcq": "mcq-Latn-ZZ", - "mcr": "mcr-Latn-ZZ", - "mcu": "mcu-Latn-ZZ", - "mda": "mda-Latn-ZZ", - "mde": "mde-Arab-ZZ", - "mdf": "mdf-Cyrl-RU", - "mdh": "mdh-Latn-PH", - "mdj": "mdj-Latn-ZZ", - "mdr": "mdr-Latn-ID", - "mdx": "mdx-Ethi-ZZ", - "med": "med-Latn-ZZ", - "mee": "mee-Latn-ZZ", - "mek": "mek-Latn-ZZ", - "men": "men-Latn-SL", - "mer": "mer-Latn-KE", - "met": "met-Latn-ZZ", - "meu": "meu-Latn-ZZ", - "mfa": "mfa-Arab-TH", - "mfe": "mfe-Latn-MU", - "mfn": "mfn-Latn-ZZ", - "mfo": "mfo-Latn-ZZ", - "mfq": "mfq-Latn-ZZ", - "mg": "mg-Latn-MG", - "mgh": "mgh-Latn-MZ", - "mgl": "mgl-Latn-ZZ", - "mgo": "mgo-Latn-CM", - "mgp": "mgp-Deva-NP", - "mgy": "mgy-Latn-TZ", - "mh": "mh-Latn-MH", - "mhi": "mhi-Latn-ZZ", - "mhl": "mhl-Latn-ZZ", - "mi": "mi-Latn-NZ", - "mif": "mif-Latn-ZZ", - "min": "min-Latn-ID", - "mis": "mis-Hatr-IQ", - "miw": "miw-Latn-ZZ", - "mk": "mk-Cyrl-MK", - "mki": "mki-Arab-ZZ", - "mkl": "mkl-Latn-ZZ", - "mkp": "mkp-Latn-ZZ", - "mkw": "mkw-Latn-ZZ", - "ml": "ml-Mlym-IN", - "mle": "mle-Latn-ZZ", - "mlp": "mlp-Latn-ZZ", - "mls": "mls-Latn-SD", - "mmo": "mmo-Latn-ZZ", - "mmu": "mmu-Latn-ZZ", - "mmx": "mmx-Latn-ZZ", - "mn": "mn-Cyrl-MN", - "mn-CN": "mn-Mong-CN", - "mn-Mong": "mn-Mong-CN", - "mna": "mna-Latn-ZZ", - "mnf": "mnf-Latn-ZZ", - "mni": "mni-Beng-IN", - "mnw": "mnw-Mymr-MM", - "moa": "moa-Latn-ZZ", - "moe": "moe-Latn-CA", - "moh": "moh-Latn-CA", - "mos": "mos-Latn-BF", - "mox": "mox-Latn-ZZ", - "mpp": "mpp-Latn-ZZ", - "mps": "mps-Latn-ZZ", - "mpt": "mpt-Latn-ZZ", - "mpx": "mpx-Latn-ZZ", - "mql": "mql-Latn-ZZ", - "mr": "mr-Deva-IN", - "mrd": "mrd-Deva-NP", - "mrj": "mrj-Cyrl-RU", - "mro": "mro-Mroo-BD", - "ms": "ms-Latn-MY", - "ms-CC": "ms-Arab-CC", - "ms-ID": "ms-Arab-ID", - "mt": "mt-Latn-MT", - "mtc": "mtc-Latn-ZZ", - "mtf": "mtf-Latn-ZZ", - "mti": "mti-Latn-ZZ", - "mtr": "mtr-Deva-IN", - "mua": "mua-Latn-CM", - "mur": "mur-Latn-ZZ", - "mus": "mus-Latn-US", - "mva": "mva-Latn-ZZ", - "mvn": "mvn-Latn-ZZ", - "mvy": "mvy-Arab-PK", - "mwk": "mwk-Latn-ML", - "mwr": "mwr-Deva-IN", - "mwv": "mwv-Latn-ID", - "mxc": "mxc-Latn-ZW", - "mxm": "mxm-Latn-ZZ", - "my": "my-Mymr-MM", - "myk": "myk-Latn-ZZ", - "mym": "mym-Ethi-ZZ", - "myv": "myv-Cyrl-RU", - "myw": "myw-Latn-ZZ", - "myx": "myx-Latn-UG", - "myz": "myz-Mand-IR", - "mzk": "mzk-Latn-ZZ", - "mzm": "mzm-Latn-ZZ", - "mzn": "mzn-Arab-IR", - "mzp": "mzp-Latn-ZZ", - "mzw": "mzw-Latn-ZZ", - "mzz": "mzz-Latn-ZZ", - "na": "na-Latn-NR", - "nac": "nac-Latn-ZZ", - "naf": "naf-Latn-ZZ", - "nak": "nak-Latn-ZZ", - "nan": "nan-Hans-CN", - "nap": "nap-Latn-IT", - "naq": "naq-Latn-NA", - "nas": "nas-Latn-ZZ", - "nb": "nb-Latn-NO", - "nca": "nca-Latn-ZZ", - "nce": "nce-Latn-ZZ", - "ncf": "ncf-Latn-ZZ", - "nch": "nch-Latn-MX", - "nco": "nco-Latn-ZZ", - "ncu": "ncu-Latn-ZZ", - "nd": "nd-Latn-ZW", - "ndc": "ndc-Latn-MZ", - "nds": "nds-Latn-DE", - "ne": "ne-Deva-NP", - "neb": "neb-Latn-ZZ", - "new": "new-Deva-NP", - "nex": "nex-Latn-ZZ", - "nfr": "nfr-Latn-ZZ", - "ng": "ng-Latn-NA", - "nga": "nga-Latn-ZZ", - "ngb": "ngb-Latn-ZZ", - "ngl": "ngl-Latn-MZ", - "nhb": "nhb-Latn-ZZ", - "nhe": "nhe-Latn-MX", - "nhw": "nhw-Latn-MX", - "nif": "nif-Latn-ZZ", - "nii": "nii-Latn-ZZ", - "nij": "nij-Latn-ID", - "nin": "nin-Latn-ZZ", - "niu": "niu-Latn-NU", - "niy": "niy-Latn-ZZ", - "niz": "niz-Latn-ZZ", - "njo": "njo-Latn-IN", - "nkg": "nkg-Latn-ZZ", - "nko": "nko-Latn-ZZ", - "nl": "nl-Latn-NL", - "nmg": "nmg-Latn-CM", - "nmz": "nmz-Latn-ZZ", - "nn": "nn-Latn-NO", - "nnf": "nnf-Latn-ZZ", - "nnh": "nnh-Latn-CM", - "nnk": "nnk-Latn-ZZ", - "nnm": "nnm-Latn-ZZ", - "no": "no-Latn-NO", - "nod": "nod-Lana-TH", - "noe": "noe-Deva-IN", - "non": "non-Runr-SE", - "nop": "nop-Latn-ZZ", - "nou": "nou-Latn-ZZ", - "nqo": "nqo-Nkoo-GN", - "nr": "nr-Latn-ZA", - "nrb": "nrb-Latn-ZZ", - "nsk": "nsk-Cans-CA", - "nsn": "nsn-Latn-ZZ", - "nso": "nso-Latn-ZA", - "nss": "nss-Latn-ZZ", - "ntm": "ntm-Latn-ZZ", - "ntr": "ntr-Latn-ZZ", - "nui": "nui-Latn-ZZ", - "nup": "nup-Latn-ZZ", - "nus": "nus-Latn-SS", - "nuv": "nuv-Latn-ZZ", - "nux": "nux-Latn-ZZ", - "nv": "nv-Latn-US", - "nwb": "nwb-Latn-ZZ", - "nxq": "nxq-Latn-CN", - "nxr": "nxr-Latn-ZZ", - "ny": "ny-Latn-MW", - "nym": "nym-Latn-TZ", - "nyn": "nyn-Latn-UG", - "nzi": "nzi-Latn-GH", - "oc": "oc-Latn-FR", - "ogc": "ogc-Latn-ZZ", - "okr": "okr-Latn-ZZ", - "okv": "okv-Latn-ZZ", - "om": "om-Latn-ET", - "ong": "ong-Latn-ZZ", - "onn": "onn-Latn-ZZ", - "ons": "ons-Latn-ZZ", - "opm": "opm-Latn-ZZ", - "or": "or-Orya-IN", - "oro": "oro-Latn-ZZ", - "oru": "oru-Arab-ZZ", - "os": "os-Cyrl-GE", - "osa": "osa-Osge-US", - "ota": "ota-Arab-ZZ", - "otk": "otk-Orkh-MN", - "ozm": "ozm-Latn-ZZ", - "pa": "pa-Guru-IN", - "pa-Arab": "pa-Arab-PK", - "pa-PK": "pa-Arab-PK", - "pag": "pag-Latn-PH", - "pal": "pal-Phli-IR", - "pal-Phlp": "pal-Phlp-CN", - "pam": "pam-Latn-PH", - "pap": "pap-Latn-AW", - "pau": "pau-Latn-PW", - "pbi": "pbi-Latn-ZZ", - "pcd": "pcd-Latn-FR", - "pcm": "pcm-Latn-NG", - "pdc": "pdc-Latn-US", - "pdt": "pdt-Latn-CA", - "ped": "ped-Latn-ZZ", - "peo": "peo-Xpeo-IR", - "pex": "pex-Latn-ZZ", - "pfl": "pfl-Latn-DE", - "phl": "phl-Arab-ZZ", - "phn": "phn-Phnx-LB", - "pil": "pil-Latn-ZZ", - "pip": "pip-Latn-ZZ", - "pka": "pka-Brah-IN", - "pko": "pko-Latn-KE", - "pl": "pl-Latn-PL", - "pla": "pla-Latn-ZZ", - "pms": "pms-Latn-IT", - "png": "png-Latn-ZZ", - "pnn": "pnn-Latn-ZZ", - "pnt": "pnt-Grek-GR", - "pon": "pon-Latn-FM", - "ppo": "ppo-Latn-ZZ", - "pra": "pra-Khar-PK", - "prd": "prd-Arab-IR", - "prg": "prg-Latn-001", - "ps": "ps-Arab-AF", - "pss": "pss-Latn-ZZ", - "pt": "pt-Latn-BR", - "ptp": "ptp-Latn-ZZ", - "puu": "puu-Latn-GA", - "pwa": "pwa-Latn-ZZ", - "qu": "qu-Latn-PE", - "quc": "quc-Latn-GT", - "qug": "qug-Latn-EC", - "rai": "rai-Latn-ZZ", - "raj": "raj-Deva-IN", - "rao": "rao-Latn-ZZ", - "rcf": "rcf-Latn-RE", - "rej": "rej-Latn-ID", - "rel": "rel-Latn-ZZ", - "res": "res-Latn-ZZ", - "rgn": "rgn-Latn-IT", - "rhg": "rhg-Arab-ZZ", - "ria": "ria-Latn-IN", - "rif": "rif-Tfng-MA", - "rif-NL": "rif-Latn-NL", - "rjs": "rjs-Deva-NP", - "rkt": "rkt-Beng-BD", - "rm": "rm-Latn-CH", - "rmf": "rmf-Latn-FI", - "rmo": "rmo-Latn-CH", - "rmt": "rmt-Arab-IR", - "rmu": "rmu-Latn-SE", - "rn": "rn-Latn-BI", - "rna": "rna-Latn-ZZ", - "rng": "rng-Latn-MZ", - "ro": "ro-Latn-RO", - "rob": "rob-Latn-ID", - "rof": "rof-Latn-TZ", - "roo": "roo-Latn-ZZ", - "rro": "rro-Latn-ZZ", - "rtm": "rtm-Latn-FJ", - "ru": "ru-Cyrl-RU", - "rue": "rue-Cyrl-UA", - "rug": "rug-Latn-SB", - "rw": "rw-Latn-RW", - "rwk": "rwk-Latn-TZ", - "rwo": "rwo-Latn-ZZ", - "ryu": "ryu-Kana-JP", - "sa": "sa-Deva-IN", - "saf": "saf-Latn-GH", - "sah": "sah-Cyrl-RU", - "saq": "saq-Latn-KE", - "sas": "sas-Latn-ID", - "sat": "sat-Latn-IN", - "saz": "saz-Saur-IN", - "sba": "sba-Latn-ZZ", - "sbe": "sbe-Latn-ZZ", - "sbp": "sbp-Latn-TZ", - "sc": "sc-Latn-IT", - "sck": "sck-Deva-IN", - "scl": "scl-Arab-ZZ", - "scn": "scn-Latn-IT", - "sco": "sco-Latn-GB", - "scs": "scs-Latn-CA", - "sd": "sd-Arab-PK", - "sd-Deva": "sd-Deva-IN", - "sd-Khoj": "sd-Khoj-IN", - "sd-Sind": "sd-Sind-IN", - "sdc": "sdc-Latn-IT", - "sdh": "sdh-Arab-IR", - "se": "se-Latn-NO", - "sef": "sef-Latn-CI", - "seh": "seh-Latn-MZ", - "sei": "sei-Latn-MX", - "ses": "ses-Latn-ML", - "sg": "sg-Latn-CF", - "sga": "sga-Ogam-IE", - "sgs": "sgs-Latn-LT", - "sgw": "sgw-Ethi-ZZ", - "sgz": "sgz-Latn-ZZ", - "shi": "shi-Tfng-MA", - "shk": "shk-Latn-ZZ", - "shn": "shn-Mymr-MM", - "shu": "shu-Arab-ZZ", - "si": "si-Sinh-LK", - "sid": "sid-Latn-ET", - "sig": "sig-Latn-ZZ", - "sil": "sil-Latn-ZZ", - "sim": "sim-Latn-ZZ", - "sjr": "sjr-Latn-ZZ", - "sk": "sk-Latn-SK", - "skc": "skc-Latn-ZZ", - "skr": "skr-Arab-PK", - "sks": "sks-Latn-ZZ", - "sl": "sl-Latn-SI", - "sld": "sld-Latn-ZZ", - "sli": "sli-Latn-PL", - "sll": "sll-Latn-ZZ", - "sly": "sly-Latn-ID", - "sm": "sm-Latn-WS", - "sma": "sma-Latn-SE", - "smj": "smj-Latn-SE", - "smn": "smn-Latn-FI", - "smp": "smp-Samr-IL", - "smq": "smq-Latn-ZZ", - "sms": "sms-Latn-FI", - "sn": "sn-Latn-ZW", - "snc": "snc-Latn-ZZ", - "snk": "snk-Latn-ML", - "snp": "snp-Latn-ZZ", - "snx": "snx-Latn-ZZ", - "sny": "sny-Latn-ZZ", - "so": "so-Latn-SO", - "sok": "sok-Latn-ZZ", - "soq": "soq-Latn-ZZ", - "sou": "sou-Thai-TH", - "soy": "soy-Latn-ZZ", - "spd": "spd-Latn-ZZ", - "spl": "spl-Latn-ZZ", - "sps": "sps-Latn-ZZ", - "sq": "sq-Latn-AL", - "sr": "sr-Cyrl-RS", - "sr-ME": "sr-Latn-ME", - "sr-RO": "sr-Latn-RO", - "sr-RU": "sr-Latn-RU", - "sr-TR": "sr-Latn-TR", - "srb": "srb-Sora-IN", - "srn": "srn-Latn-SR", - "srr": "srr-Latn-SN", - "srx": "srx-Deva-IN", - "ss": "ss-Latn-ZA", - "ssd": "ssd-Latn-ZZ", - "ssg": "ssg-Latn-ZZ", - "ssy": "ssy-Latn-ER", - "st": "st-Latn-ZA", - "stk": "stk-Latn-ZZ", - "stq": "stq-Latn-DE", - "su": "su-Latn-ID", - "sua": "sua-Latn-ZZ", - "sue": "sue-Latn-ZZ", - "suk": "suk-Latn-TZ", - "sur": "sur-Latn-ZZ", - "sus": "sus-Latn-GN", - "sv": "sv-Latn-SE", - "sw": "sw-Latn-TZ", - "swb": "swb-Arab-YT", - "swc": "swc-Latn-CD", - "swg": "swg-Latn-DE", - "swp": "swp-Latn-ZZ", - "swv": "swv-Deva-IN", - "sxn": "sxn-Latn-ID", - "sxw": "sxw-Latn-ZZ", - "syl": "syl-Beng-BD", - "syr": "syr-Syrc-IQ", - "szl": "szl-Latn-PL", - "ta": "ta-Taml-IN", - "taj": "taj-Deva-NP", - "tal": "tal-Latn-ZZ", - "tan": "tan-Latn-ZZ", - "taq": "taq-Latn-ZZ", - "tbc": "tbc-Latn-ZZ", - "tbd": "tbd-Latn-ZZ", - "tbf": "tbf-Latn-ZZ", - "tbg": "tbg-Latn-ZZ", - "tbo": "tbo-Latn-ZZ", - "tbw": "tbw-Latn-PH", - "tbz": "tbz-Latn-ZZ", - "tci": "tci-Latn-ZZ", - "tcy": "tcy-Knda-IN", - "tdd": "tdd-Tale-CN", - "tdg": "tdg-Deva-NP", - "tdh": "tdh-Deva-NP", - "te": "te-Telu-IN", - "ted": "ted-Latn-ZZ", - "tem": "tem-Latn-SL", - "teo": "teo-Latn-UG", - "tet": "tet-Latn-TL", - "tfi": "tfi-Latn-ZZ", - "tg": "tg-Cyrl-TJ", - "tg-Arab": "tg-Arab-PK", - "tg-PK": "tg-Arab-PK", - "tgc": "tgc-Latn-ZZ", - "tgo": "tgo-Latn-ZZ", - "tgu": "tgu-Latn-ZZ", - "th": "th-Thai-TH", - "thl": "thl-Deva-NP", - "thq": "thq-Deva-NP", - "thr": "thr-Deva-NP", - "ti": "ti-Ethi-ET", - "tif": "tif-Latn-ZZ", - "tig": "tig-Ethi-ER", - "tik": "tik-Latn-ZZ", - "tim": "tim-Latn-ZZ", - "tio": "tio-Latn-ZZ", - "tiv": "tiv-Latn-NG", - "tk": "tk-Latn-TM", - "tkl": "tkl-Latn-TK", - "tkr": "tkr-Latn-AZ", - "tkt": "tkt-Deva-NP", - "tl": "tl-Latn-PH", - "tlf": "tlf-Latn-ZZ", - "tlx": "tlx-Latn-ZZ", - "tly": "tly-Latn-AZ", - "tmh": "tmh-Latn-NE", - "tmy": "tmy-Latn-ZZ", - "tn": "tn-Latn-ZA", - "tnh": "tnh-Latn-ZZ", - "to": "to-Latn-TO", - "tof": "tof-Latn-ZZ", - "tog": "tog-Latn-MW", - "toq": "toq-Latn-ZZ", - "tpi": "tpi-Latn-PG", - "tpm": "tpm-Latn-ZZ", - "tpz": "tpz-Latn-ZZ", - "tqo": "tqo-Latn-ZZ", - "tr": "tr-Latn-TR", - "tru": "tru-Latn-TR", - "trv": "trv-Latn-TW", - "trw": "trw-Arab-ZZ", - "ts": "ts-Latn-ZA", - "tsd": "tsd-Grek-GR", - "tsf": "tsf-Deva-NP", - "tsg": "tsg-Latn-PH", - "tsj": "tsj-Tibt-BT", - "tsw": "tsw-Latn-ZZ", - "tt": "tt-Cyrl-RU", - "ttd": "ttd-Latn-ZZ", - "tte": "tte-Latn-ZZ", - "ttj": "ttj-Latn-UG", - "ttr": "ttr-Latn-ZZ", - "tts": "tts-Thai-TH", - "ttt": "ttt-Latn-AZ", - "tuh": "tuh-Latn-ZZ", - "tul": "tul-Latn-ZZ", - "tum": "tum-Latn-MW", - "tuq": "tuq-Latn-ZZ", - "tvd": "tvd-Latn-ZZ", - "tvl": "tvl-Latn-TV", - "tvu": "tvu-Latn-ZZ", - "twh": "twh-Latn-ZZ", - "twq": "twq-Latn-NE", - "txg": "txg-Tang-CN", - "ty": "ty-Latn-PF", - "tya": "tya-Latn-ZZ", - "tyv": "tyv-Cyrl-RU", - "tzm": "tzm-Latn-MA", - "ubu": "ubu-Latn-ZZ", - "udm": "udm-Cyrl-RU", - "ug": "ug-Arab-CN", - "ug-Cyrl": "ug-Cyrl-KZ", - "ug-KZ": "ug-Cyrl-KZ", - "ug-MN": "ug-Cyrl-MN", - "uga": "uga-Ugar-SY", - "uk": "uk-Cyrl-UA", - "uli": "uli-Latn-FM", - "umb": "umb-Latn-AO", - "und": "en-Latn-US", - "und-002": "en-Latn-NG", - "und-003": "en-Latn-US", - "und-005": "pt-Latn-BR", - "und-009": "en-Latn-AU", - "und-011": "en-Latn-NG", - "und-013": "es-Latn-MX", - "und-014": "sw-Latn-TZ", - "und-015": "ar-Arab-EG", - "und-017": "sw-Latn-CD", - "und-018": "en-Latn-ZA", - "und-019": "en-Latn-US", - "und-021": "en-Latn-US", - "und-029": "es-Latn-CU", - "und-030": "zh-Hans-CN", - "und-034": "hi-Deva-IN", - "und-035": "id-Latn-ID", - "und-039": "it-Latn-IT", - "und-053": "en-Latn-AU", - "und-054": "en-Latn-PG", - "und-057": "en-Latn-GU", - "und-061": "sm-Latn-WS", - "und-142": "zh-Hans-CN", - "und-143": "uz-Latn-UZ", - "und-145": "ar-Arab-SA", - "und-150": "ru-Cyrl-RU", - "und-151": "ru-Cyrl-RU", - "und-154": "en-Latn-GB", - "und-155": "de-Latn-DE", - "und-419": "es-Latn-419", - "und-AD": "ca-Latn-AD", - "und-Adlm": "ff-Adlm-GN", - "und-AE": "ar-Arab-AE", - "und-AF": "fa-Arab-AF", - "und-Aghb": "lez-Aghb-RU", - "und-Ahom": "aho-Ahom-IN", - "und-AL": "sq-Latn-AL", - "und-AM": "hy-Armn-AM", - "und-AO": "pt-Latn-AO", - "und-AQ": "und-Latn-AQ", - "und-AR": "es-Latn-AR", - "und-Arab": "ar-Arab-EG", - "und-Arab-CC": "ms-Arab-CC", - "und-Arab-CN": "ug-Arab-CN", - "und-Arab-GB": "ks-Arab-GB", - "und-Arab-ID": "ms-Arab-ID", - "und-Arab-IN": "ur-Arab-IN", - "und-Arab-KH": "cja-Arab-KH", - "und-Arab-MN": "kk-Arab-MN", - "und-Arab-MU": "ur-Arab-MU", - "und-Arab-NG": "ha-Arab-NG", - "und-Arab-PK": "ur-Arab-PK", - "und-Arab-TG": "apd-Arab-TG", - "und-Arab-TH": "mfa-Arab-TH", - "und-Arab-TJ": "fa-Arab-TJ", - "und-Arab-TR": "az-Arab-TR", - "und-Arab-YT": "swb-Arab-YT", - "und-Armi": "arc-Armi-IR", - "und-Armn": "hy-Armn-AM", - "und-AS": "sm-Latn-AS", - "und-AT": "de-Latn-AT", - "und-Avst": "ae-Avst-IR", - "und-AW": "nl-Latn-AW", - "und-AX": "sv-Latn-AX", - "und-AZ": "az-Latn-AZ", - "und-BA": "bs-Latn-BA", - "und-Bali": "ban-Bali-ID", - "und-Bamu": "bax-Bamu-CM", - "und-Bass": "bsq-Bass-LR", - "und-Batk": "bbc-Batk-ID", - "und-BD": "bn-Beng-BD", - "und-BE": "nl-Latn-BE", - "und-Beng": "bn-Beng-BD", - "und-BF": "fr-Latn-BF", - "und-BG": "bg-Cyrl-BG", - "und-BH": "ar-Arab-BH", - "und-Bhks": "sa-Bhks-IN", - "und-BI": "rn-Latn-BI", - "und-BJ": "fr-Latn-BJ", - "und-BL": "fr-Latn-BL", - "und-BN": "ms-Latn-BN", - "und-BO": "es-Latn-BO", - "und-Bopo": "zh-Bopo-TW", - "und-BQ": "pap-Latn-BQ", - "und-BR": "pt-Latn-BR", - "und-Brah": "pka-Brah-IN", - "und-Brai": "fr-Brai-FR", - "und-BT": "dz-Tibt-BT", - "und-Bugi": "bug-Bugi-ID", - "und-Buhd": "bku-Buhd-PH", - "und-BV": "und-Latn-BV", - "und-BY": "be-Cyrl-BY", - "und-Cakm": "ccp-Cakm-BD", - "und-Cans": "cr-Cans-CA", - "und-Cari": "xcr-Cari-TR", - "und-CD": "sw-Latn-CD", - "und-CF": "fr-Latn-CF", - "und-CG": "fr-Latn-CG", - "und-CH": "de-Latn-CH", - "und-Cham": "cjm-Cham-VN", - "und-Cher": "chr-Cher-US", - "und-CI": "fr-Latn-CI", - "und-CL": "es-Latn-CL", - "und-CM": "fr-Latn-CM", - "und-CN": "zh-Hans-CN", - "und-CO": "es-Latn-CO", - "und-Copt": "cop-Copt-EG", - "und-CP": "und-Latn-CP", - "und-Cprt": "grc-Cprt-CY", - "und-CR": "es-Latn-CR", - "und-CU": "es-Latn-CU", - "und-CV": "pt-Latn-CV", - "und-CW": "pap-Latn-CW", - "und-CY": "el-Grek-CY", - "und-Cyrl": "ru-Cyrl-RU", - "und-Cyrl-AL": "mk-Cyrl-AL", - "und-Cyrl-BA": "sr-Cyrl-BA", - "und-Cyrl-GE": "ab-Cyrl-GE", - "und-Cyrl-GR": "mk-Cyrl-GR", - "und-Cyrl-MD": "uk-Cyrl-MD", - "und-Cyrl-PL": "be-Cyrl-PL", - "und-Cyrl-RO": "bg-Cyrl-RO", - "und-Cyrl-SK": "uk-Cyrl-SK", - "und-Cyrl-TR": "kbd-Cyrl-TR", - "und-Cyrl-XK": "sr-Cyrl-XK", - "und-CZ": "cs-Latn-CZ", - "und-DE": "de-Latn-DE", - "und-Deva": "hi-Deva-IN", - "und-Deva-BT": "ne-Deva-BT", - "und-Deva-FJ": "hif-Deva-FJ", - "und-Deva-MU": "bho-Deva-MU", - "und-Deva-PK": "btv-Deva-PK", - "und-DJ": "aa-Latn-DJ", - "und-DK": "da-Latn-DK", - "und-DO": "es-Latn-DO", - "und-Dupl": "fr-Dupl-FR", - "und-DZ": "ar-Arab-DZ", - "und-EA": "es-Latn-EA", - "und-EC": "es-Latn-EC", - "und-EE": "et-Latn-EE", - "und-EG": "ar-Arab-EG", - "und-Egyp": "egy-Egyp-EG", - "und-EH": "ar-Arab-EH", - "und-Elba": "sq-Elba-AL", - "und-ER": "ti-Ethi-ER", - "und-ES": "es-Latn-ES", - "und-ET": "am-Ethi-ET", - "und-Ethi": "am-Ethi-ET", - "und-EU": "en-Latn-GB", - "und-EZ": "de-Latn-EZ", - "und-FI": "fi-Latn-FI", - "und-FO": "fo-Latn-FO", - "und-FR": "fr-Latn-FR", - "und-GA": "fr-Latn-GA", - "und-GE": "ka-Geor-GE", - "und-Geor": "ka-Geor-GE", - "und-GF": "fr-Latn-GF", - "und-GH": "ak-Latn-GH", - "und-GL": "kl-Latn-GL", - "und-Glag": "cu-Glag-BG", - "und-GN": "fr-Latn-GN", - "und-Goth": "got-Goth-UA", - "und-GP": "fr-Latn-GP", - "und-GQ": "es-Latn-GQ", - "und-GR": "el-Grek-GR", - "und-Gran": "sa-Gran-IN", - "und-Grek": "el-Grek-GR", - "und-Grek-TR": "bgx-Grek-TR", - "und-GS": "und-Latn-GS", - "und-GT": "es-Latn-GT", - "und-Gujr": "gu-Gujr-IN", - "und-Guru": "pa-Guru-IN", - "und-GW": "pt-Latn-GW", - "und-Hanb": "zh-Hanb-TW", - "und-Hang": "ko-Hang-KR", - "und-Hani": "zh-Hani-CN", - "und-Hano": "hnn-Hano-PH", - "und-Hans": "zh-Hans-CN", - "und-Hant": "zh-Hant-TW", - "und-Hant-CN": "yue-Hant-CN", - "und-Hatr": "mis-Hatr-IQ", - "und-Hebr": "he-Hebr-IL", - "und-Hebr-CA": "yi-Hebr-CA", - "und-Hebr-GB": "yi-Hebr-GB", - "und-Hebr-SE": "yi-Hebr-SE", - "und-Hebr-UA": "yi-Hebr-UA", - "und-Hebr-US": "yi-Hebr-US", - "und-Hira": "ja-Hira-JP", - "und-HK": "zh-Hant-HK", - "und-Hluw": "hlu-Hluw-TR", - "und-HM": "und-Latn-HM", - "und-Hmng": "hnj-Hmng-LA", - "und-HN": "es-Latn-HN", - "und-HR": "hr-Latn-HR", - "und-HT": "ht-Latn-HT", - "und-HU": "hu-Latn-HU", - "und-Hung": "hu-Hung-HU", - "und-IC": "es-Latn-IC", - "und-ID": "id-Latn-ID", - "und-IL": "he-Hebr-IL", - "und-IN": "hi-Deva-IN", - "und-IQ": "ar-Arab-IQ", - "und-IR": "fa-Arab-IR", - "und-IS": "is-Latn-IS", - "und-IT": "it-Latn-IT", - "und-Ital": "ett-Ital-IT", - "und-Jamo": "ko-Jamo-KR", - "und-Java": "jv-Java-ID", - "und-JO": "ar-Arab-JO", - "und-JP": "ja-Jpan-JP", - "und-Jpan": "ja-Jpan-JP", - "und-Kali": "eky-Kali-MM", - "und-Kana": "ja-Kana-JP", - "und-KE": "sw-Latn-KE", - "und-KG": "ky-Cyrl-KG", - "und-KH": "km-Khmr-KH", - "und-Khar": "pra-Khar-PK", - "und-Khmr": "km-Khmr-KH", - "und-Khoj": "sd-Khoj-IN", - "und-KM": "ar-Arab-KM", - "und-Knda": "kn-Knda-IN", - "und-Kore": "ko-Kore-KR", - "und-KP": "ko-Kore-KP", - "und-KR": "ko-Kore-KR", - "und-Kthi": "bho-Kthi-IN", - "und-KW": "ar-Arab-KW", - "und-KZ": "ru-Cyrl-KZ", - "und-LA": "lo-Laoo-LA", - "und-Lana": "nod-Lana-TH", - "und-Laoo": "lo-Laoo-LA", - "und-Latn-AF": "tk-Latn-AF", - "und-Latn-AM": "ku-Latn-AM", - "und-Latn-CN": "za-Latn-CN", - "und-Latn-CY": "tr-Latn-CY", - "und-Latn-DZ": "fr-Latn-DZ", - "und-Latn-ET": "en-Latn-ET", - "und-Latn-GE": "ku-Latn-GE", - "und-Latn-IR": "tk-Latn-IR", - "und-Latn-KM": "fr-Latn-KM", - "und-Latn-MA": "fr-Latn-MA", - "und-Latn-MK": "sq-Latn-MK", - "und-Latn-MM": "kac-Latn-MM", - "und-Latn-MO": "pt-Latn-MO", - "und-Latn-MR": "fr-Latn-MR", - "und-Latn-RU": "krl-Latn-RU", - "und-Latn-SY": "fr-Latn-SY", - "und-Latn-TN": "fr-Latn-TN", - "und-Latn-TW": "trv-Latn-TW", - "und-Latn-UA": "pl-Latn-UA", - "und-LB": "ar-Arab-LB", - "und-Lepc": "lep-Lepc-IN", - "und-LI": "de-Latn-LI", - "und-Limb": "lif-Limb-IN", - "und-Lina": "lab-Lina-GR", - "und-Linb": "grc-Linb-GR", - "und-Lisu": "lis-Lisu-CN", - "und-LK": "si-Sinh-LK", - "und-LS": "st-Latn-LS", - "und-LT": "lt-Latn-LT", - "und-LU": "fr-Latn-LU", - "und-LV": "lv-Latn-LV", - "und-LY": "ar-Arab-LY", - "und-Lyci": "xlc-Lyci-TR", - "und-Lydi": "xld-Lydi-TR", - "und-MA": "ar-Arab-MA", - "und-Mahj": "hi-Mahj-IN", - "und-Mand": "myz-Mand-IR", - "und-Mani": "xmn-Mani-CN", - "und-Marc": "bo-Marc-CN", - "und-MC": "fr-Latn-MC", - "und-MD": "ro-Latn-MD", - "und-ME": "sr-Latn-ME", - "und-Mend": "men-Mend-SL", - "und-Merc": "xmr-Merc-SD", - "und-Mero": "xmr-Mero-SD", - "und-MF": "fr-Latn-MF", - "und-MG": "mg-Latn-MG", - "und-MK": "mk-Cyrl-MK", - "und-ML": "bm-Latn-ML", - "und-Mlym": "ml-Mlym-IN", - "und-MM": "my-Mymr-MM", - "und-MN": "mn-Cyrl-MN", - "und-MO": "zh-Hant-MO", - "und-Modi": "mr-Modi-IN", - "und-Mong": "mn-Mong-CN", - "und-MQ": "fr-Latn-MQ", - "und-MR": "ar-Arab-MR", - "und-Mroo": "mro-Mroo-BD", - "und-MT": "mt-Latn-MT", - "und-Mtei": "mni-Mtei-IN", - "und-MU": "mfe-Latn-MU", - "und-Mult": "skr-Mult-PK", - "und-MV": "dv-Thaa-MV", - "und-MX": "es-Latn-MX", - "und-MY": "ms-Latn-MY", - "und-Mymr": "my-Mymr-MM", - "und-Mymr-IN": "kht-Mymr-IN", - "und-Mymr-TH": "mnw-Mymr-TH", - "und-MZ": "pt-Latn-MZ", - "und-NA": "af-Latn-NA", - "und-Narb": "xna-Narb-SA", - "und-Nbat": "arc-Nbat-JO", - "und-NC": "fr-Latn-NC", - "und-NE": "ha-Latn-NE", - "und-Newa": "new-Newa-NP", - "und-NI": "es-Latn-NI", - "und-Nkoo": "man-Nkoo-GN", - "und-NL": "nl-Latn-NL", - "und-NO": "nb-Latn-NO", - "und-NP": "ne-Deva-NP", - "und-Ogam": "sga-Ogam-IE", - "und-Olck": "sat-Olck-IN", - "und-OM": "ar-Arab-OM", - "und-Orkh": "otk-Orkh-MN", - "und-Orya": "or-Orya-IN", - "und-Osge": "osa-Osge-US", - "und-Osma": "so-Osma-SO", - "und-PA": "es-Latn-PA", - "und-Palm": "arc-Palm-SY", - "und-Pauc": "ctd-Pauc-MM", - "und-PE": "es-Latn-PE", - "und-Perm": "kv-Perm-RU", - "und-PF": "fr-Latn-PF", - "und-PG": "tpi-Latn-PG", - "und-PH": "fil-Latn-PH", - "und-Phag": "lzh-Phag-CN", - "und-Phli": "pal-Phli-IR", - "und-Phlp": "pal-Phlp-CN", - "und-Phnx": "phn-Phnx-LB", - "und-PK": "ur-Arab-PK", - "und-PL": "pl-Latn-PL", - "und-Plrd": "hmd-Plrd-CN", - "und-PM": "fr-Latn-PM", - "und-PR": "es-Latn-PR", - "und-Prti": "xpr-Prti-IR", - "und-PS": "ar-Arab-PS", - "und-PT": "pt-Latn-PT", - "und-PW": "pau-Latn-PW", - "und-PY": "gn-Latn-PY", - "und-QA": "ar-Arab-QA", - "und-QO": "en-Latn-IO", - "und-RE": "fr-Latn-RE", - "und-Rjng": "rej-Rjng-ID", - "und-RO": "ro-Latn-RO", - "und-RS": "sr-Cyrl-RS", - "und-RU": "ru-Cyrl-RU", - "und-Runr": "non-Runr-SE", - "und-RW": "rw-Latn-RW", - "und-SA": "ar-Arab-SA", - "und-Samr": "smp-Samr-IL", - "und-Sarb": "xsa-Sarb-YE", - "und-Saur": "saz-Saur-IN", - "und-SC": "fr-Latn-SC", - "und-SD": "ar-Arab-SD", - "und-SE": "sv-Latn-SE", - "und-Sgnw": "ase-Sgnw-US", - "und-Shaw": "en-Shaw-GB", - "und-Shrd": "sa-Shrd-IN", - "und-SI": "sl-Latn-SI", - "und-Sidd": "sa-Sidd-IN", - "und-Sind": "sd-Sind-IN", - "und-Sinh": "si-Sinh-LK", - "und-SJ": "nb-Latn-SJ", - "und-SK": "sk-Latn-SK", - "und-SM": "it-Latn-SM", - "und-SN": "fr-Latn-SN", - "und-SO": "so-Latn-SO", - "und-Sora": "srb-Sora-IN", - "und-SR": "nl-Latn-SR", - "und-ST": "pt-Latn-ST", - "und-Sund": "su-Sund-ID", - "und-SV": "es-Latn-SV", - "und-SY": "ar-Arab-SY", - "und-Sylo": "syl-Sylo-BD", - "und-Syrc": "syr-Syrc-IQ", - "und-Tagb": "tbw-Tagb-PH", - "und-Takr": "doi-Takr-IN", - "und-Tale": "tdd-Tale-CN", - "und-Talu": "khb-Talu-CN", - "und-Taml": "ta-Taml-IN", - "und-Tang": "txg-Tang-CN", - "und-Tavt": "blt-Tavt-VN", - "und-TD": "fr-Latn-TD", - "und-Telu": "te-Telu-IN", - "und-TF": "fr-Latn-TF", - "und-Tfng": "zgh-Tfng-MA", - "und-TG": "fr-Latn-TG", - "und-Tglg": "fil-Tglg-PH", - "und-TH": "th-Thai-TH", - "und-Thaa": "dv-Thaa-MV", - "und-Thai": "th-Thai-TH", - "und-Thai-CN": "lcp-Thai-CN", - "und-Thai-KH": "kdt-Thai-KH", - "und-Thai-LA": "kdt-Thai-LA", - "und-Tibt": "bo-Tibt-CN", - "und-Tirh": "mai-Tirh-IN", - "und-TJ": "tg-Cyrl-TJ", - "und-TK": "tkl-Latn-TK", - "und-TL": "pt-Latn-TL", - "und-TM": "tk-Latn-TM", - "und-TN": "ar-Arab-TN", - "und-TO": "to-Latn-TO", - "und-TR": "tr-Latn-TR", - "und-TV": "tvl-Latn-TV", - "und-TW": "zh-Hant-TW", - "und-TZ": "sw-Latn-TZ", - "und-UA": "uk-Cyrl-UA", - "und-UG": "sw-Latn-UG", - "und-Ugar": "uga-Ugar-SY", - "und-UY": "es-Latn-UY", - "und-UZ": "uz-Latn-UZ", - "und-VA": "it-Latn-VA", - "und-Vaii": "vai-Vaii-LR", - "und-VE": "es-Latn-VE", - "und-VN": "vi-Latn-VN", - "und-VU": "bi-Latn-VU", - "und-Wara": "hoc-Wara-IN", - "und-WF": "fr-Latn-WF", - "und-WS": "sm-Latn-WS", - "und-XK": "sq-Latn-XK", - "und-Xpeo": "peo-Xpeo-IR", - "und-Xsux": "akk-Xsux-IQ", - "und-YE": "ar-Arab-YE", - "und-Yiii": "ii-Yiii-CN", - "und-YT": "fr-Latn-YT", - "und-ZW": "sn-Latn-ZW", - "unr": "unr-Beng-IN", - "unr-Deva": "unr-Deva-NP", - "unr-NP": "unr-Deva-NP", - "unx": "unx-Beng-IN", - "ur": "ur-Arab-PK", - "uri": "uri-Latn-ZZ", - "urt": "urt-Latn-ZZ", - "urw": "urw-Latn-ZZ", - "usa": "usa-Latn-ZZ", - "utr": "utr-Latn-ZZ", - "uvh": "uvh-Latn-ZZ", - "uvl": "uvl-Latn-ZZ", - "uz": "uz-Latn-UZ", - "uz-AF": "uz-Arab-AF", - "uz-Arab": "uz-Arab-AF", - "uz-CN": "uz-Cyrl-CN", - "vag": "vag-Latn-ZZ", - "vai": "vai-Vaii-LR", - "van": "van-Latn-ZZ", - "ve": "ve-Latn-ZA", - "vec": "vec-Latn-IT", - "vep": "vep-Latn-RU", - "vi": "vi-Latn-VN", - "vic": "vic-Latn-SX", - "viv": "viv-Latn-ZZ", - "vls": "vls-Latn-BE", - "vmf": "vmf-Latn-DE", - "vmw": "vmw-Latn-MZ", - "vo": "vo-Latn-001", - "vot": "vot-Latn-RU", - "vro": "vro-Latn-EE", - "vun": "vun-Latn-TZ", - "vut": "vut-Latn-ZZ", - "wa": "wa-Latn-BE", - "wae": "wae-Latn-CH", - "waj": "waj-Latn-ZZ", - "wal": "wal-Ethi-ET", - "wan": "wan-Latn-ZZ", - "war": "war-Latn-PH", - "wbp": "wbp-Latn-AU", - "wbq": "wbq-Telu-IN", - "wbr": "wbr-Deva-IN", - "wci": "wci-Latn-ZZ", - "wer": "wer-Latn-ZZ", - "wgi": "wgi-Latn-ZZ", - "whg": "whg-Latn-ZZ", - "wib": "wib-Latn-ZZ", - "wiu": "wiu-Latn-ZZ", - "wiv": "wiv-Latn-ZZ", - "wja": "wja-Latn-ZZ", - "wji": "wji-Latn-ZZ", - "wls": "wls-Latn-WF", - "wmo": "wmo-Latn-ZZ", - "wnc": "wnc-Latn-ZZ", - "wni": "wni-Arab-KM", - "wnu": "wnu-Latn-ZZ", - "wo": "wo-Latn-SN", - "wob": "wob-Latn-ZZ", - "wos": "wos-Latn-ZZ", - "wrs": "wrs-Latn-ZZ", - "wsk": "wsk-Latn-ZZ", - "wtm": "wtm-Deva-IN", - "wuu": "wuu-Hans-CN", - "wuv": "wuv-Latn-ZZ", - "wwa": "wwa-Latn-ZZ", - "xav": "xav-Latn-BR", - "xbi": "xbi-Latn-ZZ", - "xcr": "xcr-Cari-TR", - "xes": "xes-Latn-ZZ", - "xh": "xh-Latn-ZA", - "xla": "xla-Latn-ZZ", - "xlc": "xlc-Lyci-TR", - "xld": "xld-Lydi-TR", - "xmf": "xmf-Geor-GE", - "xmn": "xmn-Mani-CN", - "xmr": "xmr-Merc-SD", - "xna": "xna-Narb-SA", - "xnr": "xnr-Deva-IN", - "xog": "xog-Latn-UG", - "xon": "xon-Latn-ZZ", - "xpr": "xpr-Prti-IR", - "xrb": "xrb-Latn-ZZ", - "xsa": "xsa-Sarb-YE", - "xsi": "xsi-Latn-ZZ", - "xsm": "xsm-Latn-ZZ", - "xsr": "xsr-Deva-NP", - "xwe": "xwe-Latn-ZZ", - "yam": "yam-Latn-ZZ", - "yao": "yao-Latn-MZ", - "yap": "yap-Latn-FM", - "yas": "yas-Latn-ZZ", - "yat": "yat-Latn-ZZ", - "yav": "yav-Latn-CM", - "yay": "yay-Latn-ZZ", - "yaz": "yaz-Latn-ZZ", - "yba": "yba-Latn-ZZ", - "ybb": "ybb-Latn-CM", - "yby": "yby-Latn-ZZ", - "yer": "yer-Latn-ZZ", - "ygr": "ygr-Latn-ZZ", - "ygw": "ygw-Latn-ZZ", - "yi": "yi-Hebr-001", - "yko": "yko-Latn-ZZ", - "yle": "yle-Latn-ZZ", - "ylg": "ylg-Latn-ZZ", - "yll": "yll-Latn-ZZ", - "yml": "yml-Latn-ZZ", - "yo": "yo-Latn-NG", - "yon": "yon-Latn-ZZ", - "yrb": "yrb-Latn-ZZ", - "yre": "yre-Latn-ZZ", - "yrl": "yrl-Latn-BR", - "yss": "yss-Latn-ZZ", - "yua": "yua-Latn-MX", - "yue": "yue-Hant-HK", - "yue-CN": "yue-Hans-CN", - "yue-Hans": "yue-Hans-CN", - "yuj": "yuj-Latn-ZZ", - "yut": "yut-Latn-ZZ", - "yuw": "yuw-Latn-ZZ", - "za": "za-Latn-CN", - "zag": "zag-Latn-SD", - "zdj": "zdj-Arab-KM", - "zea": "zea-Latn-NL", - "zgh": "zgh-Tfng-MA", - "zh": "zh-Hans-CN", - "zh-AU": "zh-Hant-AU", - "zh-BN": "zh-Hant-BN", - "zh-Bopo": "zh-Bopo-TW", - "zh-GB": "zh-Hant-GB", - "zh-GF": "zh-Hant-GF", - "zh-Hanb": "zh-Hanb-TW", - "zh-Hant": "zh-Hant-TW", - "zh-HK": "zh-Hant-HK", - "zh-ID": "zh-Hant-ID", - "zh-MO": "zh-Hant-MO", - "zh-MY": "zh-Hant-MY", - "zh-PA": "zh-Hant-PA", - "zh-PF": "zh-Hant-PF", - "zh-PH": "zh-Hant-PH", - "zh-SR": "zh-Hant-SR", - "zh-TH": "zh-Hant-TH", - "zh-TW": "zh-Hant-TW", - "zh-US": "zh-Hant-US", - "zh-VN": "zh-Hant-VN", - "zia": "zia-Latn-ZZ", - "zlm": "zlm-Latn-TG", - "zmi": "zmi-Latn-MY", - "zne": "zne-Latn-ZZ", - "zu": "zu-Latn-ZA", - "zza": "zza-Latn-TR" - } - } -} diff --git a/contrib/pai_vscode/i18n/cldr/plurals.json b/contrib/pai_vscode/i18n/cldr/plurals.json deleted file mode 100644 index 07f38b40c1..0000000000 --- a/contrib/pai_vscode/i18n/cldr/plurals.json +++ /dev/null @@ -1,857 +0,0 @@ -{ - "supplemental": { - "version": { - "_number": "$Revision: 12805 $", - "_unicodeVersion": "9.0.0", - "_cldrVersion": "30.0.3" - }, - "plurals-type-cardinal": { - "af": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ak": { - "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "am": { - "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ar": { - "pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000", - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-few": "n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …", - "pluralRule-count-many": "n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …", - "pluralRule-count-other": " @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ars": { - "pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000", - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-few": "n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …", - "pluralRule-count-many": "n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …", - "pluralRule-count-other": " @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "as": { - "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "asa": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ast": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "az": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "be": { - "pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …", - "pluralRule-count-few": "n % 10 = 2..4 and n % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 2.0, 3.0, 4.0, 22.0, 23.0, 24.0, 32.0, 33.0, 102.0, 1002.0, …", - "pluralRule-count-many": "n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", - "pluralRule-count-other": " @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …" - }, - "bem": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "bez": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "bg": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "bh": { - "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "bm": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "bn": { - "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "bo": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "br": { - "pluralRule-count-one": "n % 10 = 1 and n % 100 != 11,71,91 @integer 1, 21, 31, 41, 51, 61, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 81.0, 101.0, 1001.0, …", - "pluralRule-count-two": "n % 10 = 2 and n % 100 != 12,72,92 @integer 2, 22, 32, 42, 52, 62, 82, 102, 1002, … @decimal 2.0, 22.0, 32.0, 42.0, 52.0, 62.0, 82.0, 102.0, 1002.0, …", - "pluralRule-count-few": "n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99 @integer 3, 4, 9, 23, 24, 29, 33, 34, 39, 43, 44, 49, 103, 1003, … @decimal 3.0, 4.0, 9.0, 23.0, 24.0, 29.0, 33.0, 34.0, 103.0, 1003.0, …", - "pluralRule-count-many": "n != 0 and n % 1000000 = 0 @integer 1000000, … @decimal 1000000.0, 1000000.00, 1000000.000, …", - "pluralRule-count-other": " @integer 0, 5~8, 10~20, 100, 1000, 10000, 100000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, …" - }, - "brx": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "bs": { - "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", - "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …", - "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ca": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ce": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "cgg": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "chr": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ckb": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "cs": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-few": "i = 2..4 and v = 0 @integer 2~4", - "pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", - "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …" - }, - "cy": { - "pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000", - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-few": "n = 3 @integer 3 @decimal 3.0, 3.00, 3.000, 3.0000", - "pluralRule-count-many": "n = 6 @integer 6 @decimal 6.0, 6.00, 6.000, 6.0000", - "pluralRule-count-other": " @integer 4, 5, 7~20, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "da": { - "pluralRule-count-one": "n = 1 or t != 0 and i = 0,1 @integer 1 @decimal 0.1~1.6", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0~3.4, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "de": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "dsb": { - "pluralRule-count-one": "v = 0 and i % 100 = 1 or f % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", - "pluralRule-count-two": "v = 0 and i % 100 = 2 or f % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … @decimal 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 10.2, 100.2, 1000.2, …", - "pluralRule-count-few": "v = 0 and i % 100 = 3..4 or f % 100 = 3..4 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.3, 0.4, 1.3, 1.4, 2.3, 2.4, 3.3, 3.4, 4.3, 4.4, 5.3, 5.4, 6.3, 6.4, 7.3, 7.4, 10.3, 100.3, 1000.3, …", - "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "dv": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "dz": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ee": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "el": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "en": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "eo": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "es": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "et": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "eu": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "fa": { - "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ff": { - "pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "fi": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "fil": { - "pluralRule-count-one": "v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", - "pluralRule-count-other": " @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …" - }, - "fo": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "fr": { - "pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "fur": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "fy": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ga": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-few": "n = 3..6 @integer 3~6 @decimal 3.0, 4.0, 5.0, 6.0, 3.00, 4.00, 5.00, 6.00, 3.000, 4.000, 5.000, 6.000, 3.0000, 4.0000, 5.0000, 6.0000", - "pluralRule-count-many": "n = 7..10 @integer 7~10 @decimal 7.0, 8.0, 9.0, 10.0, 7.00, 8.00, 9.00, 10.00, 7.000, 8.000, 9.000, 10.000, 7.0000, 8.0000, 9.0000, 10.0000", - "pluralRule-count-other": " @integer 0, 11~25, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "gd": { - "pluralRule-count-one": "n = 1,11 @integer 1, 11 @decimal 1.0, 11.0, 1.00, 11.00, 1.000, 11.000, 1.0000", - "pluralRule-count-two": "n = 2,12 @integer 2, 12 @decimal 2.0, 12.0, 2.00, 12.00, 2.000, 12.000, 2.0000", - "pluralRule-count-few": "n = 3..10,13..19 @integer 3~10, 13~19 @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 3.00", - "pluralRule-count-other": " @integer 0, 20~34, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "gl": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "gsw": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "gu": { - "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "guw": { - "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "gv": { - "pluralRule-count-one": "v = 0 and i % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, …", - "pluralRule-count-two": "v = 0 and i % 10 = 2 @integer 2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, …", - "pluralRule-count-few": "v = 0 and i % 100 = 0,20,40,60,80 @integer 0, 20, 40, 60, 80, 100, 120, 140, 1000, 10000, 100000, 1000000, …", - "pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", - "pluralRule-count-other": " @integer 3~10, 13~19, 23, 103, 1003, …" - }, - "ha": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "haw": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "he": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-two": "i = 2 and v = 0 @integer 2", - "pluralRule-count-many": "v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …", - "pluralRule-count-other": " @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "hi": { - "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "hr": { - "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", - "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …", - "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "hsb": { - "pluralRule-count-one": "v = 0 and i % 100 = 1 or f % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", - "pluralRule-count-two": "v = 0 and i % 100 = 2 or f % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … @decimal 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 10.2, 100.2, 1000.2, …", - "pluralRule-count-few": "v = 0 and i % 100 = 3..4 or f % 100 = 3..4 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.3, 0.4, 1.3, 1.4, 2.3, 2.4, 3.3, 3.4, 4.3, 4.4, 5.3, 5.4, 6.3, 6.4, 7.3, 7.4, 10.3, 100.3, 1000.3, …", - "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "hu": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "hy": { - "pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "id": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ig": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ii": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "in": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "is": { - "pluralRule-count-one": "t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1~1.6, 10.1, 100.1, 1000.1, …", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "it": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "iu": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "iw": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-two": "i = 2 and v = 0 @integer 2", - "pluralRule-count-many": "v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …", - "pluralRule-count-other": " @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ja": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "jbo": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "jgo": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ji": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "jmc": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "jv": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "jw": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ka": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "kab": { - "pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "kaj": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "kcg": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "kde": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "kea": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "kk": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "kkj": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "kl": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "km": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "kn": { - "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ko": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ks": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ksb": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ksh": { - "pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000", - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ku": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "kw": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ky": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "lag": { - "pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000", - "pluralRule-count-one": "i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "lb": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "lg": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "lkt": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ln": { - "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "lo": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "lt": { - "pluralRule-count-one": "n % 10 = 1 and n % 100 != 11..19 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …", - "pluralRule-count-few": "n % 10 = 2..9 and n % 100 != 11..19 @integer 2~9, 22~29, 102, 1002, … @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 22.0, 102.0, 1002.0, …", - "pluralRule-count-many": "f != 0 @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …", - "pluralRule-count-other": " @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "lv": { - "pluralRule-count-zero": "n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", - "pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", - "pluralRule-count-other": " @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, …" - }, - "mas": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "mg": { - "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "mgo": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "mk": { - "pluralRule-count-one": "v = 0 and i % 10 = 1 or f % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", - "pluralRule-count-other": " @integer 0, 2~10, 12~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.2~1.0, 1.2~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ml": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "mn": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "mo": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-few": "v != 0 or n = 0 or n != 1 and n % 100 = 1..19 @integer 0, 2~16, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", - "pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, …" - }, - "mr": { - "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ms": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "mt": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-few": "n = 0 or n % 100 = 2..10 @integer 0, 2~10, 102~107, 1002, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 102.0, 1002.0, …", - "pluralRule-count-many": "n % 100 = 11..19 @integer 11~19, 111~117, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …", - "pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "my": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "nah": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "naq": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "nb": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "nd": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ne": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "nl": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "nn": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "nnh": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "no": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "nqo": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "nr": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "nso": { - "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ny": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "nyn": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "om": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "or": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "os": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "pa": { - "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "pap": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "pl": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …", - "pluralRule-count-many": "v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …", - "pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "prg": { - "pluralRule-count-zero": "n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", - "pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", - "pluralRule-count-other": " @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, …" - }, - "ps": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "pt": { - "pluralRule-count-one": "n = 0..2 and n != 2 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "pt-PT": { - "pluralRule-count-one": "n = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "rm": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ro": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-few": "v != 0 or n = 0 or n != 1 and n % 100 = 1..19 @integer 0, 2~16, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", - "pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, …" - }, - "rof": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "root": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ru": { - "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …", - "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …", - "pluralRule-count-many": "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …", - "pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "rwk": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "sah": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "saq": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "sdh": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "se": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "seh": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ses": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "sg": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "sh": { - "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", - "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …", - "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "shi": { - "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", - "pluralRule-count-few": "n = 2..10 @integer 2~10 @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00", - "pluralRule-count-other": " @integer 11~26, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~1.9, 2.1~2.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "si": { - "pluralRule-count-one": "n = 0,1 or i = 0 and f = 1 @integer 0, 1 @decimal 0.0, 0.1, 1.0, 0.00, 0.01, 1.00, 0.000, 0.001, 1.000, 0.0000, 0.0001, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.2~0.9, 1.1~1.8, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "sk": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-few": "i = 2..4 and v = 0 @integer 2~4", - "pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", - "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …" - }, - "sl": { - "pluralRule-count-one": "v = 0 and i % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, …", - "pluralRule-count-two": "v = 0 and i % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, …", - "pluralRule-count-few": "v = 0 and i % 100 = 3..4 or v != 0 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", - "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …" - }, - "sma": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "smi": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "smj": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "smn": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "sms": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000", - "pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "sn": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "so": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "sq": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "sr": { - "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …", - "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …", - "pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ss": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ssy": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "st": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "sv": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "sw": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "syr": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ta": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "te": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "teo": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "th": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ti": { - "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "tig": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "tk": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "tl": { - "pluralRule-count-one": "v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", - "pluralRule-count-other": " @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …" - }, - "tn": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "to": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "tr": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ts": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "tzm": { - "pluralRule-count-one": "n = 0..1 or n = 11..99 @integer 0, 1, 11~24 @decimal 0.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0", - "pluralRule-count-other": " @integer 2~10, 100~106, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ug": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "uk": { - "pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …", - "pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …", - "pluralRule-count-many": "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …", - "pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ur": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "uz": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "ve": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "vi": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "vo": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "vun": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "wa": { - "pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "wae": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "wo": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "xh": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "xog": { - "pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "yi": { - "pluralRule-count-one": "i = 1 and v = 0 @integer 1", - "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "yo": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "yue": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "zh": { - "pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - }, - "zu": { - "pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04", - "pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …" - } - } - } -} diff --git a/contrib/pai_vscode/i18n/common.json b/contrib/pai_vscode/i18n/common.json deleted file mode 100644 index 3a6d4942b9..0000000000 --- a/contrib/pai_vscode/i18n/common.json +++ /dev/null @@ -1,276 +0,0 @@ -{ - "root": { - "common.yes": "Yes", - "common.no": "No", - "common.upload": "Upload", - "common.finish": "Finish", - "common.cancel": "Cancel", - "common.edit": "Edit", - "common.cluster.storage": "Cluster storage", - "common.personal.storage": "Personal storage", - "common.workspace.nofolder": "No folder in workspace.", - "util.editjson.validationerror": "Invalid Format: {0}", - "util.editjson.parseerror": "Invalid JSON: {0}", - "util.editjson.prompt": "Please edit the configuration and click 'Finish' to save.", - "util.editjson.previousexpired": "Last session of editing configuration has expired.", - "util.validatejson.error": "Error validating JSON", - "util.openexternally.fail": "Failed to open default browser: {0}", - "util.remote.editor.save.prompt": "Please make sure you will change the remote file and click 'Upload' to save.", - "cluster.activate.error": "{0}\nPAI cluster configurations has been cleared due to validation error(s). You may edit the previous configurations.", - "cluster.activate.fix.discard": "Discard Configurations", - "cluster.version.warning": "The version of the configurations is higher than supported. Would you like to edit or reset them?", - "cluster.pick.prompt": "Please choose an existing cluster...", - "cluster.add.host.prompt": "Please enter the host of OpenPAI cluster...", - "cluster.add.host.empty": "Host is empty...", - "cluster.add.host.invalidchar": "Host should not contain '/'...", - "cluster.add.personal.storage.prompt": "Please enter the name of the storage...", - "cluster.add.personal.storage.empty": "Name is empty...", - "cluster.add.personal.storage.invalidchar": "Name should not contain '/'...", - "cluster.add.checkstatus": "Checking status of OpenPAI cluster", - "cluster.add.checkprotocolversion": "Checking the protocol version of OpenPAI cluster", - "cluster.login.openPortal": "Open OpenPAI Portal", - "cluster.login.success": "Login OpenPAI cluster success, please back to VSCode to finish the config.", - "cluster.login.failed": "Login OpenPAI cluster failed.", - "cluster.login.timeout": "Browser did not connect to local server within 10 seconds. Please copy the token from the website.", - "treeview.node.edit": "Edit Configuration...", - "treeview.node.storage": "Open Storage...", - "treeview.node.storage.clipboard": "Double click to copy url to clipboard", - "treeview.node.storage.openfile": "Double click to open", - "treeview.node.storage.server-type": "Server Type", - "treeview.node.storage.mount-point": "Mount Point", - "treeview.node.storage.load-error": "Load error", - "treeview.node.storage.load-more": "Double click to load more items", - "treeview.node.openPortal": "Open Web Portal...", - "treeview.node.listjob": "List Jobs Externally...", - "treeview.node.create-config": "Create Job Config...", - "treeview.node.submitjob": "Submit Job...", - "treeview.node.simulate": "Simulate Job Running...", - "treeview.hdfs.select-cluster.label": "Double click to connect to a PAI cluster's HDFS...", - "treeview.node.openhdfs": "Open HDFS", - "treeview.storage.cluster-root.label": "Cluster share storage", - "treeview.storage.personal-root.label": "Personal storage", - "treeview.storage.mount.point": "Mount Point", - "treeview.storage.nfs.setup.mount.point": "Double click to setup NFS mount point", - "treeview.storage.nfs.mount": "Double click to mount NFS", - "treeview.storage.error": "Failed to load storage: {0}", - "treeview.storage.nfs.sudo.prompt": "Please enter the password and click 'Finish' to mount.", - "treeview.joblist.recent": "Recent Submitted Jobs from VS Code", - "treeview.joblist.all": "All Jobs", - "treeview.joblist.view": "View Job Detail", - "treeview.joblist.more": "View More...", - "treeview.joblist.error": "Failed to load job list: {0}", - "container.hdfs.mkdir.prompt": "Please enter a folder name", - "container.hdfs.mkdir.cancelled": "Cancelled creating new folder", - "container.azure.blob.mkdir.prompt": "Please enter a folder name", - "container.azure.blob.mkdir.cancelled": "Cancelled creating new folder", - "container.nfs.mount.failed": "Mount NFS failed", - "container.nfs.mount.finish": "Mount NFS finish", - "container.nfs.mount.unsupport.os": "Mount NFS failed: Unsupport OS", - "container.nfs.mount.invalid.device.name": "Mound path should be device name (e.g. Z:)", - "container.nfs.mount.path.empty": "Mount path is empty...", - "hdfs.workspace.title": "HDFS Explorer - {0}", - "hdfs.progress": "Transferring file - {0}% ({1} bytes / {2} bytes)", - "hdfs.downloading": "Downloading {0}", - "hdfs.copying": "Copying {0} to {1}", - "hdfs.uploading": "Uploading {0}", - "hdfs.open.prompt": "{0} has been opened in the workspace.", - "hdfs.upload.status": "Uploading to HDFS - {0} / {1}", - "hdfs.upload.error": "Failed uploading to HDFS: {0}", - "hdfs.upload.success": "Files was successfully uploaded", - "storage.create.folder.error": "Failed creating folder: {0}", - "storage.create.folder.success": "Folder was successfully created", - "storage.upload.status": "Uploading to storage - {0} / {1}", - "storage.upload.error": "Failed uploading to storage: {0}", - "storage.upload.success": "Files was successfully uploaded", - "storage.download.status": "Uploading from storage - {0} / {1}", - "storage.download.error": "Failed downloading from storage: {0}", - "storage.download.success": "Files was successfully downloaded", - "storage.open.error": "File open error: {0}", - "storage.delete.error": "Failed deleting files: {0}", - "storage.delete.success": "Files was successfully deleted", - "storage.dialog.label.upload-files": "Upload Files...", - "storage.dialog.label.upload-folders": "Upload Folders...", - "storage.dialog.label.download": "Download Here", - "storage.nfs.mount.point.prompt": "Please enter the path...", - "storage.upload.pick.prompt": "Please choose a storage...", - "hdfs.read.cancelled": "Cancelled downloading file from HDFS.", - "hdfs.write.cancelled": "Cancelled uploading file to HDFS.", - "webpage.dashboard.webportal": "OpenPAI Web Portal", - "webpage.dashboard.pick.prompt": "Please choose the site to open", - "webpage.dashboard.pick.error": "No site selected. Canceled.", - "job.prepare.status": "PAI: Preparing for job submission", - "job.prepare.cluster.cancelled": "No cluster selected, job submission cancelled.", - "job.prepare.config.prompt": "Please select a PAI job config json file", - "job.prepare.config.invalid": "Invalid job config json file, job submission cancelled.", - "job.prepare.config.yaml-not-support": "Current operation don't support YAML job config file, job submission cancelled.", - "job.prepare.config.cancelled": "No job config selected, job submission cancelled.", - "job.prepare.upload.prompt": "Enable auto uploading of code?", - "job.prepare.upload.storage.type": "Please choose a storage type.", - "job.prepare.upload.yes.detail": "The extension will upload your project files to PAI job config's code dir automatically.", - "job.prepare.upload.undefined.hint": "Code auto uploading is enabled by default, you can disable it in user settings (ctrl+,).", - "job.prepare.generate-job-name.prompt": "Enable generating job name suffix?", - "job.prepare.generate-job-name.yes.detail": "The extension will add a random suffix to your job name when submitting job.", - "job.prepare.generate-job-name.undefined.hint": "Job name suffix generating is enabled by default, you can disable it in user settings (ctrl+,).", - "job.upload.status": "PAI: Uploading code", - "job.upload.progress": "PAI: Uploading code - {0} / {1}", - "job.upload.error": "Error occurred while uploading code: {0}", - "job.upload.invalid-code-dir": "Auto uploading doesn't support code dir with url scheme hdfs:// or webhdfs://. Please use environment variable $PAI_DEFAULT_FS_URI instead.", - "job.request.status": "PAI: Submitting job", - "job.submission.error": "Error occurred while submitting job: {0}", - "job.submission.success": "Successfully submitted job.", - "job.submission.success.open": "Open job page", - "job.simulation.status": "PAI: Generating the job simulation dockerfile", - "job.simulation.error": "Error occurred while generating the job simulation dockerfile: {0}", - "job.simulation.success": "Dockerfile has been generated in {0}/{1}. Please run {2} to simulate the job running. (docker is required)", - "job.simulation.success-dialog.reveal": "Reveal in Explorer", - "job.simulation.success-dialog.run-first-task": "Simulate first task in VS Code terminal", - "job.simulation.unsupported-env-var": "Job Simulation:Command {0} contains unsupported pai env variable", - "job.runtime.plugin.select": "Please choose a runtime plugin...", - "job.runtime.plugin.user-ssh.enable": "Enable user SSH?", - "job.runtime.plugin.insert": "OpenPAI: Insert a runtime plugin config.", - "job.runtime.plugin.ssh.key.select": "Please choose your SSH public key.", - "job.runtime.plugin.ssh.key.generator": "Generate new SSH key pair", - "job.runtime.plugin.ssh.key.import": "Import from file", - "job.runtime.plugin.ssh.key.input": "Input manully", - "job.config.component.select": "Please choose a job config component." - }, - "en": {}, - "zh": { - "common.yes": "是", - "common.no": "否", - "common.upload": "上传", - "common.finish": "完成", - "common.cancel": "取消", - "common.edit": "编辑", - "common.cluster.storage": "集群存储", - "common.personal.storage": "个人存储", - "common.workspace.nofolder": "当前工作区中没有文件夹。", - "util.editjson.validationerror": "格式错误:{0}", - "util.editjson.parseerror": "非法 JSON:{0}", - "util.editjson.prompt": "请修改配置,然后点击“完成”来保存。", - "util.editjson.previousexpired": "上一个修改配置的会话已经过期。", - "util.validatejson.error": "JSON 校验失败", - "util.openexternally.fail": "无法启动默认浏览器:{0}", - "util.remote.editor.save.prompt": "请确认你将修改这个远程文件,点击“上传”来保存。", - "cluster.activate.error": "{0}\nPAI 集群配置校验失败,已经重置,您也可以编辑原有的配置", - "cluster.activate.fix.discard": "放弃原有配置", - "cluster.version.warning": "配置的版本高于当前支持的版本。您希望编辑或重置配置吗?", - "cluster.pick.prompt": "请选择一个集群……", - "cluster.add.host.prompt": "请输入 OpenPAI 集群地址……", - "cluster.add.host.empty": "集群地址为空……", - "cluster.add.host.invalidchar": "集群地址不应该包含 '/'……", - "cluster.add.personal.storage.prompt": "请输入添加的存储的名字……", - "cluster.add.personal.storage.empty": "输入的名字地址为空……", - "cluster.add.personal.storage.invalidchar": "输入的名字不应该包含 '/'……", - "cluster.add.checkstatus": "正在检查 OpenPAI 集群状态", - "cluster.add.checkprotocolversion": "正在检查 OpenPAI protocol 版本", - "cluster.login.openPortal": "打开 OpenPAI 门户", - "cluster.login.success": "登录 OpenPAI 集群成功,请回到 VSCode 完成设置。", - "cluster.login.failed": "登录 OpenPAI 集群失败。", - "cluster.login.timeout": "浏览器没有在10秒内连上服务器,请您从网页上复制 token.", - "treeview.node.edit": "编辑配置...", - "treeview.node.storage": "打开存储...", - "treeview.node.storage.clipboard": "双击复制url到剪贴板", - "treeview.node.storage.openfile": "双击打开", - "treeview.node.storage.server-type": "服务器类型", - "treeview.node.storage.mount-point": "挂载点", - "treeview.node.storage.load-error": "加载出错", - "treeview.node.storage.load-more": "双击加载更多", - "treeview.node.openPortal": "打开 OpenPAI 门户...", - "treeview.node.listjob": "在浏览器里打开任务列表...", - "treeview.node.create-config": "创建任务配置文件...", - "treeview.node.submitjob": "提交任务...", - "treeview.node.simulate": "模拟任务执行...", - "treeview.hdfs.select-cluster.label": "双击以连接到 PAI 集群的 HDFS...", - "treeview.node.openhdfs": "打开 HDFS", - "treeview.storage.cluster-root.label": "集群共享存储", - "treeview.storage.personal-root.label": "用户个人存储", - "treeview.storage.mount.point": "Mount Point", - "treeview.storage.nfs.setup.mount.point": "双击以设置 NFS mount point", - "treeview.storage.nfs.mount": "双击以挂载 NFS", - "treeview.storage.error": "载入存储时发生错误: {0}", - "treeview.storage.nfs.sudo.prompt": "请输入密码后点击‘完成’以完成挂载。 ", - "treeview.joblist.recent": "近期从 VS Code 提交的任务", - "treeview.joblist.all": "所有任务", - "treeview.joblist.view": "查看任务详情", - "treeview.joblist.more": "显示更多...", - "treeview.joblist.error": "载入任务列表时发生错误:{0}", - "container.hdfs.mkdir.prompt": "请输入文件夹名", - "container.hdfs.mkdir.cancelled": "新建文件夹操作已取消", - "container.azure.blob.mkdir.prompt": "请输入文件夹名", - "container.azure.blob.mkdir.cancelled": "新建文件夹操作已取消", - "container.nfs.mount.failed": "挂载 NFS 失败", - "container.nfs.mount.finish": "挂载 NFS 完成", - "container.nfs.mount.unsupport.os": "挂载 NFS 失败: Unsupport OS", - "container.nfs.mount.invalid.device.name": "挂载地址应为驱动器名字(例如 Z:)", - "container.nfs.mount.path.empty": "挂载地址为空...", - "hdfs.workspace.title": "HDFS 浏览器 - {0}", - "hdfs.progress": "正在传输 - {0}% ({1} 字节 / {2} 字节)", - "hdfs.downloading": "正在下载 {0}", - "hdfs.copying": "正在将 {0} 复制到 {1}", - "hdfs.uploading": "正在上传 {0}", - "hdfs.open.prompt": "{0} 已经被载入到当前工作区。", - "hdfs.upload.status": "上传到 HDFS - {0} / {1}", - "hdfs.upload.error": "上传到 HDFS 时发生错误:{0}", - "hdfs.upload.success": "文件已被成功上传", - "storage.create.folder.error": "新建文件夹失败: {0}", - "storage.create.folder.success": "新建文件夹成功", - "storage.upload.status": "上传到存储服务器 - {0} / {1}", - "storage.upload.error": "上传时发生错误: {0}", - "storage.upload.success": "文件已被成功上传", - "storage.download.status": "从服务器上下载 - {0} / {1}", - "storage.download.error": "下载时发生错误: {0}", - "storage.download.success": "文件下载成功", - "storage.open.error": "下载时发生错误: {0}", - "storage.delete.error": "删除时发生错误: {0}", - "storage.delete.success": "文件已成功删除", - "storage.dialog.label.upload-files": "上传文件...", - "storage.dialog.label.upload-folders": "上传文件夹...", - "storage.dialog.label.download": "下载到这里", - "storage.nfs.mount.point.prompt": "请输入路径...", - "storage.upload.pick.prompt": "请选择一个存储……", - "hdfs.read.cancelled": "HDFS 下载操作已取消。", - "hdfs.write.cancelled": "HDFS 上传操作已取消。", - "webpage.dashboard.webportal": "OpenPAI 门户", - "webpage.dashboard.pick.prompt": "请选择要打开的网站", - "webpage.dashboard.pick.error": "未选择网站,操作已取消", - "webpage.joblist": "任务列表", - "job.prepare.status": "PAI: 正在准备提交任务", - "job.prepare.cluster.cancelled": "未选择集群,任务提交已被取消。", - "job.prepare.config.prompt": "请选择一个 PAI 任务配置 JSON", - "job.prepare.config.invalid": "任务配置文件不合法,任务提交已被取消。", - "job.prepare.config.yaml-not-support": "当前操作不支持YAML任务配置文件,任务提交已被取消。", - "job.prepare.config.cancelled": "未选择任务配置文件,任务提交已被取消。", - "job.prepare.upload.prompt": "是否启用代码自动上传功能?", - "job.prepare.upload.storage.type": "请选择存储的类型。", - "job.prepare.upload.yes.detail": "插件将会自动上传你的项目文件至 PAI 任务配置中的 code dir", - "job.prepare.upload.undefined.hint": "代码自动上传功能默认生效, 你可以在用户设置 (ctrl + ,) 中禁用该功能", - "job.prepare.generate-job-name.prompt": "是否启用自动生成任务名称后缀功能?", - "job.prepare.generate-job-name.yes.detail": "插件将会在提交任务时,自动在任务名称后追加随机字符串,以避免任务名称重复", - "job.prepare.generate-job-name.undefined.hint": "自动生成任务名称后缀功能默认生效, 你可以在用户设置 (ctrl + ,) 中禁用该功能", - "job.upload.status": "PAI: 正在上传代码", - "job.upload.progress": "PAI: 代码上传 - {0} / {1}", - "job.upload.error": "代码上传时发生错误:{0}", - "job.upload.invalid-code-dir": "自动上传不支持 hdfs:// 及 webhdfs:// 形式的 code dir, 请使用环境变量 $PAI_DEFAULT_FS_URI", - "job.submission.name-exist": "提交失败,已存在同名任务,是否启用自动生成任务名称后缀功能?", - "job.submission.name-exist.enable": "启用并重新提交", - "job.submission.error": "提交任务时发生错误:{0}", - "job.submission.success": "任务已成功提交。", - "job.submission.success.open": "打开任务详情页面", - "job.request.status": "PAI: 正在提交任务", - "job.simulation.status": "PAI: 正在生成模拟任务 Dockerfile", - "job.simulation.error": "生成模拟任务 Dockerfile 时发生错误:{0}", - "job.simulation.success": "Dockerfile 已经自动生成在 {0}/{1}。 请执行脚本 {2} 来模拟执行任务。 (需要 docker)", - "job.simulation.success-dialog.reveal": "在浏览器中打开", - "job.simulation.success-dialog.run-first-task": "在 VS Code 终端中模拟第一个任务", - "job.simulation.unsupported-env-var": "模拟运行:命令 {0} 中包含未支持的PAI环境变量", - "job.runtime.plugin.select": "请选择一个 runtime plugin...", - "job.runtime.plugin.user-ssh.enable": "是否开启用户 SSH ?", - "job.runtime.plugin.insert": "OpenPAI: 插入一个 runtime plugin 设置", - "job.runtime.plugin.ssh.key.select": "请选择你的 SSH 公钥", - "job.runtime.plugin.ssh.key.generator": "生成新的 SSH 密钥对", - "job.runtime.plugin.ssh.key.import": "从文件中载入 SSH 公钥", - "job.runtime.plugin.ssh.key.input": "手动输入", - "job.config.component.select": "请选择一个任务配置文件中的组件" - } -} diff --git a/contrib/pai_vscode/icons/PAI_dark.png b/contrib/pai_vscode/icons/PAI_dark.png deleted file mode 100644 index 708805fb42..0000000000 Binary files a/contrib/pai_vscode/icons/PAI_dark.png and /dev/null differ diff --git a/contrib/pai_vscode/icons/PAI_light.png b/contrib/pai_vscode/icons/PAI_light.png deleted file mode 100644 index 708805fb42..0000000000 Binary files a/contrib/pai_vscode/icons/PAI_light.png and /dev/null differ diff --git a/contrib/pai_vscode/icons/add_dark.svg b/contrib/pai_vscode/icons/add_dark.svg deleted file mode 100644 index 3475c1e196..0000000000 --- a/contrib/pai_vscode/icons/add_dark.svg +++ /dev/null @@ -1 +0,0 @@ -Layer 1 \ No newline at end of file diff --git a/contrib/pai_vscode/icons/add_light.svg b/contrib/pai_vscode/icons/add_light.svg deleted file mode 100644 index bdecdb0e45..0000000000 --- a/contrib/pai_vscode/icons/add_light.svg +++ /dev/null @@ -1 +0,0 @@ -Layer 1 \ No newline at end of file diff --git a/contrib/pai_vscode/icons/compute_target_dark.svg b/contrib/pai_vscode/icons/compute_target_dark.svg deleted file mode 100644 index 5afd429185..0000000000 --- a/contrib/pai_vscode/icons/compute_target_dark.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - compute_target_dark - Created with Sketch. - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/compute_target_light.svg b/contrib/pai_vscode/icons/compute_target_light.svg deleted file mode 100644 index 2295e6fe1a..0000000000 --- a/contrib/pai_vscode/icons/compute_target_light.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - compute_target_light - Created with Sketch. - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/config.svg b/contrib/pai_vscode/icons/config.svg deleted file mode 100644 index 2a47c0efe6..0000000000 --- a/contrib/pai_vscode/icons/config.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - diff --git a/contrib/pai_vscode/icons/connected_dark.svg b/contrib/pai_vscode/icons/connected_dark.svg deleted file mode 100644 index 40823fe86e..0000000000 --- a/contrib/pai_vscode/icons/connected_dark.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - connected_dark - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/connected_light.svg b/contrib/pai_vscode/icons/connected_light.svg deleted file mode 100644 index f5e49e4acf..0000000000 --- a/contrib/pai_vscode/icons/connected_light.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - connected_light - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/deployment.svg b/contrib/pai_vscode/icons/deployment.svg deleted file mode 100644 index 4c6cddd223..0000000000 --- a/contrib/pai_vscode/icons/deployment.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - deployment - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/ellipsis.svg b/contrib/pai_vscode/icons/ellipsis.svg deleted file mode 100644 index 9a76b9fab8..0000000000 --- a/contrib/pai_vscode/icons/ellipsis.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/contrib/pai_vscode/icons/error.svg b/contrib/pai_vscode/icons/error.svg deleted file mode 100644 index 8e08d84186..0000000000 --- a/contrib/pai_vscode/icons/error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/file.svg b/contrib/pai_vscode/icons/file.svg deleted file mode 100644 index 169af9ae18..0000000000 --- a/contrib/pai_vscode/icons/file.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/folder.svg b/contrib/pai_vscode/icons/folder.svg deleted file mode 100644 index 7387525ec4..0000000000 --- a/contrib/pai_vscode/icons/folder.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/history.svg b/contrib/pai_vscode/icons/history.svg deleted file mode 100644 index 9ef41c37cb..0000000000 --- a/contrib/pai_vscode/icons/history.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/contrib/pai_vscode/icons/latest.svg b/contrib/pai_vscode/icons/latest.svg deleted file mode 100644 index 3d882c1878..0000000000 --- a/contrib/pai_vscode/icons/latest.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/loading.svg b/contrib/pai_vscode/icons/loading.svg deleted file mode 100644 index e762f06d5e..0000000000 --- a/contrib/pai_vscode/icons/loading.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - diff --git a/contrib/pai_vscode/icons/loading_dark.svg b/contrib/pai_vscode/icons/loading_dark.svg deleted file mode 100644 index 7dc1ebd8cf..0000000000 --- a/contrib/pai_vscode/icons/loading_dark.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - diff --git a/contrib/pai_vscode/icons/octicon/file.svg b/contrib/pai_vscode/icons/octicon/file.svg deleted file mode 100644 index f4efbc238b..0000000000 --- a/contrib/pai_vscode/icons/octicon/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/octicon/file_dark.svg b/contrib/pai_vscode/icons/octicon/file_dark.svg deleted file mode 100644 index 6abbe9f171..0000000000 --- a/contrib/pai_vscode/icons/octicon/file_dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/octicon/home.svg b/contrib/pai_vscode/icons/octicon/home.svg deleted file mode 100644 index 8799a322c1..0000000000 --- a/contrib/pai_vscode/icons/octicon/home.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/octicon/home_dark.svg b/contrib/pai_vscode/icons/octicon/home_dark.svg deleted file mode 100644 index 1d033a9d85..0000000000 --- a/contrib/pai_vscode/icons/octicon/home_dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/octicon/terminal.svg b/contrib/pai_vscode/icons/octicon/terminal.svg deleted file mode 100644 index b6df312df4..0000000000 --- a/contrib/pai_vscode/icons/octicon/terminal.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/octicon/terminal_dark.svg b/contrib/pai_vscode/icons/octicon/terminal_dark.svg deleted file mode 100644 index fb45d72a62..0000000000 --- a/contrib/pai_vscode/icons/octicon/terminal_dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/ok.svg b/contrib/pai_vscode/icons/ok.svg deleted file mode 100644 index 3efeb56727..0000000000 --- a/contrib/pai_vscode/icons/ok.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/pai_container.png b/contrib/pai_vscode/icons/pai_container.png deleted file mode 100644 index b071bcc917..0000000000 Binary files a/contrib/pai_vscode/icons/pai_container.png and /dev/null differ diff --git a/contrib/pai_vscode/icons/queue.svg b/contrib/pai_vscode/icons/queue.svg deleted file mode 100644 index a0577b519e..0000000000 --- a/contrib/pai_vscode/icons/queue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/refresh_dark.svg b/contrib/pai_vscode/icons/refresh_dark.svg deleted file mode 100644 index d79fdaa4e8..0000000000 --- a/contrib/pai_vscode/icons/refresh_dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/refresh_light.svg b/contrib/pai_vscode/icons/refresh_light.svg deleted file mode 100644 index e034574819..0000000000 --- a/contrib/pai_vscode/icons/refresh_light.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/run.svg b/contrib/pai_vscode/icons/run.svg deleted file mode 100644 index a37ceb2579..0000000000 --- a/contrib/pai_vscode/icons/run.svg +++ /dev/null @@ -1 +0,0 @@ -ProgressBar_16x \ No newline at end of file diff --git a/contrib/pai_vscode/icons/run_history_dark.svg b/contrib/pai_vscode/icons/run_history_dark.svg deleted file mode 100644 index c1f1eb6252..0000000000 --- a/contrib/pai_vscode/icons/run_history_dark.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - run_history_dark - Created with Sketch. - - - - - - - - - - - - - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/run_history_light.svg b/contrib/pai_vscode/icons/run_history_light.svg deleted file mode 100644 index dfb79230c8..0000000000 --- a/contrib/pai_vscode/icons/run_history_light.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - run_history_light - Created with Sketch. - - - - - - - - - - - - - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/stop.svg b/contrib/pai_vscode/icons/stop.svg deleted file mode 100644 index 6b6668df9c..0000000000 --- a/contrib/pai_vscode/icons/stop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/contrib/pai_vscode/icons/storage.svg b/contrib/pai_vscode/icons/storage.svg deleted file mode 100644 index 3c2de89c24..0000000000 --- a/contrib/pai_vscode/icons/storage.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/contrib/pai_vscode/installYamlExtension.js b/contrib/pai_vscode/installYamlExtension.js deleted file mode 100644 index 94d8570823..0000000000 --- a/contrib/pai_vscode/installYamlExtension.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -var fs = require('fs'); -var getDirName = require('path').dirname; -var os = require('os'); -var path = require('path'); -var request = require('request'); -var unzipper = require('unzipper'); - -function mkDirByPathSync(targetDir) { - const sep = path.sep; - const initDir = path.isAbsolute(targetDir) ? sep : ''; - return targetDir.split(sep).reduce((parentDir, childDir) => { - const curDir = path.resolve(parentDir, childDir); - try { - fs.mkdirSync(curDir); - } catch (err) { - if (err.code === 'EEXIST') { - return curDir; - } - } - return curDir; - }, initDir); -} - -async function downloadAndUnzipExtension(url, dest) { - request(url).pipe(unzipper.Parse()).on('entry', function (entry) { - if (entry.path.startsWith('extension/')) { - var newPath = path.resolve(dest, entry.path.slice(10)); - mkDirByPathSync(getDirName(newPath)); - entry.pipe(fs.createWriteStream(newPath)); - } else if (entry.path === 'extension.vsixmanifest') { - var newPath = path.resolve(dest, entry.path.slice(9)); - mkDirByPathSync(getDirName(newPath)); - entry.pipe(fs.createWriteStream(newPath)); - } else { - entry.autodrain(); - } - }); -} - -function installVscodeYamlExtension() { - const version = '0.4.0'; - const extensionPath = path.join(os.homedir(), `.vscode/extensions/redhat.vscode-yaml-${version}`); - const url = `https://github.com/redhat-developer/vscode-yaml/releases/download/0.4.0/redhat.vscode-yaml-0.4.0.vsix`; - downloadAndUnzipExtension(url, extensionPath); -} - -installVscodeYamlExtension(); diff --git a/contrib/pai_vscode/package.json b/contrib/pai_vscode/package.json deleted file mode 100644 index b4b6076d4d..0000000000 --- a/contrib/pai_vscode/package.json +++ /dev/null @@ -1,679 +0,0 @@ -{ - "name": "pai-vscode", - "displayName": "OpenPAI VS Code Client", - "description": "Interact with Open Platform for AI (OpenPAI) from inside your editor", - "version": "0.3.0", - "publisher": "OpenPAIVSCodeClient", - "preview": true, - "icon": "assets/pai_logo.png", - "repository": { - "url": "https://github.com/Microsoft/pai" - }, - "bugs": { - "url": "https://github.com/Microsoft/pai/issues" - }, - "engines": { - "vscode": "^1.40.0" - }, - "categories": [ - "Other" - ], - "keywords": [ - "AI", - "Deep Learning", - "PAI" - ], - "activationEvents": [ - "*", - "onFileSystem:webhdfs" - ], - "main": "./out/extension", - "contributes": { - "commands": [ - { - "command": "paiext.cluster.dashboard.open", - "title": "%paiext.cluster.dashboard.open%", - "category": "PAI" - }, - { - "command": "paiext.cluster.job.list", - "title": "%paiext.cluster.job.list%", - "category": "PAI" - }, - { - "command": "paiext.cluster.job.submit", - "title": "%paiext.cluster.job.submit%", - "category": "PAI" - }, - { - "command": "paiext.cluster.job.simulate", - "title": "%paiext.cluster.job.simulate%", - "category": "PAI" - }, - { - "command": "paiext.job.config.insert.menu", - "title": "%paiext.job.config.insert.menu%", - "category": "PAI" - }, - { - "command": "paiext.cluster.job.create-config", - "title": "%paiext.cluster.job.create-config%", - "category": "PAI" - }, - { - "command": "paiext.cluster.job.create-config-v1", - "title": "%paiext.cluster.job.create-config-v1%", - "category": "PAI" - }, - { - "command": "paiext.cluster.job.create-config-v2", - "title": "%paiext.cluster.job.create-config-v2%", - "category": "PAI" - }, - { - "command": "paiext.cluster.delete", - "title": "%paiext.cluster.delete%", - "category": "PAI" - }, - { - "command": "paiext.cluster.edit", - "title": "%paiext.cluster.edit%", - "category": "PAI" - }, - { - "command": "paiext.storage.personal.delete", - "title": "%paiext.storage.personal.delete%", - "category": "PAI" - }, - { - "command": "paiext.storage.personal.edit", - "title": "%paiext.storage.personal.edit%", - "category": "PAI" - }, - { - "command": "paiext.storage.open", - "title": "%paiext.storage.open%", - "category": "PAI" - }, - { - "command": "paiext.hdfs.upload.files", - "title": "%paiext.hdfs.upload.files%", - "category": "PAI" - }, - { - "command": "paiext.hdfs.upload.folders", - "title": "%paiext.hdfs.upload.folders%", - "category": "PAI" - }, - { - "command": "paiext.hdfs.download", - "title": "%paiext.hdfs.download%", - "category": "PAI" - }, - { - "command": "paiext.storage.delete", - "title": "%paiext.storage.delete%", - "category": "PAI" - }, - { - "command": "paiext.storage.upload.files", - "title": "%paiext.storage.upload.files%", - "category": "PAI" - }, - { - "command": "paiext.storage.create.folder", - "title": "%paiext.storage.create.folder%", - "category": "PAI" - }, - { - "command": "paiext.storage.download", - "title": "%paiext.storage.download%", - "category": "PAI" - }, - { - "command": "paiext.container.joblist.refresh", - "title": "%paiext.common.refresh%", - "icon": { - "light": "icons/refresh_light.svg", - "dark": "icons/refresh_dark.svg" - }, - "category": "PAI" - }, - { - "command": "paiext.container.joblist.more", - "title": "%paiext.cluster.job.more%", - "category": "PAI" - }, - { - "command": "paiext.cluster.refresh", - "title": "%paiext.common.refresh%", - "icon": { - "light": "icons/refresh_light.svg", - "dark": "icons/refresh_dark.svg" - }, - "category": "PAI" - }, - { - "command": "paiext.cluster.add", - "title": "%paiext.cluster.add%", - "icon": { - "light": "icons/add_light.svg", - "dark": "icons/add_dark.svg" - }, - "category": "PAI" - }, - { - "command": "paiext.storage.personal.add", - "title": "%paiext.storage.personal.add%", - "category": "PAI" - }, - { - "command": "paiext.container.hdfs.refresh", - "title": "%paiext.common.refresh%", - "icon": { - "light": "icons/refresh_light.svg", - "dark": "icons/refresh_dark.svg" - }, - "category": "PAI" - }, - { - "command": "paiext.container.hdfs.back", - "title": "%paiext.container.hdfs.back%", - "icon": { - "light": "icons/octicon/home.svg", - "dark": "icons/octicon/home_dark.svg" - } - }, - { - "command": "paiext.container.storage.refresh", - "title": "%paiext.common.refresh%", - "icon": { - "light": "icons/refresh_light.svg", - "dark": "icons/refresh_dark.svg" - }, - "category": "PAI" - }, - { - "command": "paiext.container.storage.back", - "title": "%paiext.container.storage.back%", - "icon": { - "light": "icons/octicon/home.svg", - "dark": "icons/octicon/home_dark.svg" - } - }, - { - "command": "paiext.container.hdfs.delete", - "title": "%paiext.container.hdfs.delete%" - }, - { - "command": "paiext.container.hdfs.mkdir", - "title": "%paiext.container.hdfs.mkdir%" - }, - { - "command": "paiext.job.config.insert.runtime.plugin", - "title": "%paiext.job.config.insert.runtime.plugin%", - "category": "PAI" - } - ], - "viewsContainers": { - "activitybar": [ - { - "id": "PAIContainer", - "title": "%container.title%", - "icon": "icons/pai_container.png" - } - ] - }, - "views": { - "explorer": [ - { - "id": "PAIExplorer", - "name": "%explorer.paiClusterExplorer%" - } - ], - "PAIContainer": [ - { - "id": "PAIContainerStorage", - "name": "%container.storage.title%" - }, - { - "id": "PAIContainerHDFS", - "name": "%container.hdfs.title%" - }, - { - "id": "PAIContainerJobList", - "name": "%container.joblist.title%" - } - ] - }, - "menus": { - "commandPalette": [ - { - "command": "paiext.cluster.job.list", - "when": "false" - }, - { - "command": "paiext.cluster.refresh", - "when": "false" - }, - { - "command": "paiext.cluster.edit", - "when": "false" - }, - { - "command": "paiext.cluster.delete", - "when": "false" - }, - { - "command": "paiext.container.joblist.refresh", - "when": "false" - }, - { - "command": "paiext.container.joblist.more", - "when": "false" - }, - { - "command": "paiext.hdfs.upload.files", - "when": "false" - }, - { - "command": "paiext.hdfs.upload.folders", - "when": "false" - }, - { - "command": "paiext.hdfs.download", - "when": "false" - }, - { - "command": "paiext.container.hdfs.refresh", - "when": "false" - }, - { - "command": "paiext.container.hdfs.back", - "when": "false" - }, - { - "command": "paiext.container.hdfs.delete", - "when": "false" - }, - { - "command": "paiext.container.hdfs.mkdir", - "when": "false" - }, - { - "command": "paiext.job.config.insert.menu", - "when": "false" - }, - { - "command": "paiext.job.config.insert.runtime.plugin", - "when": "false" - }, - { - "command": "paiext.storage.personal.edit", - "when": "false" - }, - { - "command": "paiext.storage.personal.delete", - "when": "false" - }, - { - "command": "paiext.cluster.job.create-config-v1", - "when": "false" - }, - { - "command": "paiext.cluster.job.create-config-v2", - "when": "false" - } - ], - "view/title": [ - { - "command": "paiext.cluster.refresh", - "when": "view == PAIExplorer", - "group": "navigation" - }, - { - "command": "paiext.cluster.add", - "when": "view == PAIExplorer", - "group": "navigation" - }, - { - "command": "paiext.container.hdfs.refresh", - "when": "view == PAIContainerHDFS", - "group": "navigation" - }, - { - "command": "paiext.container.hdfs.back", - "when": "view == PAIContainerHDFS", - "group": "navigation" - }, - { - "command": "paiext.container.storage.refresh", - "when": "view == PAIContainerStorage", - "group": "navigation" - }, - { - "command": "paiext.container.storage.back", - "when": "view == PAIContainerStorage", - "group": "navigation" - }, - { - "command": "paiext.container.joblist.refresh", - "when": "view == PAIContainerJobList", - "group": "navigation" - } - ], - "explorer/context": [ - { - "command": "paiext.cluster.job.create-config-v1", - "when": "resourceLangId =~ /(python)|(cntk)/ && resourceScheme == file" - }, - { - "command": "paiext.cluster.job.create-config-v2", - "when": "resourceLangId =~ /(python)|(cntk)/ && resourceScheme == file" - }, - { - "command": "paiext.cluster.job.submit", - "when": "resourceFilename =~ /\\.(jsonc?|yaml|yml)$/ && resourceScheme == file" - }, - { - "command": "paiext.cluster.job.simulate", - "when": "resourceFilename =~ /\\.(jsonc?|yaml|yml)$/ && resourceScheme == file" - }, - { - "command": "paiext.hdfs.download", - "when": "resourceScheme == webhdfs" - }, - { - "command": "paiext.hdfs.upload.files", - "when": "resourceScheme == webhdfs && !resourceIsFile" - }, - { - "command": "paiext.hdfs.upload.folders", - "when": "resourceScheme == webhdfs && !resourceIsFile" - } - ], - "editor/context": [ - { - "command": "paiext.cluster.job.create-config-v1", - "when": "resourceLangId =~ /(python)|(cntk)/ && resourceScheme == file" - }, - { - "command": "paiext.cluster.job.create-config-v2", - "when": "resourceLangId =~ /(python)|(cntk)/ && resourceScheme == file" - }, - { - "command": "paiext.cluster.job.submit", - "when": "resourceFilename =~ /\\.(jsonc?|yaml|yml)$/ && resourceScheme == file" - }, - { - "command": "paiext.cluster.job.simulate", - "when": "resourceFilename =~ /\\.(jsonc?|yaml|yml)?$/ && resourceScheme == file" - }, - { - "command": "paiext.job.config.insert.menu", - "when": "resourceFilename =~ /\\.(yaml|yml)$/ && resourceScheme == file" - } - ], - "view/item/context": [ - { - "command": "paiext.cluster.edit", - "when": "view == PAIExplorer && viewItem == PAIConfiguration" - }, - { - "command": "paiext.cluster.delete", - "when": "view == PAIExplorer && viewItem == PAIConfiguration" - }, - { - "command": "paiext.storage.personal.edit", - "when": "view == PAIContainerStorage && viewItem == PAIStoragePersonalItem" - }, - { - "command": "paiext.storage.personal.delete", - "when": "view == PAIContainerStorage && viewItem == PAIStoragePersonalItem" - }, - { - "command": "paiext.storage.personal.add", - "when": "view == PAIContainerStorage && viewItem == PAIStoragePersonalRoot" - }, - { - "command": "paiext.container.hdfs.mkdir", - "when": "view == PAIContainerHDFS && viewItem && viewItem != PAIHdfsFile", - "group": "1@1" - }, - { - "command": "paiext.hdfs.download", - "when": "view == PAIContainerHDFS && viewItem && viewItem != PAIHdfsRoot", - "group": "2@1" - }, - { - "command": "paiext.hdfs.upload.files", - "when": "view == PAIContainerHDFS && viewItem && viewItem != PAIHdfsFile", - "group": "2@2" - }, - { - "command": "paiext.hdfs.upload.folders", - "when": "view == PAIContainerHDFS && viewItem && viewItem != PAIHdfsFile", - "group": "2@3" - }, - { - "command": "paiext.container.hdfs.delete", - "when": "view == PAIContainerHDFS && viewItem && viewItem != PAIHdfsRoot", - "group": "3@1" - }, - { - "command": "paiext.cluster.job.list", - "when": "view == PAIContainerJobList && viewItem && viewItem == PAIJobListCluster" - }, - { - "command": "paiext.container.storage.refresh", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStorageTeamItem", - "group": "refresh" - }, - { - "command": "paiext.container.storage.refresh", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStorageMountPointItem", - "group": "refresh" - }, - { - "command": "paiext.container.storage.refresh", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStorageFolder", - "group": "refresh" - }, - { - "command": "paiext.container.storage.refresh", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStorageClusterRoot", - "group": "refresh" - }, - { - "command": "paiext.container.storage.refresh", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStoragePersonalRoot", - "group": "refresh" - }, - { - "command": "paiext.container.storage.refresh", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStoragePersonalItem", - "group": "refresh" - }, - { - "command": "paiext.storage.delete", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStorageFile", - "group": "delete" - }, - { - "command": "paiext.storage.delete", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStorageFolder", - "group": "delete" - }, - { - "command": "paiext.storage.upload.files", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStorageFolder", - "group": "upload" - }, - { - "command": "paiext.storage.upload.files", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStorageMountPointItem", - "group": "upload" - }, - { - "command": "paiext.storage.upload.files", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStoragePersonalItem", - "group": "upload" - }, - { - "command": "paiext.storage.create.folder", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStorageFolder", - "group": "mkdir" - }, - { - "command": "paiext.storage.create.folder", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStorageMountPointItem", - "group": "mkdir" - }, - { - "command": "paiext.storage.create.folder", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStoragePersonalItem", - "group": "mkdir" - }, - { - "command": "paiext.storage.download", - "when": "view == PAIContainerStorage && viewItem && viewItem == PAIStorageFile", - "group": "azure@5" - } - ] - }, - "jsonValidation": [ - { - "fileMatch": "*.pai.json*", - "url": "./schemas/pai_job_config.schema.json" - }, - { - "fileMatch": "pai_cluster_*.json*", - "url": "./schemas/pai_cluster.schema.json" - }, - { - "fileMatch": "pai_full_configuration.json*", - "url": "./schemas/pai_configuration.schema.json" - } - ], - "configuration": { - "title": "%config.title%", - "properties": { - "pai.job.upload.enabled": { - "type": "boolean", - "description": "%config.job.upload.enabled%", - "default": null - }, - "pai.job.upload.exclude": { - "type": "array", - "description": "%config.job.upload.exclude%" - }, - "pai.job.upload.include": { - "type": "array", - "description": "%config.job.upload.include%" - }, - "pai.job.generateJobName.enabled": { - "type": "boolean", - "description": "%config.job.generateJobName.enabled%", - "default": null - }, - "pai.job.jobList.recentJobsLength": { - "type": "number", - "description": "%config.job.jobList.recentJobsLength%", - "default": 5 - }, - "pai.job.jobList.allJobsPageSize": { - "type": "number", - "description": "%config.job.jobList.allJobsPageSize%", - "default": 20 - }, - "pai.job.jobList.refreshInterval": { - "type": "number", - "description": "%config.job.jobList.refreshInterval%", - "default": 10 - }, - "pai.job.v2.upload": { - "type": "object", - "description": "%config.job.v2.upload%", - "default": null - }, - "pai.hdfs.location": { - "type": "string", - "enum": [ - "sidebar", - "explorer" - ], - "default": "sidebar", - "description": "%config.hdfs.location%" - }, - "pai.storage.nfs.mountPoint": { - "type": "object", - "default": null - } - } - } - }, - "extensionDependencies": [ - "redhat.vscode-yaml" - ], - "scripts": { - "vscode:prepublish": "yarn compile", - "compile": "tsc -p ./", - "watch": "tsc -watch -p ./", - "package": "vsce package --yarn", - "test": "yarn compile && node ./installYamlExtension.js && node ./out/test/runTest.js" - }, - "devDependencies": { - "@types/fs-extra": "^5.0.4", - "@types/globalize": "^0.0.34", - "@types/globby": "^9.1.0", - "@types/inversify": "^2.0.33", - "@types/js-yaml": "^3.12.1", - "@types/lodash": "^4.14.117", - "@types/mocha": "^5.2.5", - "@types/nock": "^9.3.0", - "@types/node": "^12.12.5", - "@types/node-rsa": "^1.0.0", - "@types/opn": "^5.1.0", - "@types/request": "^2.47.1", - "@types/request-promise-native": "^1.0.15", - "@types/semver-compare": "^1.0.0", - "@types/sshpk": "^1.10.4", - "@types/uuid": "^3.4.4", - "@types/vscode": "^1.40.0", - "mocha": "^7.0.1", - "nock": "^11.8.2", - "tslint": "^6.0.0", - "tslint-microsoft-contrib": "^5.2.1", - "typescript": "^3.5.1", - "typescript-tslint-plugin": "^0.4.0", - "vsce": "^1.73.0", - "vscode-test": "^1.3.0" - }, - "dependencies": { - "@azure/storage-blob": "^12.0.1", - "ajv": "^6.5.4", - "fs-extra": "^7.0.0", - "globalize": "^1.4.0", - "globby": "^10.0.1", - "inversify": "^5.0.1", - "js-yaml": "^3.13.1", - "json-inline-doc": "^2.0.1", - "json-schema-ref-parser": "^7.1.3", - "jsonc-parser": "^2.1.1", - "lodash": "^4.17.15", - "node-rsa": "^1.0.7", - "node-yaml-parser": "^0.0.9", - "openpai-js-sdk": "microsoft/pai#openpai-js-sdk", - "opn": "^5.4.0", - "reflect-metadata": "^0.1.12", - "request": "^2.88.0", - "request-promise-native": "^1.0.8", - "semver-compare": "^1.0.0", - "sshpk": "^1.16.1", - "streamifier": "^0.1.1", - "unixify": "^1.0.0", - "unzipper": "^0.10.1", - "uuid": "^3.3.2", - "webhdfs": "^1.2.0" - } -} diff --git a/contrib/pai_vscode/package.nls.json b/contrib/pai_vscode/package.nls.json deleted file mode 100644 index 427f340077..0000000000 --- a/contrib/pai_vscode/package.nls.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "explorer.paiClusterExplorer": "PAI Cluster Explorer", - "container.title": "Open Platform for AI", - "container.hdfs.title": "HDFS Explorer", - "container.storage.title": "Storage Explorer", - "container.joblist.title": "PAI Job List", - "paiext.common.refresh": "Refresh", - "paiext.cluster.add": "Add PAI Cluster", - "paiext.cluster.edit": "Edit PAI Cluster Configuration", - "paiext.cluster.delete": "Delete PAI Cluster Configuration", - "paiext.storage.personal.add": "Add Personal Storage", - "paiext.storage.personal.edit": "Edit Personal Storage Configuration", - "paiext.storage.personal.delete": "Delete Personal Storage Configuration", - "paiext.storage.open": "Open Share Storage", - "paiext.hdfs.upload.files": "Upload Files", - "paiext.hdfs.upload.folders": "Upload Folders", - "paiext.hdfs.download": "Download", - "paiext.storage.create.folder": "New folder", - "paiext.storage.upload.files": "Upload Files", - "paiext.storage.upload.folders": "Upload Folders", - "paiext.storage.delete": "Delete", - "paiext.storage.download": "Download", - "paiext.nfs.open": "Open NFS", - "paiext.azure-blob.open": "Open Azure Blob", - "paiext.container.hdfs.back": "Go back to cluster selection", - "paiext.container.hdfs.delete": "Delete", - "paiext.container.hdfs.mkdir": "New Folder", - "paiext.container.storage.back": "Go back", - "paiext.cluster.dashboard.open": "Open Website", - "paiext.cluster.job.list": "Open Job List Externally", - "paiext.cluster.job.submit": "Submit Job to PAI Cluster", - "paiext.cluster.job.create-config": "Create PAI Job Config", - "paiext.cluster.job.create-config-v1": "Create PAI Job Config V1", - "paiext.cluster.job.create-config-v2": "Create PAI Job Config V2", - "paiext.cluster.job.simulate": "Simulate PAI Job Running", - "paiext.cluster.job.view": "View Job Detail", - "paiext.cluster.job.more": "View More...", - "paiext.job.config.insert.runtime.plugin": "Insert runtime plugin config", - "paiext.job.config.insert.menu": "OpenPAI: Insert job config", - "config.title": "OpenPAI VS Code Client Settings", - "config.job.upload.enabled": "Controls whether the extension will upload your project files to PAI job config's code dir automatically", - "config.job.upload.exclude": "Glob pattern for excluding files and folders", - "config.job.upload.include": "Glob pattern for including files and folders", - "config.job.generateJobName.enabled": "Controls whether the extension will add a random suffix to your job name when submitting job", - "config.job.jobList.recentJobsLength": "Controls the number of recently submitted jobs to keep in history for each PAI cluster", - "config.job.jobList.allJobsPageSize": "Controls the page size of list when listing jobs for each PAI cluster", - "config.job.jobList.refreshInterval": "Controls the refresh interval of job list (in seconds)", - "config.job.v2.upload": "Config the extension to upload your project files to PAI storage automatically", - "config.hdfs.location": "Location where hdfs explorer will be shown", - "pai.storage.nfs.mountPoint": "OpenPAI NFS storage mount point, '_: '" -} diff --git a/contrib/pai_vscode/package.nls.zh-cn.json b/contrib/pai_vscode/package.nls.zh-cn.json deleted file mode 100644 index d689abeebc..0000000000 --- a/contrib/pai_vscode/package.nls.zh-cn.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "explorer.paiClusterExplorer": "PAI 集群浏览器", - "container.title": "AI 开发平台 (PAI)", - "container.hdfs.title": "HDFS 浏览器", - "container.storage.title": "存储浏览器", - "container.joblist.title": "PAI 任务列表", - "paiext.common.refresh": "刷新", - "paiext.cluster.add": "添加 PAI 集群", - "paiext.cluster.edit": "编辑 PAI 集群配置", - "paiext.cluster.delete": "删除 PAI 集群配置", - "paiext.storage.personal.add": "添加个人存储", - "paiext.storage.personal.edit": "编辑个人存储设置", - "paiext.storage.personal.delete": "删除个人存储设置", - "paiext.storage.open": "打开共享存储", - "paiext.hdfs.upload.files": "上传文件", - "paiext.hdfs.upload.folders": "上传文件夹", - "paiext.hdfs.download": "下载", - "paiext.storage.create.folder": "新建文件夹", - "paiext.storage.upload.files": "上传文件", - "paiext.storage.upload.folders": "上传文件夹", - "paiext.storage.delete": "删除", - "paiext.storage.download": "下载", - "paiext.azure-blob.open": "打开 Azure Blob", - "paiext.nfs.open": "打开 NFS", - "paiext.container.hdfs.back": "后退至集群选择", - "paiext.container.hdfs.delete": "删除", - "paiext.container.hdfs.mkdir": "新建文件夹", - "paiext.container.storage.back": "后退", - "paiext.cluster.dashboard.open": "打开网站页面", - "paiext.cluster.job.list": "在浏览器里打开任务列表", - "paiext.cluster.job.submit": "在 PAI 集群上提交任务", - "paiext.cluster.job.create-config": "创建 PAI 任务配置文件", - "paiext.cluster.job.create-config-v1": "创建 PAI 任务配置文件 V1", - "paiext.cluster.job.create-config-v2": "创建 PAI 任务配置文件 V2", - "paiext.cluster.job.simulate": "模拟 PAI 任务执行", - "paiext.cluster.job.view": "查看任务详情", - "paiext.cluster.job.more": "显示更多...", - "paiext.job.config.insert.runtime.plugin": "插入 runtime plugin 设置", - "paiext.job.config.insert.menu": "OpenPAI: 插入任务设置", - "config.title": "OpenPAI VS Code 客户端设置", - "config.job.upload.enabled": "控制插件是否会自动将项目源代码上传至 PAI 任务配置文件的 CodeDir", - "config.job.upload.exclude": "控制排除文件、文件夹的 Glob 模式", - "config.job.upload.include": "控制包括文件、文件夹的 Glob 模式", - "config.job.generateJobName.enabled": "控制插件是否会在提交任务时,自动在任务名称后添加随机字符串,以避免重复", - "config.job.jobList.recentJobsLength": "控制每个 PAI 集群保留最近提交任务的数量", - "config.job.jobList.allJobsPageSize": "控制 PAI 集群任务列表的分页大小", - "config.job.jobList.refreshInterval": "控制任务列表的刷新间隔(秒)", - "config.job.v2.upload": "设置插件自动将项目文件上传至 PAI 的存储服务器", - "config.hdfs.location": "HDFS 浏览器显示的位置", - "pai.storage.nfs.mountPoint": "OpenPAI NFS 存储挂载点, '<集群名称>_: <挂载路径>'" -} diff --git a/contrib/pai_vscode/schemas/pai_cluster.schema.json b/contrib/pai_vscode/schemas/pai_cluster.schema.json deleted file mode 100644 index 9b550dc370..0000000000 --- a/contrib/pai_vscode/schemas/pai_cluster.schema.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "PAI cluster name" - }, - "username": { - "type": "string", - "description": "PAI cluster user name", - "minLength": 1 - }, - "password": { - "type": "string", - "description": "PAI cluster password, please remove it if use token", - "minLength": 1 - }, - "token": { - "type": "string", - "description": "PAI cluster access token, please remove it if use password" - }, - "https": { - "type": "boolean", - "description": "Use https to access PAI cluster" - }, - "rest_server_uri": { - "type": "string", - "description": "PAI rest api endpoint", - "pattern": "^(?!https?:\/\/).*" - }, - "webhdfs_uri": { - "type": "string", - "description": "PAI webhdfs endpoint", - "pattern": "^(?!https?:\/\/).*" - }, - "hdfs_uri": { - "type": "string", - "description": "PAI hdfs endpoint", - "pattern": "^hdfs:\/\/.*" - }, - "grafana_uri": { - "type": "string", - "description": "PAI grafana dashboard endpoint", - "pattern": "^(?!https?:\/\/).*" - }, - "k8s_dashboard_uri": { - "type": "string", - "description": "PAI kubernetes dashboard endpoint", - "pattern": "^(?!https?:\/\/).*" - }, - "web_portal_uri": { - "type": "string", - "description": "PAI web portal endpoint", - "pattern": "^(?!https?:\/\/).*" - }, - "protocol_version": { - "type": "string", - "description": "PAI protocol version" - } - }, - "oneOf": [ - { - "required": [ - "username", - "password", - "rest_server_uri" - ] - }, - { - "required": [ - "username", - "token", - "rest_server_uri" - ] - } - ] -} diff --git a/contrib/pai_vscode/schemas/pai_configuration.schema.json b/contrib/pai_vscode/schemas/pai_configuration.schema.json deleted file mode 100644 index 17eb34d8c5..0000000000 --- a/contrib/pai_vscode/schemas/pai_configuration.schema.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "properties": { - "version": { - "type": "string", - "description": "Version of the configuration in semver format" - }, - "pais": { - "type": "array", - "description": "Individual configurations of each PAI cluster", - "items": { "$ref": "pai_cluster.schema.json" } - } - }, - "required": [ - "version", - "pais" - ] -} \ No newline at end of file diff --git a/contrib/pai_vscode/schemas/pai_job_config.schema.json b/contrib/pai_vscode/schemas/pai_job_config.schema.json deleted file mode 100644 index 989c9d36e7..0000000000 --- a/contrib/pai_vscode/schemas/pai_job_config.schema.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "type": "object", - "description": "PAI job config\nThis file can be submitted directly on PAI web portal.", - "properties": { - "jobName": { - "type": "string", - "description": "Name for the job, need to be unique", - "pattern": "^[A-Za-z0-9\\-._~]+$" - }, - "image": { - "type": "string", - "description": "URL pointing to the Docker image for all tasks in the job" - }, - "authFile": { - "type": "string", - "description": "Docker registry authentication file existing on HDFS" - }, - "dataDir": { - "type": "string", - "description": "Data directory existing on HDFS.\nFull HDFS path will be exported as an environment variable $PAI_DATA_DIR." - }, - "outputDir": { - "type": "string", - "description": "Output directory on HDFS, $PAI_DEFAULT_FS_URI/Output/$jobName will be used if not specified.\nFull HDFS path will be exported as an environment variable $PAI_OUTPUT_DIR." - }, - "codeDir": { - "type": "string", - "description": "Code directory existing on HDFS.\nFull HDFS path will be exported as an environment variable $PAI_CODE_DIR." - }, - "virtualCluster": { - "type": "string", - "description": "The virtual cluster job runs on. If omitted, the job will run on default virtual cluster" - }, - "taskRoles": { - "type": "array", - "description": "List of taskRole, one task role at least", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name for the task role, need to be unique with other roles", - "pattern": "^[A-Za-z0-9\\-._~]+$" - }, - "taskNumber": { - "type": "integer", - "description": "Number of tasks for the task role, no less than 1", - "minimum": 1 - }, - "cpuNumber": { - "type": "integer", - "description": "CPU number for one task in the task role, no less than 1", - "minimum": 1 - }, - "memoryMB": { - "type": "integer", - "description": "Memory for one task in the task role, no less than 100", - "minimum": 100 - }, - "shmMB": { - "type": "integer", - "description": "Shared memory for one task in the task role, no more than memory size. The default value is 64MB" - }, - "gpuNumber": { - "type": "integer", - "description": "GPU number for one task in the task role, no less than 0", - "minimum": 0 - }, - "portList": { - "type": "array", - "description": "List of portType to use", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string", - "description": "Label name for the port type", - "pattern": "^[A-Za-z0-9._~]+$" - }, - "beginAt": { - "type": "number", - "description": "The port to begin with in the port type, 0 for random selection" - }, - "portNumber": { - "type": "number", - "description": "Number of ports for the specific type" - } - }, - "required": [ - "label", - "beginAt", - "portNumber" - ] - } - }, - "command": { - "type": "string", - "description": "Executable command for tasks in the task role, can not be empty\n** PLEASE CHANGE MANUALLY **", - "minLength": 1, - "pattern": "^(?!.*)" - }, - "minFailedTaskCount": { - "type": ["integer", "null"], - "description": "Number of failed tasks to kill the entire job, null or no less than 1", - "minimum": 1 - }, - "minSucceededTaskCount": { - "type": ["integer", "null"], - "description": "Number of succeeded tasks to kill the entire job, null or no less than 1", - "minimum": 1 - } - }, - "required": [ - "name", - "taskNumber", - "cpuNumber", - "memoryMB", - "gpuNumber", - "command" - ] - }, - "minItems": 1 - }, - "gpuType": { - "type": "string", - "description": "Specify the GPU type to be used in the tasks. If omitted, the job will run on any gpu type" - }, - "retryCount": { - "type": "integer", - "description": "Job retry count, no less than 0", - "minimum": 0 - }, - "jobEnvs": { - "type": "object", - "description": "Job env parameters, key-value pairs, available in job container and no substitution allowed" - }, - "extras": { - "type": "object", - "description": "Extra parameters, key-value pairs, save any information that job may use" - } - }, - "required": [ - "jobName", - "image", - "taskRoles" - ] -} \ No newline at end of file diff --git a/contrib/pai_vscode/schemas/pai_personal_storage.schema.json b/contrib/pai_vscode/schemas/pai_personal_storage.schema.json deleted file mode 100644 index 80adc2d798..0000000000 --- a/contrib/pai_vscode/schemas/pai_personal_storage.schema.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "type": "object", - "properties": { - "spn": { - "type": "string", - "description": "storage server display name", - "minLength": 1 - }, - "data": { - "type": "object", - "description": "Azure blob", - "properties": { - "containerName": { - "type": "string" - }, - "accountName": { - "type": "string" - }, - "key": { - "type": "string" - } - }, - "additionalProperties": true - } - } -} diff --git a/contrib/pai_vscode/schemas/pai_yaml_job_config.schema.json b/contrib/pai_vscode/schemas/pai_yaml_job_config.schema.json deleted file mode 100644 index 1331bbc65a..0000000000 --- a/contrib/pai_vscode/schemas/pai_yaml_job_config.schema.json +++ /dev/null @@ -1,366 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Job Config", - "description": "OpenPAI Job Protocol", - "type": "object", - - "definitions": { - "basicSchema": { - "type": "object", - "properties": { - "protocolVersion": { - "description": "Protocol version, current version is 2.", - "enum": ["2", 2] - }, - "name": { - "type": "string", - "pattern": "^[a-zA-Z0-9_-]+$" - }, - "version": { - "description": "Component version, default is latest.", - "type": ["string", "number"] - }, - "contributor": { - "type": "string" - }, - "description": { - "type": "string" - } - }, - "required": ["name"] - }, - "prerequisite": { - "description": "The protocol for data, script, dockerimage, or output type.", - "type": "object", - "allOf": [ - { - "$ref": "#/definitions/basicSchema" - }, - { - "oneOf": [ - { - "$comment": "Script or output prerequisite.", - "properties": { - "type": { - "description": "Component type. Must be one of the following: data, script, dockerimage, or output. Prerequisites.type cannot be 'job'.", - "type": "string", - "enum": ["script", "output"] - }, - "uri": { - "description": "Only when the type is data can the uri be a list.", - "type": "string" - } - }, - "required": ["type", "uri"] - }, - { - "$comment": "Data prerequisite.", - "properties": { - "type": { - "description": "Component type. Must be one of the following: data, script, dockerimage, or output. Prerequisites.type cannot be 'job'.", - "type": "string", - "enum": ["data"] - }, - "uri": { - "description": "Only when the type is data can the uri be a list.", - "type": ["array"], - "items": { - "type": "string" - } - } - }, - "required": ["type", "uri"] - }, - { - "$comment": "Docker image prerequisite.", - "properties": { - "type": { - "description": "Component type. Must be one of the following: data, script, dockerimage, or output. Prerequisites.type cannot be 'job'.", - "type": "string", - "enum": ["dockerimage"] - }, - "auth": { - "description": "Only available when the type is dockerimage.", - "type": "object", - "properties": { - "username": { - "type": "string" - }, - "password": { - "description": "If a password is needed, it should be referenced as a secret.", - "type": "string" - }, - "registryuri": { - "type": "string" - } - } - }, - "uri": { - "description": "Only when the type is data can the uri be a list.", - "type": "string" - } - }, - "required": ["type", "uri"] - } - ] - } - ], - "required": ["name", "type", "uri"] - }, - "parameters": { - "description": "If specified, the whole parameters object can be referenced as `$parameters`. Scope of reference `$parameters`: the reference is shared among all task roles.", - "type": "object", - "additionalProperties": true - }, - "secrets": { - "description": "Can be referenced by `<% $secrets.secret1 %>`, `<% $secrets.secret2 %>`.", - "type": "object", - "additionalProperties": true - }, - "taskRole": { - "description": "Task roles are different types of task in the protocol. One job may have one or more task roles, each task role has one or more instances, and each instance runs inside one container.", - "type": "object", - "properties": { - "instances": { - "description": "Default is 1, instances of a taskRole, no less than 1.", - "type": "integer", - "minimum": 1 - }, - "completion": { - "description": "Completion poclicy for the job, https://github.com/Microsoft/pai/blob/master/subprojects/frameworklauncher/yarn/doc/USERMANUAL.md#ApplicationCompletionPolicy.", - "type": "object", - "properties": { - "minFailedInstances": { - "description": "Number of failed tasks to fail the entire job, null or no less than 1, if set to null means the job will always succeed regardless any task failure.", - "type": ["integer", "null"] - }, - "minSucceededInstances": { - "description": "Number of succeeded tasks to succeed the entire job, null or no less than 1, if set to null means the job will only succeed until all tasks are completed and minFailedInstances is not triggered.", - "type": ["integer", "null"] - }, - "additionalProperties": false - } - }, - "taskRetryCount": { - "type": "integer" - }, - "dockerImage": { - "description": "Should reference to a dockerimage defined in prerequisites.", - "type": "string" - }, - "data": { - "description": "Select data defined in prerequisites, target can be referenced as `$data` in this task role.", - "type": "string" - }, - "output": { - "description": "Select output defined in prerequisites, target can be referenced as `$output` in this task role.", - "type": "string" - }, - "script": { - "description": "Select script defined in prerequisites, target can be referenced as `$script` in this task role.", - "type": "string" - }, - "extraContainerOptions": { - "type": "object", - "properties": { - "shmMB": { - "description": "Config the /dev/shm in a docker container, https://docs.docker.com/compose/compose-file/#shm_size.", - "type": "integer" - } - }, - "additionalProperties": false - }, - "resourcePerInstance": { - "type": "object", - "properties": { - "cpu": { - "description": "CPU number, unit is CPU vcore.", - "type": "integer" - }, - "memoryMB": { - "description": "Memory number, unit is MB.", - "type": "integer" - }, - "gpu": { - "description": "GPU number, unit is GPU card.", - "type": "integer" - }, - "ports": { - "type": "object", - "patternProperties": { - "^[a-zA-Z_][a-zA-Z0-9_]*$": { - "type": "integer" - } - }, - "minProperties": 1 - } - }, - "additionalProperties": false, - "required": ["cpu", "memoryMB", "gpu"] - }, - "commands": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1 - } - }, - "additionalProperties": false, - "required": ["dockerImage", "resourcePerInstance", "commands"] - }, - "deployment": { - "type": "object", - "properties": { - "name": { - "description": "Should be in taskRoles.", - "type": "string" - }, - "taskRoles": { - "type": "object", - "patternProperties": { - "^[A-Za-z0-9._~]+$": { - "type": "object", - "properties": { - "preCommands": { - "description": "Execute before the taskRole's command.", - "type": "array", - "items": { - "type": "string" - } - }, - "postCommands": { - "description": "Execute after the taskRole's command", - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "minProperties": 1 - } - }, - "additionalProperties": false, - "required": ["name", "taskRoles"] - }, - "defaults": { - "description": "Default cluster specific settings.", - "type": "object", - "properties": { - "virtualCluster": { - "type": "string" - }, - "deployment": { - "description": "Should reference to deployment defined in deployments.", - "type": "string" - } - }, - "additionalProperties": false - }, - "extras": { - "description": "Extra field, object, save any information that plugin may use.", - "type": "object", - "properties": { - "submitFrom": { - "type": "string" - }, - "hivedscheduler": { - "type": "object", - "properties": { - "jobPriorityClass": { - "type": "string" - }, - "taskRoles": { - "type": "object", - "patternProperties": { - "^[A-Za-z0-9._~]+$": { - "type": "object", - "properties": { - "gpuType/reservationId": { - "type": "string" - }, - "affinityGroupName": { - "type": "string" - } - }, - "additionalProperties": true - } - } - } - }, - "required": ["jobPriorityClass"], - "additionalProperties": true - } - }, - "additionalProperties": true - } - }, - - "properties": { - "protocolVersion": { - "description": "Protocol version, current version is 2.", - "enum": ["2", 2] - }, - "name": { - "type": "string", - "pattern": "^[a-zA-Z0-9_-]+$" - }, - "type": { - "description": "Component type, should be 'job' here.", - "type": "string", - "enum": ["job"] - }, - "version": { - "description": "Component version, default is latest.", - "type": ["string", "number"] - }, - "contributor": { - "type": "string" - }, - "description": { - "type": "string" - }, - "prerequisites": { - "type": "array", - "items": { - "$ref": "#/definitions/prerequisite" - } - }, - "parameters": { - "$ref": "#/definitions/parameters" - }, - "secrets": { - "$ref": "#/definitions/secrets" - }, - "jobRetryCount": { - "type": "integer", - "minimum": 0 - }, - "taskRoles": { - "type": "object", - "patternProperties": { - "^[a-zA-Z_][a-zA-Z0-9_]*$": { - "$ref": "#/definitions/taskRole" - } - }, - "minProperties": 1 - }, - "deployments": { - "type": "array", - "items": { - "$ref": "#/definitions/deployment" - }, - "minItems": 1 - }, - "defaults": { - "$ref": "#/definitions/defaults" - }, - "extras": { - "$ref": "#/definitions/extras" - } - }, - "additionalProperties": false, - "required": ["protocolVersion", "name", "type", "taskRoles"] -} diff --git a/contrib/pai_vscode/snippets/job_config.yaml b/contrib/pai_vscode/snippets/job_config.yaml deleted file mode 100644 index c73aec9d1d..0000000000 --- a/contrib/pai_vscode/snippets/job_config.yaml +++ /dev/null @@ -1,13 +0,0 @@ -name: openPaiJobConfig -label: OpenPAI Job Config -documentation: OpenPAI Job Config -insertText: |2 - protocolVersion: 2 - name: ${1:} - type: job - prerequisites: - ${2:prerequisite} - taskRoles: - ${3:taskRole} - extras: - ${4:runtimePlugin} diff --git a/contrib/pai_vscode/snippets/job_deployment_taskRole.yaml b/contrib/pai_vscode/snippets/job_deployment_taskRole.yaml deleted file mode 100644 index e8b4fd8648..0000000000 --- a/contrib/pai_vscode/snippets/job_deployment_taskRole.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: deployment -label: OpenPAI Job deployment taskRole -documentation: OpenPAI Job deployment taskRole -insertText: |2 - ${1:}: - preCommands: - - ${2: - `; -} - -/** - * DCP - */ -@injectable() -export class DocumentContentProvider extends Singleton implements vscode.TextDocumentContentProvider { - public scheme: string = 'paiext'; - - constructor() { - super(); - this.context.subscriptions.push( - vscode.workspace.registerTextDocumentContentProvider(this.scheme, this) - ); - } - - public provideTextDocumentContent(uri: vscode.Uri): string { - const query: string = uri.query; - const target: string = querystring.parse(query).url; - - return generateHtml(target); - } -} - -export async function previewHtml(url: string, title: string): Promise { - url = Util.fixURL(url); - // vscode will decode the querystring when parse the url - // https://github.com/Microsoft/vscode/issues/53824 - // https://github.com/Microsoft/vscode/issues/32026 - void vscode.commands.executeCommand( - 'vscode.previewHtml', - `${(await getSingleton(DocumentContentProvider)).scheme}://authority/redirect?${encodeURIComponent(querystring.stringify({ - url: url - }))}`, - vscode.ViewColumn.Active, - title - ); -} - -export async function openInWebView(url: string, title: string): Promise { - url = Util.fixURL(url); - const panel: vscode.WebviewPanel = vscode.window.createWebviewPanel( - (await getSingleton(DocumentContentProvider)).scheme, - title, - vscode.ViewColumn.Active, - { enableScripts: true } - ); - panel.iconPath = vscode.Uri.file(Util.resolvePath(ICON_PAI.dark)); - panel.webview.html = generateHtml(url); -} diff --git a/contrib/pai_vscode/src/common/singleton.ts b/contrib/pai_vscode/src/common/singleton.ts deleted file mode 100644 index 58bd30f3f5..0000000000 --- a/contrib/pai_vscode/src/common/singleton.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import 'reflect-metadata'; // tslint:disable-line -import { injectable, Container } from 'inversify'; // tslint:disable-line -import * as vscode from 'vscode'; - -import { __ } from './i18n'; - -export const container: Container = new Container({ autoBindInjectable: true, defaultScope: 'Singleton' }); -export const EXTENSION_CONTEXT: symbol = Symbol('vscode.ExtensionContext'); - -type Constructor = new(...arg: any[]) => T; - -/** - * Singleton base class. - * Please implement constructor as simple as possible - only for injecting required components. - * Most initialization logic (including async initialization) should occur in onActivate. - */ -@injectable() -export abstract class Singleton { - protected context: vscode.ExtensionContext = container.get(EXTENSION_CONTEXT); - private activated: boolean = false; - - constructor() { - console.log(`Singleton ${this.constructor.name} constructed`); - container.bind(Singleton).toConstantValue(this); - } - - public onActivate?(): Promise | void; - public onDeactivate?(): Promise | void; - - public async ensureActivated(): Promise { - if (this.onActivate && !this.activated) { - this.activated = true; - await this.onActivate(); - console.log(`Singleton ${this.constructor.name} activated`); - } - return this; - } -} - -let getSingletonDisabled: boolean = false; -let initializationFinish: boolean = false; - -export function getSingleton(clazz: Constructor): Promise | T { - if (!container.isBound(clazz)) { - container.bind(clazz).toSelf(); - } - if (getSingletonDisabled) { - throw new Error('Getting async initialized Singleton in Singleton constructor is prohibited!'); - } - return container.get(clazz).ensureActivated(); -} - -export function bindExtensionContext(context: vscode.ExtensionContext): void { - container.bind(EXTENSION_CONTEXT).toConstantValue(context); -} - -export async function initializeAll(singletonClasses: Constructor[]): Promise { - getSingletonDisabled = true; - const allSingletons: Singleton[] = singletonClasses.map(clazz => container.get(clazz)); - getSingletonDisabled = false; - await Promise.all(allSingletons.map(singleton => singleton.ensureActivated())); - initializationFinish = true; -} - -export async function waitForAllSingletonFinish(): Promise { - while (!initializationFinish) { - await delay(10); - } -} - -export async function delay(ms: number): Promise { - // tslint:disable-next-line: no-unnecessary-callback-wrapper - await new Promise((something) => setTimeout(() => something(), ms)); -} - -export async function dispose(): Promise { - for (const singleton of container.getAll(Singleton)) { - if (singleton.onDeactivate) { - await singleton.onDeactivate(); - } - } -} diff --git a/contrib/pai_vscode/src/common/treeViewHelper.ts b/contrib/pai_vscode/src/common/treeViewHelper.ts deleted file mode 100644 index dc2e79c6a9..0000000000 --- a/contrib/pai_vscode/src/common/treeViewHelper.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { injectable } from 'inversify'; -import { isNil } from 'lodash'; -import { commands, workspace } from 'vscode'; - -import { COMMAND_TREEVIEW_DOUBLECLICK } from '../common/constants'; -import { __ } from '../common/i18n'; -import { Singleton } from '../common/singleton'; - -/** - * Supports double click commands for tree view - */ -@injectable() -export class TreeViewHelper extends Singleton { - - private lastClick?: { command: string, time: number }; - private readonly doubleClickInterval: number = 300; - - constructor() { - super(); - this.context.subscriptions.push( - commands.registerCommand(COMMAND_TREEVIEW_DOUBLECLICK, (command: string, ...args: string[]) => { - const mode: string | undefined = workspace.getConfiguration('workbench.list').get('openMode'); - if (mode === 'doubleClick') { - void commands.executeCommand(command, ...args); - } else { - // Single Click - if ( - !isNil(this.lastClick) && - this.lastClick.command === command && - Date.now() - this.lastClick.time < this.doubleClickInterval - ) { - this.lastClick = undefined; - void commands.executeCommand(command, ...args); - } else { - this.lastClick = { command, time: Date.now() }; - } - } - }) - ); - } -} diff --git a/contrib/pai_vscode/src/common/util.ts b/contrib/pai_vscode/src/common/util.ts deleted file mode 100644 index 18f5db3771..0000000000 --- a/contrib/pai_vscode/src/common/util.ts +++ /dev/null @@ -1,357 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as Ajv from 'ajv'; -import * as fs from 'fs-extra'; -import { injectable } from 'inversify'; -import { SchemaMetadataWriter } from 'json-inline-doc'; -import { dereference, JSONSchema } from 'json-schema-ref-parser'; -import { parse, visit } from 'jsonc-parser'; -import * as os from 'os'; -import * as path from 'path'; -import * as vscode from 'vscode'; - -import { __, I18nFormatFunction } from './i18n'; -import { Singleton } from './singleton'; - -import unixify = require('unixify'); // tslint:disable-line -import opn = require('opn'); // tslint:disable-line - -export let Util: UtilClass; - -/** - * Utility class - */ -@injectable() -export class UtilClass extends Singleton { - private static readonly TMP_DIR_PREFIX: string = 'paiext_'; - private static readonly SCHEMAS_DIR: string = './schemas/'; - - private readonly FINISH: string = __('common.finish'); - private readonly CANCEL: string = __('common.cancel'); - - private readonly ajv: Ajv.Ajv = new Ajv({ - loadSchema: this.loadSchema.bind(this) - }); - - private readonly schemaPromises: Map> = new Map(); - private readonly tempDirectories: Set = new Set(); - private lastJSONEditorPath: string | undefined; - - constructor() { - super(); - Util = this; - } - - public tuple(data: [T1, T2, T3, T4, T5]) : typeof data; - public tuple(data: [T1, T2, T3, T4]) : typeof data; - public tuple(data: [T1, T2, T3]) : typeof data; - public tuple(data: [T1, T2]) : typeof data; - // Forcing array literal as tuple type - public tuple(data: T[]): T[] { - return data; - } - - public resolvePath(filePath: string): string; - public resolvePath(filePath: { light: string, dark: string }): { light: string, dark: string }; - public resolvePath(filePath: string | { light: string, dark: string }): string | { light: string, dark: string }; - public resolvePath(filePath: string | { light: string, dark: string }): string | { light: string, dark: string } { - if (typeof filePath === 'string') { - if (!path.isAbsolute(filePath)) { - return path.join(this.context!.extensionPath, filePath); - } - return filePath; - } else { - return { - light: this.resolvePath(filePath.light), - dark: this.resolvePath(filePath.dark) - }; - } - } - - public uriPathAppend(base: vscode.Uri, suffix: string): vscode.Uri { - const lastChar: string = base.path[base.path.length - 1]; - if (lastChar !== '/' && lastChar !== '\\') { - suffix = '/' + suffix; - } - return vscode.Uri.parse(base.toString() + unixify(suffix)); - } - - public uriPathPop(base: vscode.Uri): vscode.Uri { - const newPath: string = path.dirname(base.path); - const originalStr: string = base.toString(); - - return vscode.Uri.parse(originalStr.substr(0, originalStr.length - base.path.length + newPath.length)); - } - - public info: I18nFormatFunction = function (this: UtilClass, ...args: any): string { - const result: string = __.apply(this, args); - void vscode.window.showInformationMessage(result); - return result; - }; - - public warn: I18nFormatFunction = function (this: UtilClass, ...args: any): string { - const result: string = __.apply(this, args); - void vscode.window.showWarningMessage(result); - return result; - }; - - // Call this function with the same argument as you do with __ function to show an error message - public err: I18nFormatFunction = function (this: UtilClass, ...args: any): string { - const result: string = __.apply(this, args); - void vscode.window.showErrorMessage(result); - return result; - }; - - public async getNewTempDirectory(): Promise { - const p: string = await fs.mkdtemp(path.join(os.tmpdir(), UtilClass.TMP_DIR_PREFIX)); - this.tempDirectories.add(p); - return p; - } - - public async cleanTempDirectory(tempPath: string): Promise { - this.tempDirectories.delete(tempPath); - try { - // Remove the temp directory and remaining file - await fs.remove(tempPath); - } catch { } - } - - public async generateCommentedJSON(obj: any, schemaFile: string): Promise { - const schema: JSONSchema = await dereference(this.schemaPath(schemaFile)); - const writer: SchemaMetadataWriter = new SchemaMetadataWriter( - schema, - s => s.description ? { type: 'line', content: s.description } : undefined - ); - return writer.stringify(obj, null, 4); - } - - public getPositionByOffset(content: string, offset: number): vscode.Position { - let line: number = 0; - let char: number = 0; - for (let i: number = 0; i < offset; i++) { - if (content[i] === '\n') { - line++; - char = 0; - } else { - char++; - } - } - return new vscode.Position(line, char); - } - - public getJsonValueSelection(content: string, keyToSelect: string): vscode.Selection | undefined { - // Select value of keyToSelect in json object - let keyEncountered: boolean = false; - let selection: vscode.Selection | undefined; - visit(content, { - onObjectProperty(property: string, offset: number, length: number): void { - if (property === keyToSelect) { - keyEncountered = true; - } - }, - onLiteralValue(value: any, offset: number, length: number): void { - if (keyEncountered && content) { - keyEncountered = false; - const pos: vscode.Position = Util.getPositionByOffset(content, offset); - selection = new vscode.Selection( - pos.translate(0, 1), - pos.translate(0, length - 1) - ); - } - } - }); - return selection; - } - - public getRandomString(length: number = 10): string { - // tslint:disable-next-line: insecure-random - return Math.random().toString(36).substring(2, length + 2); - } - - public async createTemporaryFile(fileName: string): Promise { - const folderName: string = this.getRandomString(12); - const filePath: string = path.join(os.tmpdir(), folderName, fileName); - await fs.ensureFile(filePath); - return filePath; - } - - public async editJSON(obj: T, fileName: string, schemaFile?: string, highlightKey?: string): Promise { - const tempPath: string = await this.getNewTempDirectory(); - let filePath: string = path.join(tempPath, fileName.replace(/\//g, '')); - - // If the active editor is another editJSON session editor... - if (vscode.window.activeTextEditor && - vscode.window.activeTextEditor.document.fileName === this.lastJSONEditorPath) { - - // Try to close the last temporary editor - vscode doesn't provide close editor API so do it hacky way - await vscode.window.activeTextEditor.document.save(); - - // Check again in case the editor is not the original one - if (vscode.window.activeTextEditor && - vscode.window.activeTextEditor.document.fileName === this.lastJSONEditorPath) { - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - } - void vscode.window.showWarningMessage(__('util.editjson.previousexpired')); - } - let content: string | undefined; - if (schemaFile) { - if (!filePath.endsWith('.jsonc')) { - filePath += 'c'; - } - content = await this.generateCommentedJSON(obj, schemaFile); - } else { - content = JSON.stringify(obj, null, 4); - } - await fs.writeFile(filePath, content); - - const jsonShowOptions: vscode.TextDocumentShowOptions = { preview: false }; - if (highlightKey && content) { - const selection: vscode.Selection | undefined = this.getJsonValueSelection(content, highlightKey); - if (selection) { - jsonShowOptions.selection = selection; - } - } - const editor: vscode.TextEditor = await vscode.window.showTextDocument(vscode.Uri.file(filePath), jsonShowOptions); - - // DO NOT use filePath - there may be difference with drive letters ('C' vs 'c') - this.lastJSONEditorPath = editor.document.fileName; - - try { - while (true) { - // Only error message won't be collapsed automatically by vscode. - const result: string | undefined = await vscode.window.showErrorMessage( - __('util.editjson.prompt'), - this.FINISH, this.CANCEL - ); - if (result === this.FINISH) { - await editor.document.save(); - try { - const editedObject: T & object = parse(editor.document.getText()); - if (schemaFile) { - const error: string | undefined = await this.validateJSON(editedObject, schemaFile); - if (error) { - this.err('util.editjson.validationerror', [error]); - continue; - } - } - return editedObject; - } catch (ex) { - this.err('util.editjson.parseerror', [ex]); - continue; - } - } - return; - } - } finally { - // Try to close the temporary editor - vscode doesn't provide close editor API so do it hacky way - // Note: The editor may have already been closed, either by user or by another editJSON session. - if (vscode.window.activeTextEditor === editor) { - await editor.document.save(); - - // Check again in case the editor is not the original one - if (vscode.window.activeTextEditor === editor) { - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - } - } - await this.cleanTempDirectory(tempPath); - } - } - - public async validateJSON(obj: object, schemaFile: string): Promise { - let validateFnPromise: PromiseLike; - if (!this.schemaPromises.has(schemaFile)) { - const schemaJSON: object = await this.loadSchema(schemaFile); - validateFnPromise = this.ajv.compileAsync(schemaJSON); - this.schemaPromises.set(schemaFile, validateFnPromise); - } else { - validateFnPromise = this.schemaPromises.get(schemaFile)!; - } - const validateFn: Ajv.ValidateFunction = await validateFnPromise; - if (validateFn(obj)) { - return undefined; - } - if (!validateFn.errors) { - return __('util.validatejson.error'); - } - const error: Ajv.ErrorObject = validateFn.errors[0]; - return `${error.dataPath}: ${error.message}`; - } - - public async pick(options: T[], placeHolder: string): Promise; - public async pick(options: T[], placeHolder: string, fnToQuickPickItem: (obj: T) => vscode.QuickPickItem): Promise; - public async pick(options: T[], placeHolder: string, fnToQuickPickItem?: (obj: T) => vscode.QuickPickItem): Promise { - type IQuickPickItemInternal = vscode.QuickPickItem & { index?: number }; - const items: IQuickPickItemInternal[] = options.map((obj, index) => { - const result: IQuickPickItemInternal = fnToQuickPickItem ? fnToQuickPickItem(obj) : obj; - result.index = index; - return result; - }); - const pickResult: IQuickPickItemInternal | undefined = await vscode.window.showQuickPick(items, { - matchOnDescription: true, matchOnDetail: true, placeHolder - }); - if (!pickResult) { - return undefined; - } else { - return options[pickResult.index || 0]; - } - } - - public async pickWorkspaceFolder(): Promise { - let folders: vscode.WorkspaceFolder[] | undefined = vscode.workspace.workspaceFolders; - if (!folders) { - throw new Error(__('common.workspace.nofolder')); - } - folders = folders.filter(folder => folder.uri.scheme === 'file'); - if (folders.length === 0) { - throw new Error(__('common.workspace.nofolder')); - } - if (folders.length === 1) { - return folders[0]; - } - - return this.pick(folders, __('common.workspace.pick'), folder => ({ - label: folder.name, - detail: folder.uri.fsPath - })); - } - - public fixURL(url: string, https?: boolean): string { - if (!/^[a-zA-Z]+?\:\/\//.test(url)) { - url = `http${https ? 's' : ''}://` + url; - } - return url; - } - - public async openExternally(url: string): Promise { - url = Util.fixURL(url); - try { - await opn(url, { wait: false }); - } catch (ex) { - void vscode.window.showWarningMessage(__('util.openexternally.fail', [ex])); - } - } - - public quote(p: string): string { - if (os.platform() === 'win32') { - return `"${p}"`; - } else { - return `'${p}'`; - } - } - - public async onDeactivate(): Promise { - await Promise.all(Array.from(this.tempDirectories).map(dir => fs.remove(dir))); - } - - private schemaPath(uri: string): string { - return this.resolvePath(path.join(UtilClass.SCHEMAS_DIR, uri)); - } - - private loadSchema(uri: string): Promise { - return fs.readJSON(this.schemaPath(uri)); - } -} diff --git a/contrib/pai_vscode/src/extension.ts b/contrib/pai_vscode/src/extension.ts deleted file mode 100644 index f2e7e74d39..0000000000 --- a/contrib/pai_vscode/src/extension.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as vscode from 'vscode'; - -import * as Singleton from './common/singleton'; -import { allSingletonClasses } from './root'; - -export async function activate(context: vscode.ExtensionContext): Promise { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; - Singleton.bindExtensionContext(context); - await Singleton.initializeAll(allSingletonClasses); -} - -export async function deactivate(): Promise { - await Singleton.dispose(); -} diff --git a/contrib/pai_vscode/src/global.d.ts b/contrib/pai_vscode/src/global.d.ts deleted file mode 100644 index 65a11339d0..0000000000 --- a/contrib/pai_vscode/src/global.d.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Make tsc happy. Especially with libs without type definitions. -declare module 'webhdfs'; -declare module 'unixify' { - declare function unixify(filepath: string, stripTrailingSlash?: boolean): string; - export = unixify; -} -declare module 'streamifier'; -declare module 'node-yaml-parser' { - export function parse(text: string): { readonly documents: YamlDocument[]; readonly lineLengths: number[] }; - - export interface YamlNode { - readonly kind: string; - readonly raw: string; - readonly startPosition: number; - readonly endPosition: number; - readonly parent?: any; - readonly mappings: any[]; - } - - export interface YamlDocument { - readonly nodes: YamlNode[]; - readonly errors: string[]; - } - - export interface Util { - isKey(node: YamlNode): boolean; - isMapping(node: YamlNode): boolean; - } - - export const util: Util; -} diff --git a/contrib/pai_vscode/src/pai/clusterManager.ts b/contrib/pai_vscode/src/pai/clusterManager.ts deleted file mode 100644 index c8cb3031c3..0000000000 --- a/contrib/pai_vscode/src/pai/clusterManager.ts +++ /dev/null @@ -1,359 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { injectable } from 'inversify'; -import { clone, range } from 'lodash'; -import { IAuthnInfo, ILoginInfo, OpenPAIClient } from 'openpai-js-sdk'; -import * as request from 'request-promise-native'; -import * as vscode from 'vscode'; - -import { - COMMAND_ADD_CLUSTER, COMMAND_DELETE_CLUSTER, COMMAND_EDIT_CLUSTER} from '../common/constants'; -import { __ } from '../common/i18n'; -import { getSingleton, Singleton } from '../common/singleton'; -import { Util } from '../common/util'; - -import { ClusterExplorerChildNode, ConfigurationTreeDataProvider, ITreeData } from './container/configurationTreeDataProvider'; -import { IPAICluster } from './utility/paiInterface'; - -import semverCompare = require('semver-compare'); // tslint:disable-line -import { login } from './utility/azureADLogin'; - -export interface IConfiguration { - readonly version: string; - pais: IPAICluster[]; -} - -export type IClusterModification = { - index: number; - type: 'EDIT' | 'REMOVE'; -} | { - type: 'RESET'; -}; - -export function getClusterIdentifier(conf: IPAICluster): string { - return `${conf.username}@${conf.rest_server_uri}`; -} - -export function getClusterName(conf: IPAICluster): string { - return conf.name || getClusterIdentifier(conf); -} - -/** - * Manager class for cluster configurations - */ -@injectable() -export class ClusterManager extends Singleton { - private static readonly CONF_KEY: string = 'openpai.conf'; - private static readonly default: IConfiguration = { - version: '0.0.1', - pais: [] - }; - private static readonly paiDefault: IPAICluster = { - name: 'Sample Cluster', - username: '', - password: '', - token: '', - https: true, - rest_server_uri: '127.0.0.1:9186', - hdfs_uri: 'hdfs://127.0.0.1:9000', - webhdfs_uri: '127.0.0.1:50070', - grafana_uri: '127.0.0.1:3000', - k8s_dashboard_uri: '127.0.0.1:9090', - protocol_version: '2' - }; - - private onDidChangeEmitter: vscode.EventEmitter = new vscode.EventEmitter(); - public onDidChange: vscode.Event = this.onDidChangeEmitter.event; // tslint:disable-line - - private readonly EDIT: string = __('common.edit'); - private readonly DISCARD: string = __('cluster.activate.fix.discard'); - - private configuration: IConfiguration | undefined; - - public get allConfigurations(): IPAICluster[] { - return this.configuration!.pais; - } - - public async onActivate(): Promise { - this.context.subscriptions.push( - vscode.commands.registerCommand(COMMAND_ADD_CLUSTER, () => this.add()), - vscode.commands.registerCommand(COMMAND_EDIT_CLUSTER, async (node: ClusterExplorerChildNode | ITreeData) => { - if (node instanceof ClusterExplorerChildNode) { - await this.edit(node.index); - } else { - await this.edit(node.clusterIndex); - } - }), - vscode.commands.registerCommand(COMMAND_DELETE_CLUSTER, async (node: ClusterExplorerChildNode | ITreeData) => { - if (node instanceof ClusterExplorerChildNode) { - await this.delete(node.index); - } else { - await this.delete(node.clusterIndex); - } - }) - ); - this.configuration = this.context.globalState.get(ClusterManager.CONF_KEY) || ClusterManager.default; - try { - await this.validateConfiguration(); - await this.ensureProtocolVersion(); - } catch (ex) { - await this.askConfigurationFix(__('cluster.activate.error', [ex])); - } - } - - public async validateConfiguration(): Promise { - if (semverCompare(ClusterManager.default.version, this.configuration!.version) < 0) { - throw __('cluster.version.warning'); - } - const validateResult: string | undefined = await Util.validateJSON(this.configuration!, 'pai_configuration.schema.json'); - if (validateResult) { - throw validateResult; - } - } - - public async ensureProtocolVersion(): Promise { - let updated: Boolean = true; - const list: Promise[] = []; - this.configuration!.pais.forEach((config: IPAICluster, i, pais) => { - if (!config.protocol_version) { - updated = true; - const url: string = Util.fixURL(`${config.rest_server_uri}/api/v2/jobs/protocolversion/config`, config.https); - list.push(request - .get(url, { timeout: 5 * 1000 }) - .then(() => { - pais[i].protocol_version = '2'; - }) - .catch((err) => { - const error: any = JSON.parse(err.error); - if (error.code === 'NoApiError') { - pais[i].protocol_version = '1'; - } else { - pais[i].protocol_version = '2'; - } - })); - } - }); - - if (updated) { - await Promise.all(list).then(async () => await this.save()); - } - } - - public async autoAddOIDCUserInfo(cluster: IPAICluster): Promise { - try { - const client: OpenPAIClient = new OpenPAIClient({ - rest_server_uri: Util.fixURL(cluster.rest_server_uri, cluster.https) - }); - - const authnInfo: IAuthnInfo = await client.authn.info(); - - if (authnInfo.authn_type === 'OIDC') { - const loginInfo: ILoginInfo = await login( - `https://${cluster.rest_server_uri}`, - `https://${cluster.web_portal_uri}`, - async () => { - const response: string | undefined = await vscode.window.showInformationMessage( - // tslint:disable-next-line: no-multiline-string - __('cluster.login.timeout'), - __('cluster.login.openPortal')); - if (response) { - cluster.username = ''; - cluster.token = ''; - cluster.password = undefined; - await Util.openExternally(cluster.web_portal_uri!); - } - } - ); - - let clusterToken: string = loginInfo.token; - - try { - const response: any = await client.authn.createApplicationToken(clusterToken); - clusterToken = response.token; - } catch (error) { - console.log('Get application token fail, use user token.'); - } - - cluster.username = loginInfo.user; - cluster.token = clusterToken; - cluster.password = undefined; - } - } catch (ex) { - cluster.token = ''; - } - } - - public async add(): Promise { - const host: string | undefined = await vscode.window.showInputBox({ - prompt: __('cluster.add.host.prompt'), - validateInput: (val: string): string => { - if (!val) { - return __('cluster.add.host.empty'); - } - if (val.includes('/')) { - return __('cluster.add.host.invalidchar'); - } - return ''; - } - }); - if (!host) { - return; - } - - let pylonReady: boolean = true; - - const cluster: IPAICluster = clone(ClusterManager.paiDefault); - try { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: __('cluster.add.checkstatus'), - cancellable: true - }, - (progress, cancellationToken) => new Promise((resolve, reject) => { - const req: request.RequestPromise = request.get(Util.fixURL(`${host}/healthz`, cluster.https), { timeout: 5 * 1000 }); - cancellationToken.onCancellationRequested(() => { - req.abort(); - reject(); - }); - req.then(resolve).catch(() => { - const httpReq: request.RequestPromise = request.get(Util.fixURL(`${host}/healthz`, false), { timeout: 5 * 1000 }); - cancellationToken.onCancellationRequested(() => { - httpReq.abort(); - reject(); - }); - httpReq.then(() => { cluster.https = false; resolve(); }).catch(reject); - }); - })); - } catch (err) { - pylonReady = false; - } - await this.setClusterDefaultProperty(cluster, host, pylonReady); - - // Config the protocol version. - try { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: __('cluster.add.checkprotocolversion'), - cancellable: true - }, - (_progress, cancellationToken) => new Promise((resolve, reject) => { - const req: request.RequestPromise = request - .get(Util.fixURL(`${cluster.rest_server_uri}/api/v2/jobs/protocolversion/config`, cluster.https), - { timeout: 5 * 1000 }); - cancellationToken.onCancellationRequested(() => { - req.abort(); - reject(); - }); - req.then(resolve).catch(reject); - })); - cluster.protocol_version = '2'; - } catch (exception) { - try { - const error: any = JSON.parse(exception.error); - if (error.code === 'NoApiError') { - cluster.protocol_version = '1'; - } else { - cluster.protocol_version = '2'; - } - } catch (err) { - cluster.protocol_version = '2'; - } - } - - return this.edit(this.allConfigurations.length, cluster); - } - - public async setClusterDefaultProperty(cluster: IPAICluster, host: string, pylonReady: boolean): Promise { - if (pylonReady) { - cluster.name = host; - cluster.rest_server_uri = `${host}/rest-server`; - cluster.k8s_dashboard_uri = `${host}/kubernetes-dashboard`; - cluster.grafana_uri = `${host}/grafana`; - cluster.web_portal_uri = `${host}`; - cluster.hdfs_uri = `hdfs://${host}:9000`; - cluster.webhdfs_uri = `${host}/webhdfs/api/v1`; - } else { - cluster.name = host; - cluster.rest_server_uri = `${host}:9186`; - cluster.webhdfs_uri = `${host}:50070/webhdfs/v1`; - cluster.grafana_uri = `${host}:3000`; - cluster.web_portal_uri = `${host}`; - cluster.hdfs_uri = `hdfs://${host}:9000`; - cluster.k8s_dashboard_uri = `${host}:9090`; - } - await this.autoAddOIDCUserInfo(cluster); - } - - public async edit(index: number, defaultConfiguration: IPAICluster = ClusterManager.paiDefault): Promise { - const original: IPAICluster = this.allConfigurations[index] || defaultConfiguration; - const editResult: IPAICluster | undefined = await Util.editJSON( - original, - `pai_cluster_${original.rest_server_uri}.json`, - 'pai_cluster.schema.json' - ); - if (editResult) { - this.allConfigurations[index] = editResult; - this.onDidChangeEmitter.fire({ type: 'EDIT', index }); - await this.save(); - } - } - - public async delete(index: number): Promise { - this.allConfigurations.splice(index, 1); - this.onDidChangeEmitter.fire({ type: 'REMOVE', index }); - await this.save(); - } - - public async save(): Promise { - await this.context.globalState.update(ClusterManager.CONF_KEY, this.configuration!); - await (await getSingleton(ConfigurationTreeDataProvider)).refresh(); - } - - public async pick(): Promise { - if (this.allConfigurations.length === 1) { - return 0; - } - return await Util.pick(range(this.allConfigurations.length), __('cluster.pick.prompt'), (index: number) => { - const conf: IPAICluster = this.allConfigurations[index]; - return { - label: getClusterName(conf), - detail: getClusterIdentifier(conf) - }; - }); - } - - private async askConfigurationFix(prompt: string): Promise { - const previousConfigurations: IConfiguration = this.configuration!; - this.configuration = ClusterManager.default; - const result: string | undefined = await vscode.window.showWarningMessage( - prompt, - this.EDIT, - this.DISCARD - ); - switch (result) { - case this.EDIT: - const editResult: IConfiguration | undefined = await Util.editJSON( - previousConfigurations, - 'pai_full_configuration.json', - 'pai_configuration.schema.json' - ); - if (editResult) { - this.configuration = editResult; - await this.save(); - } - break; - - case this.DISCARD: - case undefined: - default: - break; - } - this.onDidChangeEmitter.fire({ type: 'RESET' }); - } -} diff --git a/contrib/pai_vscode/src/pai/container/common/clusterNode.ts b/contrib/pai_vscode/src/pai/container/common/clusterNode.ts deleted file mode 100644 index 84be4ba1fc..0000000000 --- a/contrib/pai_vscode/src/pai/container/common/clusterNode.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { TreeItem, TreeItemCollapsibleState} from 'vscode'; - -import { - CONTEXT_JOBLIST_CLUSTER, - ICON_PAI -} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; -import { Util } from '../../../common/util'; -import { getClusterName } from '../../clusterManager'; -import { IPAICluster } from '../../utility/paiInterface'; - -/** - * Root node representing PAI cluster - */ -export class ClusterNode extends TreeItem { - public readonly index: number; - public constructor(configuration: IPAICluster, index: number) { - super(getClusterName(configuration), TreeItemCollapsibleState.Collapsed); - this.index = index; - this.iconPath = Util.resolvePath(ICON_PAI); - this.contextValue = CONTEXT_JOBLIST_CLUSTER; - } -} diff --git a/contrib/pai_vscode/src/pai/container/common/fileNode.ts b/contrib/pai_vscode/src/pai/container/common/fileNode.ts deleted file mode 100644 index f59077e035..0000000000 --- a/contrib/pai_vscode/src/pai/container/common/fileNode.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { - TreeItemCollapsibleState, - Uri -} from 'vscode'; - -import { - CONTEXT_HDFS_FILE -} from '../../../common/constants'; - -import { TreeNode } from './treeNode'; - -/** - * File node - */ -export class FileNode extends TreeNode { - public readonly contextValue: string = CONTEXT_HDFS_FILE; - constructor(uri: Uri, parent: TreeNode) { - super(uri, TreeItemCollapsibleState.None); - this.parent = parent; - } -} diff --git a/contrib/pai_vscode/src/pai/container/common/folderNode.ts b/contrib/pai_vscode/src/pai/container/common/folderNode.ts deleted file mode 100644 index 3cee585e01..0000000000 --- a/contrib/pai_vscode/src/pai/container/common/folderNode.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { - TreeItemCollapsibleState, - Uri -} from 'vscode'; - -import { - CONTEXT_HDFS_FOLDER -} from '../../../common/constants'; - -import { TreeNode } from './treeNode'; - -/** - * Folder node - */ -export class FolderNode extends TreeNode { - public readonly contextValue: string = CONTEXT_HDFS_FOLDER; - constructor(uri: Uri, parent: TreeNode) { - super(uri, TreeItemCollapsibleState.Collapsed); - this.parent = parent; - } -} diff --git a/contrib/pai_vscode/src/pai/container/common/rootNode.ts b/contrib/pai_vscode/src/pai/container/common/rootNode.ts deleted file mode 100644 index 7e444ce20f..0000000000 --- a/contrib/pai_vscode/src/pai/container/common/rootNode.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { - TreeItemCollapsibleState, - Uri -} from 'vscode'; - -import { - COMMAND_OPEN_HDFS, - COMMAND_TREEVIEW_DOUBLECLICK, - CONTEXT_HDFS_ROOT, - CONTEXT_HDFS_SELECT_CLUSTER, - CONTEXT_HDFS_SELECT_CLUSTER_ROOT, - ICON_PAI -} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; -import { Util } from '../../../common/util'; -import { getClusterName } from '../../clusterManager'; -import { IPAICluster } from '../../utility/paiInterface'; - -import { TreeNode } from './treeNode'; - -/** - * Root node - */ -export class RootNode extends TreeNode { - public readonly contextValue: string = CONTEXT_HDFS_ROOT; - constructor(uri: Uri) { - super(uri, TreeItemCollapsibleState.Expanded); - this.label = uri.toString(); - this.iconPath = Util.resolvePath(ICON_PAI); - } -} - -/** - * Cluster root node - */ -export class SelectClusterRootNode extends TreeNode { - public readonly contextValue: string = CONTEXT_HDFS_SELECT_CLUSTER_ROOT; - constructor() { - super(__('treeview.hdfs.select-cluster.label'), TreeItemCollapsibleState.Expanded); - } -} - -/** - * Cluster node (when no cluster is selected) - */ -export class SelectClusterNode extends TreeNode { - public readonly contextValue: string = CONTEXT_HDFS_SELECT_CLUSTER; - public readonly cluster: IPAICluster; - constructor(cluster: IPAICluster, parent: TreeNode) { - super(getClusterName(cluster)); - this.cluster = cluster; - this.parent = parent; - this.command = { - title: __('treeview.node.openhdfs'), - command: COMMAND_TREEVIEW_DOUBLECLICK, - arguments: [COMMAND_OPEN_HDFS, this.cluster] - }; - this.iconPath = Util.resolvePath(ICON_PAI); - } -} diff --git a/contrib/pai_vscode/src/pai/container/common/treeDataEnum.ts b/contrib/pai_vscode/src/pai/container/common/treeDataEnum.ts deleted file mode 100644 index 4a18df9336..0000000000 --- a/contrib/pai_vscode/src/pai/container/common/treeDataEnum.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -export enum TreeDataType { - Cluster = 0, - Filter = 1, - Job = 2, - More = 3 -} - -export enum FilterType { - Recent = 0, - All = 1 -} - -export enum LoadingState { - Finished = 0, - Loading = 1, - Error = 2 -} diff --git a/contrib/pai_vscode/src/pai/container/common/treeNode.ts b/contrib/pai_vscode/src/pai/container/common/treeNode.ts deleted file mode 100644 index a404353d77..0000000000 --- a/contrib/pai_vscode/src/pai/container/common/treeNode.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { - TreeItem, TreeItemCollapsibleState, Uri -} from 'vscode'; - -import { - COMMAND_TREEVIEW_DOUBLECLICK, - COMMAND_TREEVIEW_LOAD_MORE, - CONTEXT_STORAGE_LOAD_MORE -} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; - -/** - * Abstract tree node - */ -export abstract class TreeNode extends TreeItem { - public parent?: TreeNode; -} - -/** - * Storage tree node base - */ -export class StorageTreeNode extends TreeNode { - public static pageSize: number = 10; - public parent?: StorageTreeNode; - - protected children: StorageTreeNode[] = []; - protected initialized: boolean = false; - - constructor(label: string, parent?: StorageTreeNode, collapsibleState?: TreeItemCollapsibleState) { - super(label, collapsibleState); - this.parent = parent; - } - - /** - * Get children - */ - public async getChildren(): Promise { - if (!this.initialized) { - await this.refresh(); - this.initialized = true; - } - return this.children; - } - - /** - * Get parent - */ - public getParent(): StorageTreeNode | undefined { - return this.parent; - } - - /** - * Refresh - */ - public async refresh(): Promise { - console.log('Refresh not implemented.'); - } - - /** - * Load more items - */ - public async loadMore(): Promise { - console.log('Load more not implemented.'); - } - - /** - * Create folder - */ - public async createFolder(folder?: string): Promise { - console.log('Create folder not implemented.'); - } - - /** - * Upload file - */ - public async uploadFile(file?: Uri[]): Promise { - console.log('Upload file not implemented.'); - } - - /** - * Upload folder - */ - public async uploadFolder(): Promise { - console.log('Upload folder not implemented.'); - } - - /** - * Download - */ - public async download(dest?: Uri): Promise { - console.log('Download not implemented.'); - } - - /** - * Delete - */ - public async delete(): Promise { - console.log('Delete not implemented.'); - } - - /** - * Open file - */ - public async openFile(): Promise { - console.log('Open file not implemented.'); - } -} - -/** - * Storage load more tree node. - */ -export class StorageLoadMoreTreeNode extends StorageTreeNode { - public contextValue: string = CONTEXT_STORAGE_LOAD_MORE; - - constructor(parent: StorageTreeNode) { - super('Load More...', parent, TreeItemCollapsibleState.None); - this.command = { - title: __('treeview.node.storage.load-more'), - command: COMMAND_TREEVIEW_DOUBLECLICK, - arguments: [COMMAND_TREEVIEW_LOAD_MORE, this.parent] - }; - } -} diff --git a/contrib/pai_vscode/src/pai/container/configurationTreeDataProvider.ts b/contrib/pai_vscode/src/pai/container/configurationTreeDataProvider.ts deleted file mode 100644 index 919a9d0fcd..0000000000 --- a/contrib/pai_vscode/src/pai/container/configurationTreeDataProvider.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { injectable } from 'inversify'; -import { - commands, window, - Event, EventEmitter, TreeDataProvider, TreeItem, TreeItemCollapsibleState -} from 'vscode'; - -import { - COMMAND_CREATE_JOB_CONFIG, COMMAND_EDIT_CLUSTER, COMMAND_LIST_JOB, COMMAND_OPEN_STORAGE, - COMMAND_REFRESH_CLUSTER, COMMAND_SIMULATE_JOB, COMMAND_SUBMIT_JOB, - COMMAND_TREEVIEW_DOUBLECLICK, COMMAND_TREEVIEW_OPEN_PORTAL, - CONTEXT_CONFIGURATION_ITEM, - ICON_CREATE_CONFIG, ICON_DASHBOARD, ICON_EDIT, ICON_HDFS, ICON_LIST_JOB, ICON_PAI, ICON_SIMULATE_JOB, ICON_SUBMIT_JOB, - VIEW_CONFIGURATION_TREE -} from '../../common/constants'; -import { __ } from '../../common/i18n'; -import { getSingleton, Singleton } from '../../common/singleton'; -import { Util } from '../../common/util'; -import { getClusterName, ClusterManager } from '../clusterManager'; -import { IPAICluster } from '../utility/paiInterface'; - -interface IChildNodeDefinition { - title: string; - command: string; - icon: string | { light: string, dark: string }; - condition?(conf: IPAICluster): boolean; -} - -const childNodeDefinitions: IChildNodeDefinition[] = [ - { - title: 'treeview.node.openPortal', - command: COMMAND_TREEVIEW_OPEN_PORTAL, - icon: ICON_DASHBOARD - }, - { - title: 'treeview.node.listjob', - command: COMMAND_LIST_JOB, - icon: ICON_LIST_JOB - }, - { - title: 'treeview.node.create-config', - command: COMMAND_CREATE_JOB_CONFIG, - icon: ICON_CREATE_CONFIG - }, - { - title: 'treeview.node.submitjob', - command: COMMAND_SUBMIT_JOB, - icon: ICON_SUBMIT_JOB - }, - { - title: 'treeview.node.simulate', - command: COMMAND_SIMULATE_JOB, - icon: ICON_SIMULATE_JOB - }, - { - title: 'treeview.node.edit', - command: COMMAND_EDIT_CLUSTER, - icon: ICON_EDIT - }, - { - title: 'treeview.node.storage', - command: COMMAND_OPEN_STORAGE, - icon: ICON_HDFS, - condition: (conf: IPAICluster): boolean => !!conf.webhdfs_uri - } -]; - -export interface ITreeData { - clusterIndex: number; - childDef?: IChildNodeDefinition; -} - -/** - * Root nodes representing cluster configuration - */ -export class ClusterExplorerRootNode extends TreeItem { - public readonly index: number; - - public constructor(configuration: IPAICluster, index: number) { - super(getClusterName(configuration), TreeItemCollapsibleState.Expanded); - this.iconPath = Util.resolvePath(ICON_PAI); - this.index = index; - this.contextValue = CONTEXT_CONFIGURATION_ITEM; - } -} - -/** - * Child nodes representing operation - */ -export class ClusterExplorerChildNode extends TreeItem { - public readonly index: number; - - public constructor(clusterIndex: number, def: IChildNodeDefinition) { - super(__(def.title), TreeItemCollapsibleState.None); - this.iconPath = Util.resolvePath(def.icon); - this.index = clusterIndex; - this.command = { - title: __(def.title), - command: COMMAND_TREEVIEW_DOUBLECLICK, - arguments: [def.command, this] - }; - } -} - -/** - * Contributes to the tree view of cluster configurations - */ -@injectable() -export class ConfigurationTreeDataProvider extends Singleton implements TreeDataProvider { - public onDidChangeTreeData: Event; - private onDidChangeTreeDataEmitter: EventEmitter; - - constructor() { - super(); - this.onDidChangeTreeDataEmitter = new EventEmitter(); - this.onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event; - } - - public async refresh(): Promise { - this.onDidChangeTreeDataEmitter.fire(); - } - - public async getTreeItem(data: ITreeData): Promise { - if (!data.childDef) { - const cluster: IPAICluster = (await getSingleton(ClusterManager)).allConfigurations[data.clusterIndex]; - return new ClusterExplorerRootNode(cluster, data.clusterIndex); - } else { - return new ClusterExplorerChildNode(data.clusterIndex, data.childDef); - } - } - - public async getChildren(data?: ITreeData): Promise { - if (!data) { - const allConfigurations: IPAICluster[] = (await getSingleton(ClusterManager)).allConfigurations; - return allConfigurations.map((c, i) => ({ - clusterIndex: i - })); - } else if (!data.childDef) { - const cluster: IPAICluster = (await getSingleton(ClusterManager)).allConfigurations[data.clusterIndex]; - return childNodeDefinitions.filter((def) => !def.condition || def.condition(cluster)).map(def => ({ - clusterIndex: data.clusterIndex, - childDef: def - })); - } else { - return undefined; - } - } - - public getParent(data: ITreeData): ITreeData | undefined { - if (data.childDef) { - return { - clusterIndex: data.clusterIndex - }; - } else { - return undefined; - } - } - - public async onActivate(): Promise { - this.context.subscriptions.push( - commands.registerCommand(COMMAND_REFRESH_CLUSTER, () => this.refresh()), - window.registerTreeDataProvider(VIEW_CONFIGURATION_TREE, this) - ); - } -} diff --git a/contrib/pai_vscode/src/pai/container/hdfsTreeView.ts b/contrib/pai_vscode/src/pai/container/hdfsTreeView.ts deleted file mode 100644 index fda19f79fd..0000000000 --- a/contrib/pai_vscode/src/pai/container/hdfsTreeView.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { injectable } from 'inversify'; -import { - commands, window, Event, EventEmitter, FileType, - TreeDataProvider, TreeItem, TreeView, Uri -} from 'vscode'; - -import { - COMMAND_CONTAINER_HDFS_BACK, COMMAND_CONTAINER_HDFS_DELETE, COMMAND_CONTAINER_HDFS_MKDIR, COMMAND_CONTAINER_HDFS_REFRESH, - CONTEXT_HDFS_FOLDER, CONTEXT_HDFS_ROOT, CONTEXT_HDFS_SELECT_CLUSTER_ROOT, - VIEW_CONTAINER_HDFS -} from '../../common/constants'; -import { __ } from '../../common/i18n'; -import { getSingleton, Singleton } from '../../common/singleton'; -import { Util } from '../../common/util'; -import { ClusterManager } from '../clusterManager'; -import { HDFS, HDFSFileSystemProvider } from '../storage/hdfs'; -import { IPAICluster } from '../utility/paiInterface'; - -import { FileNode } from './common/fileNode'; -import { FolderNode } from './common/folderNode'; -import { RootNode, SelectClusterNode, SelectClusterRootNode } from './common/rootNode'; -import { TreeNode } from './common/treeNode'; - -type IFileList = [string, FileType][]; - -/** - * Contributes to the tree view of HDFS explorer. - */ -@injectable() -export class HDFSTreeDataProvider extends Singleton implements TreeDataProvider { - public readonly view: TreeView; - public root: TreeNode; - public onDidChangeTreeData: Event; - - private onDidChangeTreeDataEmitter: EventEmitter; - - private uri?: Uri; - - constructor() { - super(); - this.onDidChangeTreeDataEmitter = new EventEmitter(); - this.onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event; - this.root = new SelectClusterRootNode(); - this.view = window.createTreeView(VIEW_CONTAINER_HDFS, { treeDataProvider: this }); - } - - public async onActivate(): Promise { - this.context.subscriptions.push( - commands.registerCommand(COMMAND_CONTAINER_HDFS_REFRESH, () => this.refresh()), - commands.registerCommand(COMMAND_CONTAINER_HDFS_BACK, () => this.reset()), - commands.registerCommand(COMMAND_CONTAINER_HDFS_DELETE, async (node: TreeItem) => { - await (await getSingleton(HDFS)).provider!.delete(node.resourceUri!, { recursive: true }); - }), - commands.registerCommand(COMMAND_CONTAINER_HDFS_MKDIR, async (node: TreeItem) => { - const res: string | undefined = await window.showInputBox({ - prompt: __('container.hdfs.mkdir.prompt') - }); - if (res === undefined) { - Util.warn('container.hdfs.mkdir.cancelled'); - } else { - await (await getSingleton(HDFS)).provider!.createDirectory(Util.uriPathAppend(node.resourceUri!, res)); - } - }) - ); - this.refresh(); - (await getSingleton(HDFS)).provider!.onDidChangeFile(() => this.refresh()); - } - - public reset(): void { - this.uri = undefined; - this.root = new SelectClusterRootNode(); - this.refresh(); - void this.view.reveal(this.root); - } - - public setUri(uri?: Uri): void { - if (uri === undefined) { - this.reset(); - } else { - this.uri = uri; - this.root = new RootNode(this.uri); - this.refresh(); - void this.view.reveal(this.root); - } - } - - public refresh(node?: TreeNode): void { - this.onDidChangeTreeDataEmitter.fire(node); - } - - public getTreeItem(element: TreeNode): TreeNode { - return element; - } - - public async getChildren(element?: TreeNode): Promise { - if (!this.uri) { - if (!element) { - return [this.root]; - } else if (element.contextValue === CONTEXT_HDFS_SELECT_CLUSTER_ROOT) { - const allConfigurations: IPAICluster[] = (await getSingleton(ClusterManager)).allConfigurations; - return allConfigurations.filter( - conf => !!conf.webhdfs_uri - ).map( - conf => new SelectClusterNode(conf, element) - ); - } else { - return; - } - } else { - if (!element) { - return [this.root]; - } else if (element.contextValue === CONTEXT_HDFS_FOLDER || element.contextValue === CONTEXT_HDFS_ROOT) { - const provider: HDFSFileSystemProvider | undefined = (await getSingleton(HDFS)).provider; - if (!provider) { - return; - } - const uri: Uri = element.resourceUri!; - const res: IFileList = await provider.readDirectory(uri); - return [ - ...res.filter( - ([, type]) => type === FileType.Directory - ).sort( - ([name1], [name2]) => name1.localeCompare(name2) - ).map( - ([name]) => new FolderNode(Util.uriPathAppend(uri, name), element) - ), - ...res.filter( - ([, type]) => type === FileType.File - ).sort( - ([name1], [name2]) => name1.localeCompare(name2) - ).map( - ([name]) => new FileNode(Util.uriPathAppend(uri, name), element) - ) - ]; - } else { - return; - } - - } - } - - public getParent(element: TreeNode): TreeNode | undefined { - return element.parent; - } -} diff --git a/contrib/pai_vscode/src/pai/container/jobListTreeView.ts b/contrib/pai_vscode/src/pai/container/jobListTreeView.ts deleted file mode 100644 index 9fa3204125..0000000000 --- a/contrib/pai_vscode/src/pai/container/jobListTreeView.ts +++ /dev/null @@ -1,340 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { injectable } from 'inversify'; -import * as request from 'request-promise-native'; -import { - commands, window, workspace, Event, EventEmitter, TreeDataProvider, - TreeItem, TreeItemCollapsibleState, TreeView, WorkspaceConfiguration -} from 'vscode'; - -import { - COMMAND_CONTAINER_JOBLIST_MORE, COMMAND_CONTAINER_JOBLIST_REFRESH, - COMMAND_TREEVIEW_DOUBLECLICK, COMMAND_VIEW_JOB, - ICON_ELLIPSIS, - ICON_ERROR, - ICON_HISTORY, - ICON_LATEST, - ICON_LOADING, - ICON_OK, - ICON_QUEUE, - ICON_RUN, - ICON_STOP, - SETTING_JOB_JOBLIST_ALLJOBSPAGESIZE, - SETTING_JOB_JOBLIST_RECENTJOBSLENGTH, - SETTING_JOB_JOBLIST_REFERSHINTERVAL, - SETTING_SECTION_JOB, - VIEW_CONTAINER_JOBLIST -} from '../../common/constants'; -import { __ } from '../../common/i18n'; -import { getSingleton, Singleton } from '../../common/singleton'; -import { Util } from '../../common/util'; -import { ClusterManager } from '../clusterManager'; -import { RecentJobManager } from '../recentJobManager'; -import { IPAICluster, IPAIJobInfo } from '../utility/paiInterface'; -import { PAIRestUri } from '../utility/paiUri'; - -import { ClusterNode } from './common/clusterNode'; -import { FilterType, LoadingState, TreeDataType } from './common/treeDataEnum'; - -/** - * Leaf node representing job on PAI - */ -export class JobNode extends TreeItem { - private static statusIcons: { [status in IPAIJobInfo['state']]: string | undefined } = { - SUCCEEDED: ICON_OK, - FAILED: ICON_ERROR, - WAITING: ICON_QUEUE, - STOPPED: ICON_STOP, - RUNNING: ICON_RUN, - UNKNOWN: undefined - }; - - public constructor(jobInfo: IPAIJobInfo, config: IPAICluster) { - super(jobInfo.name); - this.command = { - title: __('treeview.joblist.view'), - command: COMMAND_TREEVIEW_DOUBLECLICK, - arguments: [COMMAND_VIEW_JOB, jobInfo, config] - }; - const icon: string | undefined = JobNode.statusIcons[jobInfo.state]; - if (icon) { - this.iconPath = Util.resolvePath(icon); - } - } -} - -/** - * Expand job list when chosen - */ -class ShowMoreNode extends TreeItem { - public constructor(cluster: IClusterData) { - super(__('treeview.joblist.more')); - this.command = { - title: __('treeview.joblist.more'), - command: COMMAND_TREEVIEW_DOUBLECLICK, - arguments: [COMMAND_CONTAINER_JOBLIST_MORE, cluster] - }; - this.iconPath = Util.resolvePath(ICON_ELLIPSIS); - } -} - -/** - * Secondary node containing filtered job list - */ -class FilterNode extends TreeItem { - public constructor(type: FilterType, loadingState: LoadingState) { - if (type === FilterType.Recent) { - super(__('treeview.joblist.recent'), TreeItemCollapsibleState.Expanded); - } else { - super(__('treeview.joblist.all'), TreeItemCollapsibleState.Collapsed); - } - this.iconPath = Util.resolvePath( - loadingState === LoadingState.Loading ? ICON_LOADING : - loadingState === LoadingState.Error ? ICON_ERROR : - type === FilterType.Recent ? ICON_LATEST : ICON_HISTORY); - } -} - -interface IClusterData { - type: TreeDataType.Cluster; - config: IPAICluster; - index: number; - shownAmount: number; - loadingState: LoadingState; - jobs: IPAIJobInfo[]; - lastShownAmount?: number; -} - -interface IFilterData { - type: TreeDataType.Filter; - filterType: FilterType; - clusterIndex: number; -} - -interface IJobData { - type: TreeDataType.Job; - job: IPAIJobInfo; - clusterIndex: number; - filterType: FilterType; -} - -interface IMoreData { - type: TreeDataType.More; - clusterIndex: number; -} - -type ITreeData = IClusterData | IFilterData | IJobData | IMoreData; - -/** - * Contributes to the tree view of cluster job list - */ -@injectable() -export class JobListTreeDataProvider extends Singleton implements TreeDataProvider { - private onDidChangeTreeDataEmitter: EventEmitter = new EventEmitter(); - public onDidChangeTreeData: Event = this.onDidChangeTreeDataEmitter.event; // tslint:disable-line - - private clusters: IClusterData[] = []; - private clusterLoadError: boolean[] = []; - private readonly treeView: TreeView; - private refreshTimer: NodeJS.Timer | undefined; - - constructor() { - super(); - this.treeView = window.createTreeView(VIEW_CONTAINER_JOBLIST, { treeDataProvider: this }); - this.context.subscriptions.push( - commands.registerCommand(COMMAND_CONTAINER_JOBLIST_REFRESH, () => this.refresh()), - commands.registerCommand( - COMMAND_CONTAINER_JOBLIST_MORE, - (cluster: IClusterData) => { - if (cluster.jobs.length <= cluster.shownAmount) { - return; - } - const settings: WorkspaceConfiguration = workspace.getConfiguration(SETTING_SECTION_JOB); - cluster.lastShownAmount = cluster.shownAmount; - cluster.shownAmount += settings.get(SETTING_JOB_JOBLIST_ALLJOBSPAGESIZE)!; - void this.refresh(cluster.index, false); - } - ) - ); - } - - public async refresh(index: number = -1, reload: boolean = true): Promise { - if (index === -1 || !this.clusters[index]) { - const settings: WorkspaceConfiguration = workspace.getConfiguration(SETTING_SECTION_JOB); - const allConfigurations: IPAICluster[] = (await getSingleton(ClusterManager)).allConfigurations; - this.clusters = allConfigurations.map((config, i) => ({ - type: TreeDataType.Cluster, - index: i, - config, - loadingState: LoadingState.Finished, - jobs: [], - shownAmount: settings.get(SETTING_JOB_JOBLIST_ALLJOBSPAGESIZE)! - })); - if (this.clusterLoadError.length !== this.clusters.length) { - this.clusterLoadError = new Array(this.clusters.length).fill(false); - } - this.onDidChangeTreeDataEmitter.fire(); - if (reload) { - void this.reloadJobs(); - } - } else { - this.onDidChangeTreeDataEmitter.fire(this.clusters[index]); - if (reload) { - void this.reloadJobs(index); - } - } - } - - public getTreeItem(element: ITreeData): TreeItem { - switch (element.type) { - case TreeDataType.Cluster: - return new ClusterNode(element.config, element.index); - case TreeDataType.Filter: - return new FilterNode(element.filterType, this.clusters[element.clusterIndex].loadingState); - case TreeDataType.Job: - return new JobNode(element.job, this.clusters[element.clusterIndex].config); - case TreeDataType.More: - return new ShowMoreNode(this.clusters[element.clusterIndex]); - default: - throw new Error('Unexpected node type'); - } - } - - public async getChildren(element?: ITreeData): Promise { - if (!element) { - // Root nodes: configurations - return this.clusters; - } - switch (element.type) { - case TreeDataType.Cluster: { - return [ - { type: TreeDataType.Filter, filterType: FilterType.Recent, clusterIndex: element.index }, - { type: TreeDataType.Filter, filterType: FilterType.All, clusterIndex: element.index } - ]; - } - case TreeDataType.Filter: - if (element.filterType === FilterType.Recent) { - const cluster: IClusterData = this.clusters[element.clusterIndex]; - const settings: WorkspaceConfiguration = workspace.getConfiguration(SETTING_SECTION_JOB); - const recentMaxLen: number = settings.get(SETTING_JOB_JOBLIST_RECENTJOBSLENGTH)!; - const recentJobs: string[] | undefined = (await getSingleton(RecentJobManager)).allRecentJobs[cluster.index] || []; - const result: IJobData[] = []; - for (const name of recentJobs.slice(0, recentMaxLen)) { - const foundJob: IPAIJobInfo | undefined = cluster.jobs.find(job => job.name === name); - if (foundJob) { - result.push({ - type: TreeDataType.Job, - job: foundJob, - clusterIndex: element.clusterIndex, - filterType: element.filterType - }); - } - } - return result; - } else { - const cluster: IClusterData = this.clusters[element.clusterIndex]; - const result: (IJobData | IMoreData)[] = cluster.jobs.slice(0, cluster.shownAmount).map( - job => ({ - type: TreeDataType.Job, - job, - clusterIndex: element.clusterIndex, - filterType: element.filterType - }) - ); - if (cluster.lastShownAmount && cluster.lastShownAmount !== cluster.shownAmount) { - setImmediate(i => this.treeView.reveal(result[i]), cluster.lastShownAmount - 1); - cluster.lastShownAmount = cluster.shownAmount; - } - if (cluster.jobs.length > cluster.shownAmount) { - result.push({ type: TreeDataType.More, clusterIndex: element.clusterIndex }); - } - return result; - } - case TreeDataType.Job: - case TreeDataType.More: - return undefined; - default: - } - } - - public getParent(element: ITreeData): ITreeData | undefined { - if (element.type === TreeDataType.Job) { - return { - type: TreeDataType.Filter, - filterType: element.filterType, - clusterIndex: element.clusterIndex - }; - } - if (element.type === TreeDataType.Filter) { - return this.clusters[element.clusterIndex]; - } - } - - public async revealLatestJob(clusterIndex: number, jobName: string): Promise { - const job: IPAIJobInfo | undefined = this.clusters[clusterIndex].jobs.find(j => j.name === jobName); - if (job) { - /** - * Note: treeView.reveal() will obtain the node's parent and - * traverse to ancestors (upwards) until a expanded node has been found, - * then go back down and expand nodes via getChildren() on demand - */ - await this.treeView.reveal( - { - type: TreeDataType.Job, - job: job, - clusterIndex: clusterIndex, - filterType: FilterType.Recent - }, - { focus: true } - ); - } - } - - public onActivate(): Promise { - return this.refresh(); - } - - public async onDeactivate(): Promise { - if (this.refreshTimer) { - clearTimeout(this.refreshTimer); - } - this.treeView.dispose(); - } - - private async reloadJobs(index: number = -1): Promise { - if (this.refreshTimer) { - clearTimeout(this.refreshTimer); - } - - if (this.treeView.visible) { - const clusters: IClusterData[] = index !== -1 ? [this.clusters[index]] : this.clusters ; - await Promise.all(clusters.map(async cluster => { - cluster.loadingState = LoadingState.Loading; - this.onDidChangeTreeDataEmitter.fire(cluster); - try { - cluster.jobs = await request.get( - PAIRestUri.jobs(cluster.config), - { json: true } - ); - cluster.loadingState = LoadingState.Finished; - this.clusterLoadError[cluster.index] = false; - } catch (e) { - if (!this.clusterLoadError[cluster.index]) { - Util.err('treeview.joblist.error', [e.message || e]); - this.clusterLoadError[cluster.index] = true; - } - cluster.loadingState = LoadingState.Error; - } - this.onDidChangeTreeDataEmitter.fire(cluster); - })); - } - - const settings: WorkspaceConfiguration = workspace.getConfiguration(SETTING_SECTION_JOB); - const interval: number = settings.get(SETTING_JOB_JOBLIST_REFERSHINTERVAL)!; - this.refreshTimer = setTimeout(this.reloadJobs.bind(this), interval * 1000); - } -} diff --git a/contrib/pai_vscode/src/pai/container/storage/NfsTreeItem.ts b/contrib/pai_vscode/src/pai/container/storage/NfsTreeItem.ts deleted file mode 100644 index 068bff9a01..0000000000 --- a/contrib/pai_vscode/src/pai/container/storage/NfsTreeItem.ts +++ /dev/null @@ -1,137 +0,0 @@ - -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as fs from 'fs-extra'; -import { Dictionary } from 'lodash'; -import { IMountInfo, IStorageServer } from 'openpai-js-sdk'; -import * as os from 'os'; -import * as path from 'path'; -import { workspace, Uri, WorkspaceConfiguration } from 'vscode'; - -import { - COMMAND_STORAGE_NFS_MOUNT, - COMMAND_STORAGE_NFS_MOUNT_POINT, - COMMAND_TREEVIEW_DOUBLECLICK, - CONTEXT_STORAGE_MOUNTPOINT_ITEM, - CONTEXT_STORAGE_NFS, - SETTING_SECTION_STORAGE_NFS, - SETTING_STORAGE_NFS_MOUNT_POINT -} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; -import { PathBaseStorageManager } from '../../storage/pathBaseStorageManager'; -import { StorageTreeNode } from '../common/treeNode'; - -import { MountPointTreeNode } from './mountPointTreeItem'; -import { PathBaseTreeNode } from './pathBaseTreeItem'; - -/** - * PAI NFS storage root node. - */ -export class NfsRootNode extends StorageTreeNode { - public storageServer: IStorageServer; - public mountInfo: IMountInfo; - public setupMountPoint: boolean = false; - public rootPath?: string; - - constructor(storage: IStorageServer, info: IMountInfo, parent: StorageTreeNode) { - super(storage.spn, parent); - this.contextValue = CONTEXT_STORAGE_NFS; - this.storageServer = storage; - this.description = 'NFS'; - this.mountInfo = info; - this.loadRootPath(); - } - - public loadRootPath(): void { - const settings: WorkspaceConfiguration = - workspace.getConfiguration(SETTING_SECTION_STORAGE_NFS); - const clusterName: string = (this.parent).contextValue === CONTEXT_STORAGE_MOUNTPOINT_ITEM ? - (this.parent).cluster.name! : 'personal_storage'; - const storageName: string = this.storageServer.spn; - const key: string = this.generateMountConfigKey(clusterName, storageName, this.mountInfo.mountPoint); - const map: Dictionary | undefined = - settings.get(SETTING_STORAGE_NFS_MOUNT_POINT); - if (map !== null && map![key]) { - this.rootPath = this.resolveHome(path.join(map![key], '/')); - this.setupMountPoint = true; - } - } - - public resolveHome(filepath: string): string { - if (filepath[0] === '~') { - return path.join(os.homedir(), filepath.slice(1)); - } - return filepath; - } - - public async refresh(): Promise { - this.loadRootPath(); - if (this.setupMountPoint) { - try { - const list: string[] = fs.readdirSync(this.rootPath!); - this.children = list.map(name => - new PathBaseTreeNode(name, path.join(this.rootPath!, name), this)); - } catch (err) { - const setupNfsMountPoint: StorageTreeNode = - new StorageTreeNode(__('treeview.storage.nfs.mount'), this.parent); - const clusterName: string = (this.parent).contextValue === CONTEXT_STORAGE_MOUNTPOINT_ITEM ? - (this.parent).cluster.name! : 'personal_storage'; - const storageName: string = this.storageServer.spn; - const key: string = this.generateMountConfigKey(clusterName, storageName, this.mountInfo.mountPoint); - const settings: WorkspaceConfiguration = - workspace.getConfiguration(SETTING_SECTION_STORAGE_NFS); - const map: Dictionary | undefined = - settings.get(SETTING_STORAGE_NFS_MOUNT_POINT); - setupNfsMountPoint.command = { - title: __('pai.storage.nfs.mountPoint'), - command: COMMAND_TREEVIEW_DOUBLECLICK, - arguments: [COMMAND_STORAGE_NFS_MOUNT, this, map![key]] - }; - this.children = [ - setupNfsMountPoint - ]; - } - } else { - const setupNfsMountPoint: StorageTreeNode = - new StorageTreeNode(__('treeview.storage.nfs.setup.mount.point'), this.parent); - const clusterName: string = (this.parent).contextValue === CONTEXT_STORAGE_MOUNTPOINT_ITEM ? - (this.parent).cluster.name! : 'personal_storage'; - const storageName: string = this.storageServer.spn; - const key: string = this.generateMountConfigKey(clusterName, storageName, this.mountInfo.mountPoint); - setupNfsMountPoint.command = { - title: __('pai.storage.nfs.mountPoint'), - command: COMMAND_TREEVIEW_DOUBLECLICK, - arguments: [COMMAND_STORAGE_NFS_MOUNT_POINT, this, key] - }; - this.children = [ - setupNfsMountPoint - ]; - } - } - - public async uploadFile(files?: Uri[]): Promise { - if (this.setupMountPoint) { - await PathBaseStorageManager.uploadFiles(this, files); - } - } - - public async uploadFolder(): Promise { - if (this.setupMountPoint) { - await PathBaseStorageManager.uploadFolders(this); - } - } - - public async createFolder(folder?: string): Promise { - if (this.setupMountPoint) { - await PathBaseStorageManager.createFolder(this, folder); - } - } - - private generateMountConfigKey(clusterName: string, storageName: string, mountPoint: string): string { - return `${clusterName}~${storageName}~${mountPoint}`; - } -} diff --git a/contrib/pai_vscode/src/pai/container/storage/azureBlobTreeItem.ts b/contrib/pai_vscode/src/pai/container/storage/azureBlobTreeItem.ts deleted file mode 100644 index c4364af43d..0000000000 --- a/contrib/pai_vscode/src/pai/container/storage/azureBlobTreeItem.ts +++ /dev/null @@ -1,308 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { PagedAsyncIterableIterator } from '@azure/core-paging'; -import { - BlobItem, - BlobPrefix, - BlobServiceClient, - ContainerClient, - ContainerListBlobHierarchySegmentResponse, - StorageSharedKeyCredential -} from '@azure/storage-blob'; -import { IStorageServer } from 'openpai-js-sdk'; -import * as path from 'path'; -import { TreeItemCollapsibleState, Uri } from 'vscode'; - -import { - COMMAND_STORAGE_OPEN_FILE, - COMMAND_TREEVIEW_DOUBLECLICK, - CONTEXT_STORAGE_AZURE_BLOB, - CONTEXT_STORAGE_FILE, - CONTEXT_STORAGE_FOLDER, - CONTEXT_STORAGE_LOAD_MORE, - ICON_FILE, - ICON_FOLDER, - ICON_STORAGE -} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; -import { getSingleton } from '../../../common/singleton'; -import { Util } from '../../../common/util'; -import { AzureBlobManager } from '../../storage/azureBlobManager'; -import { RemoteFileEditor } from '../../utility/remoteFileEditor'; -import { StorageLoadMoreTreeNode, StorageTreeNode } from '../common/treeNode'; - -export type BlobIter = PagedAsyncIterableIterator<({ - kind: 'prefix'; - } & BlobPrefix) | ({ - kind: 'blob'; - } & BlobItem), ContainerListBlobHierarchySegmentResponse>; - -export type BlobValue = ({ - kind: 'prefix'; - } & BlobPrefix) | ({ - kind: 'blob'; - } & BlobItem); - -export type BlobEntity = { - done?: boolean | undefined; - value: ({ - kind: 'prefix'; - } & BlobPrefix) | ({ - kind: 'blob'; - } & BlobItem); - }; - -export type BlobMetadata = { - [propertyName: string]: string; - } | undefined; - -function isFolder(blob: BlobValue, metadata: BlobMetadata): boolean { - if (blob.kind === 'prefix') { - return true; - } else if (metadata && - metadata.hdi_isfolder && - metadata.hdi_isfolder === 'true') { - return true; - } - return false; -} - -/** - * PAI azure blob storage tree item. - */ -export class AzureBlobTreeItem extends StorageTreeNode { - public client: ContainerClient; - public blob: BlobValue; - public rootPath: string; - - private currentContinuationToken?: string; - private currentPrefixes: Map; - - public constructor( - blob: BlobValue, - client: ContainerClient, - parent: StorageTreeNode - ) { - const metadata: BlobMetadata = blob.kind === 'blob' ? blob.metadata : undefined; - const folder: boolean = isFolder(blob, metadata); - const name: string | undefined = path.basename(blob.name); - - super( - name ? name : path.dirname(blob.name), - parent, - folder ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None - ); - - this.contextValue = folder ? - CONTEXT_STORAGE_FOLDER : CONTEXT_STORAGE_FILE; - this.client = client; - this.blob = blob; - this.rootPath = blob.name; - this.description = this.rootPath; - this.currentPrefixes = new Map(); - - if (folder) { - this.iconPath = Uri.file(Util.resolvePath(ICON_FOLDER)); - } else { - this.iconPath = Uri.file(Util.resolvePath(ICON_FILE)); - this.command = { - title: __('treeview.node.storage.openfile'), - command: COMMAND_TREEVIEW_DOUBLECLICK, - arguments: [COMMAND_STORAGE_OPEN_FILE, this] - }; - } - } - - public async refresh(): Promise { - if (this.blob.kind === 'blob') { - return; - } - - this.currentContinuationToken = undefined; - this.currentPrefixes.clear(); - this.children = []; - - await this.loadMore(); - } - - public async loadMore(): Promise { - if (this.children.length > 0 && - this.children[this.children.length - 1].contextValue === CONTEXT_STORAGE_LOAD_MORE - ) { - this.children.pop(); - } - - try { - const iter: IteratorResult = - await this.client.listBlobsByHierarchy('/', { - prefix: this.rootPath, - includeMetadata: true - }).byPage({ - continuationToken: this.currentContinuationToken, - maxPageSize: AzureBlobTreeItem.pageSize - }).next(); - this.currentContinuationToken = iter.value.continuationToken; - const prefixes: BlobPrefix[] | undefined = iter.value.segment.blobPrefixes; - if (prefixes) { - prefixes.forEach(item => { - const value: BlobValue = item; - value.kind = 'prefix'; - const node: AzureBlobTreeItem = - new AzureBlobTreeItem(value, this.client, this); - this.currentPrefixes.set(item.name, node); - this.children.push(node); - }); - } - const blobs: BlobItem[] = iter.value.segment.blobItems; - - for (const blob of blobs) { - if (blob.metadata && blob.metadata.hdi_isfolder && blob.metadata.hdi_isfolder === 'true') { - if (this.currentPrefixes.has(`${blob.name}/`)) { - continue; - } - } - const value: BlobValue = blob; - value.kind = 'blob'; - this.children.push(new AzureBlobTreeItem(value, this.client, this)); - } - - if (this.currentContinuationToken) { - this.children.push(new StorageLoadMoreTreeNode(this)); - } - } catch (err) { - const child: StorageTreeNode = - new StorageTreeNode(__('treeview.node.storage.load-error'), this); - child.description = err.message; - this.children.push(child); - } - } - - public async download(dest?: Uri): Promise { - await AzureBlobManager.downloadFile(this, dest); - } - - public async uploadFile(files?: Uri[]): Promise { - await AzureBlobManager.uploadFiles(this, files); - } - - public async delete(): Promise { - await AzureBlobManager.delete(this); - } - - public async openFile(): Promise { - const remoteFileEditor: RemoteFileEditor = - await getSingleton(RemoteFileEditor); - await remoteFileEditor.showEditor(this); - } - - public async uploadFolder(): Promise { - await AzureBlobManager.uploadFolders(this); - } - - public async createFolder(folder?: string): Promise { - await AzureBlobManager.createFolder(this, folder); - } -} - -/** - * PAI azure blob storage root item. - */ -export class AzureBlobRootItem extends StorageTreeNode { - public storage: IStorageServer; - public client: ContainerClient; - public rootPath: string; - - private currentContinuationToken?: string; - private currentPrefixes: Map; - - public constructor(storage: IStorageServer, rootPath: string, parent: StorageTreeNode) { - super(storage.spn, parent, TreeItemCollapsibleState.Collapsed); - this.storage = storage; - this.contextValue = CONTEXT_STORAGE_AZURE_BLOB; - this.description = 'Azure Blob'; - this.rootPath = rootPath; - this.iconPath = Uri.file(Util.resolvePath(ICON_STORAGE)); - this.currentPrefixes = new Map(); - - const credential: StorageSharedKeyCredential = - new StorageSharedKeyCredential(storage.data.accountName, storage.data.key); - const blobClient: BlobServiceClient = new BlobServiceClient( - `https://${storage.data.accountName}.blob.core.windows.net`, credential); - this.client = blobClient.getContainerClient(storage.data.containerName); - } - - public async refresh(): Promise { - this.currentContinuationToken = undefined; - this.currentPrefixes.clear(); - this.children = []; - await this.loadMore(); - } - - public async loadMore(): Promise { - if (this.children.length > 0 && - this.children[this.children.length - 1].contextValue === CONTEXT_STORAGE_LOAD_MORE - ) { - this.children.pop(); - } - - try { - const iter: IteratorResult = - await this.client.listBlobsByHierarchy('/', { - prefix: this.rootPath, - includeMetadata: true - }).byPage({ - continuationToken: this.currentContinuationToken, - maxPageSize: AzureBlobTreeItem.pageSize - }).next(); - this.currentContinuationToken = iter.value.continuationToken; - const prefixes: BlobPrefix[] | undefined = iter.value.segment.blobPrefixes; - if (prefixes) { - prefixes.forEach(item => { - const value: BlobValue = item; - value.kind = 'prefix'; - const node: AzureBlobTreeItem = - new AzureBlobTreeItem(value, this.client, this.parent!); - this.currentPrefixes.set(item.name, node); - this.children.push(node); - }); - } - const blobs: BlobItem[] = iter.value.segment.blobItems; - - for (const blob of blobs) { - if (blob.metadata && blob.metadata.hdi_isfolder && blob.metadata.hdi_isfolder === 'true') { - if (this.currentPrefixes.has(`${blob.name}/`)) { - continue; - } - } - const value: BlobValue = blob; - value.kind = 'blob'; - this.children.push(new AzureBlobTreeItem(value, this.client, this.parent!)); - } - - if (this.currentContinuationToken) { - this.children.push(new StorageLoadMoreTreeNode(this.parent!)); - } - } catch (err) { - const child: StorageTreeNode = - new StorageTreeNode(__('treeview.node.storage.load-error'), this.parent); - child.description = err.message; - this.children.push(child); - } - } - - public async uploadFile(files?: Uri[]): Promise { - await AzureBlobManager.uploadFiles(this, files); - } - - public async uploadFolder(): Promise { - await AzureBlobManager.uploadFolders(this); - } - - public async createFolder(folder?: string): Promise { - await AzureBlobManager.createFolder(this, folder); - } -} diff --git a/contrib/pai_vscode/src/pai/container/storage/clusterStorageTreeItem.ts b/contrib/pai_vscode/src/pai/container/storage/clusterStorageTreeItem.ts deleted file mode 100644 index 6b38f42fa3..0000000000 --- a/contrib/pai_vscode/src/pai/container/storage/clusterStorageTreeItem.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { IStorageConfig, IStorageServer, OpenPAIClient } from 'openpai-js-sdk'; -import { TreeItemCollapsibleState } from 'vscode'; - -import { - CONTEXT_STORAGE_CLUSTER_ITEM, - CONTEXT_STORAGE_CLUSTER_ROOT, - ICON_PAI -} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; -import { getSingleton } from '../../../common/singleton'; -import { Util } from '../../../common/util'; -import { ClusterManager } from '../../clusterManager'; -import { IPAICluster } from '../../utility/paiInterface'; -import { StorageTreeNode } from '../common/treeNode'; - -import { TeamStorageTreeNode } from './teamStorageTreeItem'; - -/** - * PAI cluster storage tree node. - */ -export class ClusterStorageTreeNode extends StorageTreeNode { - public readonly contextValue: string = CONTEXT_STORAGE_CLUSTER_ITEM; - - private cluster: IPAICluster; - - constructor( - cluster: IPAICluster, - parent?: StorageTreeNode, - collapsibleState: TreeItemCollapsibleState = TreeItemCollapsibleState.Collapsed - ) { - super(cluster.name!, parent, collapsibleState); - this.iconPath = Util.resolvePath(ICON_PAI); - this.cluster = cluster; - } - - public async refresh(): Promise { - try { - const client: OpenPAIClient = new OpenPAIClient({ - rest_server_uri: this.cluster.rest_server_uri, - token: this.cluster.token, - username: this.cluster.username, - password: this.cluster.password, - https: this.cluster.https - }); - const storageServers: Map = - new Map((await client.storage.getServer()).map(server => [server.spn, server])); - const storageConfigs: IStorageConfig[] = await client.storage.getConfig(); - this.children = storageConfigs.map(config => - new TeamStorageTreeNode(config, storageServers, this.cluster, client, this)); - } catch (e) { - Util.err('treeview.storage.error', [e.message || e]); - } - } -} - -/** - * PAI cluster storage root node. - */ -export class ClusterStorageRootNode extends StorageTreeNode { - public readonly contextValue: string = CONTEXT_STORAGE_CLUSTER_ROOT; - - constructor() { - super(__('treeview.storage.cluster-root.label'), undefined, TreeItemCollapsibleState.Expanded); - } - - public async refresh(): Promise { - const clusterManager: ClusterManager = await getSingleton(ClusterManager); - const clusters: IPAICluster[] = clusterManager.allConfigurations; - - this.children = clusters.map(cluster => new ClusterStorageTreeNode(cluster, this)); - } -} diff --git a/contrib/pai_vscode/src/pai/container/storage/mountPointTreeItem.ts b/contrib/pai_vscode/src/pai/container/storage/mountPointTreeItem.ts deleted file mode 100644 index c86f0b7440..0000000000 --- a/contrib/pai_vscode/src/pai/container/storage/mountPointTreeItem.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { IMountInfo, IStorageServer } from 'openpai-js-sdk'; -import { TreeItemCollapsibleState, Uri } from 'vscode'; - -import { - CONTEXT_STORAGE_MOUNTPOINT_ITEM -} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; -import { IPAICluster } from '../../utility/paiInterface'; -import { StorageTreeNode } from '../common/treeNode'; - -import { AzureBlobRootItem } from './azureBlobTreeItem'; -import { NfsRootNode } from './NfsTreeItem'; -import { SambaRootNode } from './SambaTreeItem'; - -/** - * PAI storage mount point tree node. - */ -export class MountPointTreeNode extends StorageTreeNode { - public contextValue: string = CONTEXT_STORAGE_MOUNTPOINT_ITEM; - public data: StorageTreeNode; - public cluster: IPAICluster; - - constructor( - info: IMountInfo, - cluster: IPAICluster, - server: IStorageServer, - parent?: StorageTreeNode, - collapsibleState: TreeItemCollapsibleState = TreeItemCollapsibleState.Collapsed - ) { - super('Mount Point', parent, collapsibleState); - this.description = info.mountPoint; - - this.cluster = cluster; - this.data = this.initializeData(info, server); - } - - public async refresh(): Promise { - return this.data.refresh(); - } - - public async getChildren(): Promise { - return this.data.getChildren(); - } - - public async loadMore(): Promise { - await this.data.loadMore(); - } - - public async uploadFile(files?: Uri[]): Promise { - await this.data.uploadFile(files); - } - - public async createFolder(folder?: string): Promise { - await this.data.createFolder(folder); - } - - private initializeData(info: IMountInfo, server: IStorageServer): StorageTreeNode { - switch (server.type) { - case 'azureblob': - return new AzureBlobRootItem(server, this.getRootPath(info, this.cluster), this); - case 'azurefile': - return new StorageTreeNode('Azure File'); - case 'nfs': - return new NfsRootNode(server, info, this); - case 'samba': - return new SambaRootNode(server, info.mountPoint, this); - default: - return new StorageTreeNode('Unsupported storage'); - } - } - - private getRootPath(info: IMountInfo, cluster: IPAICluster): string { - const envs: Map = new Map([ - ['\${PAI_USER_NAME}', cluster.username!] - ]); - let path: string = info.path; - for (const [key, value] of envs) { - path = path.replace(key, value); - } - if (!path.endsWith('/')) { - path = path + '/'; - } - return path; - } -} diff --git a/contrib/pai_vscode/src/pai/container/storage/pathBaseTreeItem.ts b/contrib/pai_vscode/src/pai/container/storage/pathBaseTreeItem.ts deleted file mode 100644 index dd1be02f98..0000000000 --- a/contrib/pai_vscode/src/pai/container/storage/pathBaseTreeItem.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as fs from 'fs-extra'; -import * as path from 'path'; -import { TreeItemCollapsibleState, Uri } from 'vscode'; - -import { - COMMAND_STORAGE_OPEN_FILE, - COMMAND_TREEVIEW_DOUBLECLICK, - CONTEXT_STORAGE_FILE, - CONTEXT_STORAGE_FOLDER, - ICON_FILE, - ICON_FOLDER -} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; -import { getSingleton } from '../../../common/singleton'; -import { Util } from '../../../common/util'; -import { PathBaseStorageManager } from '../../storage/pathBaseStorageManager'; -import { RemoteFileEditor } from '../../utility/remoteFileEditor'; -import { StorageTreeNode } from '../common/treeNode'; - -/** - * PAI path base storage tree node. - */ -export class PathBaseTreeNode extends StorageTreeNode { - public rootPath: string; - public name: string; - public isFolder: boolean; - - constructor(name: string, rootPath: string, parent: StorageTreeNode) { - const stat: fs.Stats = fs.statSync(rootPath); - const isFolder: boolean = stat.isDirectory(); - super(name, parent, isFolder ? - TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None); - this.isFolder = isFolder; - this.contextValue = this.isFolder ? CONTEXT_STORAGE_FOLDER : CONTEXT_STORAGE_FILE; - this.rootPath = rootPath; - this.name = name; - - if (this.isFolder) { - this.iconPath = Uri.file(Util.resolvePath(ICON_FOLDER)); - } else { - this.iconPath = Uri.file(Util.resolvePath(ICON_FILE)); - this.command = { - title: __('treeview.node.storage.openfile'), - command: COMMAND_TREEVIEW_DOUBLECLICK, - arguments: [COMMAND_STORAGE_OPEN_FILE, this] - }; - } - } - - public async refresh(): Promise { - if (!this.isFolder) { - return; - } - - try { - const list: string[] = fs.readdirSync(this.rootPath); - this.children = list.map(name => - new PathBaseTreeNode(name, path.join(this.rootPath, name), this)); - } catch (err) { - const child: StorageTreeNode = - new StorageTreeNode(__('treeview.node.storage.load-error'), this.parent); - child.description = err.message; - this.children.push(child); - } - } - - public async download(dest?: Uri): Promise { - await PathBaseStorageManager.downloadFile(this, dest); - } - - public async uploadFile(files?: Uri[]): Promise { - await PathBaseStorageManager.uploadFiles(this, files); - } - - public async delete(): Promise { - await PathBaseStorageManager.delete(this); - } - - public async openFile(): Promise { - const remoteFileEditor: RemoteFileEditor = - await getSingleton(RemoteFileEditor); - await remoteFileEditor.showEditor(this); - } - - public async uploadFolder(): Promise { - await PathBaseStorageManager.uploadFolders(this); - } - - public async createFolder(folder?: string): Promise { - await PathBaseStorageManager.createFolder(this, folder); - } -} diff --git a/contrib/pai_vscode/src/pai/container/storage/personalStorageTreeItem.ts b/contrib/pai_vscode/src/pai/container/storage/personalStorageTreeItem.ts deleted file mode 100644 index 308f467b77..0000000000 --- a/contrib/pai_vscode/src/pai/container/storage/personalStorageTreeItem.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { IMountInfo, IStorageServer } from 'openpai-js-sdk'; -import { TreeItemCollapsibleState, Uri } from 'vscode'; - -import { - CONTEXT_STORAGE_PERSONAL_ITEM, - CONTEXT_STORAGE_PERSONAL_ROOT, - ICON_STORAGE -} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; -import { getSingleton } from '../../../common/singleton'; -import { Util } from '../../../common/util'; -import { PersonalStorageManager } from '../../storage/personalStorageManager'; -import { StorageTreeNode } from '../common/treeNode'; - -import { AzureBlobRootItem } from './azureBlobTreeItem'; -import { NfsRootNode } from './NfsTreeItem'; -import { SambaRootNode } from './SambaTreeItem'; - -/** - * PAI personal storage tree node. - */ -export class PersonalStorageTreeNode extends StorageTreeNode { - public readonly contextValue: string = CONTEXT_STORAGE_PERSONAL_ITEM; - public storage: IStorageServer; - public data: StorageTreeNode; - public index: number; - - constructor( - storage: IStorageServer, - index: number, - parent?: StorageTreeNode, - collapsibleState: TreeItemCollapsibleState = TreeItemCollapsibleState.Collapsed - ) { - super(storage.spn, parent, collapsibleState); - this.iconPath = Util.resolvePath(ICON_STORAGE); - this.index = index; - this.storage = storage; - this.data = this.initializeData(); - } - - public async refresh(): Promise { - return this.data.refresh(); - } - - public async getChildren(): Promise { - return this.data.getChildren(); - } - - public async loadMore(): Promise { - await this.data.loadMore(); - } - - public async uploadFile(files?: Uri[]): Promise { - await this.data.uploadFile(files); - } - - public async uploadFolder(): Promise { - await this.data.uploadFolder(); - } - - public async createFolder(folder?: string): Promise { - await this.data.createFolder(folder); - } - - public initializeData(): StorageTreeNode { - switch (this.storage.type) { - case 'azureblob': - return new AzureBlobRootItem(this.storage, '', this); - case 'azurefile': - return new StorageTreeNode('Azure File'); - case 'nfs': - return new NfsRootNode(this.storage, { mountPoint: '' }, this); - case 'samba': - return new SambaRootNode(this.storage, '', this); - default: - return new StorageTreeNode('Unsupported storage'); - } - } -} - -/** - * PAI personal storage root node. - */ -export class PersonalStorageRootNode extends StorageTreeNode { - public readonly contextValue: string = CONTEXT_STORAGE_PERSONAL_ROOT; - - constructor() { - super(__('treeview.storage.personal-root.label'), undefined, TreeItemCollapsibleState.Expanded); - } - - public async refresh(): Promise { - const personalStorageManager: PersonalStorageManager = await getSingleton(PersonalStorageManager); - const storages: IStorageServer[] = personalStorageManager.allConfigurations; - - this.children = storages.map((storage, index) => new PersonalStorageTreeNode(storage, index, this)); - } -} diff --git a/contrib/pai_vscode/src/pai/container/storage/sambaTreeItem.ts b/contrib/pai_vscode/src/pai/container/storage/sambaTreeItem.ts deleted file mode 100644 index 302079bb6d..0000000000 --- a/contrib/pai_vscode/src/pai/container/storage/sambaTreeItem.ts +++ /dev/null @@ -1,64 +0,0 @@ - -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as fs from 'fs-extra'; -import { IStorageServer } from 'openpai-js-sdk'; -import * as path from 'path'; -import { TreeItemCollapsibleState, Uri } from 'vscode'; - -import { - CONTEXT_STORAGE_SAMBA} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; -import { PathBaseStorageManager } from '../../storage/pathBaseStorageManager'; -import { StorageTreeNode } from '../common/treeNode'; - -import { PathBaseTreeNode } from './pathBaseTreeItem'; - -/** - * PAI Samba storage root node. - */ -export class SambaRootNode extends StorageTreeNode { - public storageServer: IStorageServer; - public mountPath: string; - public rootPath: string; - - constructor(storage: IStorageServer, mountPath: string, parent: StorageTreeNode) { - super(storage.spn, parent, TreeItemCollapsibleState.Collapsed); - this.contextValue = CONTEXT_STORAGE_SAMBA; - this.storageServer = storage; - this.description = 'Samba'; - this.mountPath = mountPath; - const rootUrl: string = - `//${this.storageServer.data.address}${this.storageServer.data.rootPath}`; - this.rootPath = path.join(rootUrl, this.mountPath); - } - - public async refresh(): Promise { - try { - const list: string[] = fs.readdirSync(this.rootPath); - this.children = list.map(name => - new PathBaseTreeNode(name, path.join(this.rootPath, name), this)); - } catch (err) { - const child: StorageTreeNode = - new StorageTreeNode(__('treeview.node.storage.load-error'), this.parent); - child.description = err.message; - this.children.push(child); - } - } - - public async uploadFile(files?: Uri[]): Promise { - await PathBaseStorageManager.uploadFiles(this, files); - } - - public async uploadFolder(): Promise { - await PathBaseStorageManager.uploadFolders(this); - } - - public async createFolder(folder?: string): Promise { - await PathBaseStorageManager.createFolder(this, folder); - } -} diff --git a/contrib/pai_vscode/src/pai/container/storage/storageTreeView.ts b/contrib/pai_vscode/src/pai/container/storage/storageTreeView.ts deleted file mode 100644 index 040275598f..0000000000 --- a/contrib/pai_vscode/src/pai/container/storage/storageTreeView.ts +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { injectable } from 'inversify'; -import { - commands, - window, - workspace, - Event, - EventEmitter, - TextDocument, - TreeDataProvider, - TreeItem, - TreeView -} from 'vscode'; - -import { - COMMAND_CONTAINER_STORAGE_BACK, - COMMAND_CONTAINER_STORAGE_REFRESH, - COMMAND_OPEN_STORAGE, - COMMAND_STORAGE_CREATE_FOLDER, - COMMAND_STORAGE_DELETE, - COMMAND_STORAGE_DOWNLOAD, - COMMAND_STORAGE_OPEN_FILE, - COMMAND_STORAGE_UPLOAD_FILES, - COMMAND_STORAGE_UPLOAD_FOLDERS, - COMMAND_TREEVIEW_LOAD_MORE, - CONTEXT_STORAGE_CLUSTER_ROOT, - VIEW_CONTAINER_STORAGE -} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; -import { getSingleton, Singleton } from '../../../common/singleton'; -import { ClusterManager } from '../../clusterManager'; -import { IPAICluster } from '../../utility/paiInterface'; -import { RemoteFileEditor } from '../../utility/remoteFileEditor'; -import { StorageTreeNode, TreeNode } from '../common/treeNode'; -import { ClusterExplorerChildNode } from '../configurationTreeDataProvider'; - -import { ClusterStorageRootNode } from './clusterStorageTreeItem'; -import { PersonalStorageRootNode } from './personalStorageTreeItem'; - -/** - * Contributes to the tree view of storage explorer. - */ -@injectable() -export class StorageTreeDataProvider extends Singleton implements TreeDataProvider { - public readonly view: TreeView; - public root: TreeNode[]; - public onDidChangeTreeData: Event; - - private onDidChangeTreeDataEmitter: EventEmitter; - - constructor() { - super(); - this.onDidChangeTreeDataEmitter = new EventEmitter(); - this.onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event; - this.root = [ - new ClusterStorageRootNode() - ]; - this.initializeRoot(); - this.view = window.createTreeView(VIEW_CONTAINER_STORAGE, { treeDataProvider: this }); - } - - public async onActivate(): Promise { - this.context.subscriptions.push( - commands.registerCommand(COMMAND_CONTAINER_STORAGE_REFRESH, element => this.refresh(element)), - commands.registerCommand(COMMAND_CONTAINER_STORAGE_BACK, () => this.reset()), - commands.registerCommand( - COMMAND_OPEN_STORAGE, - async (node?: ClusterExplorerChildNode | IPAICluster) => this.openStorage(node) - ), - commands.registerCommand( - COMMAND_STORAGE_DOWNLOAD, - async (target: StorageTreeNode) => target.download() - ), - commands.registerCommand( - COMMAND_STORAGE_UPLOAD_FILES, - async (target: StorageTreeNode) => { - await target.uploadFile(); - await this.refresh(target); - } - ), - commands.registerCommand( - COMMAND_STORAGE_DELETE, - async (target: StorageTreeNode) => { - await target.delete(); - await this.refresh(target.parent); - } - ), - commands.registerCommand( - COMMAND_STORAGE_UPLOAD_FOLDERS, - async (target: StorageTreeNode) => { - await target.uploadFolder(); - await this.refresh(target); - } - ), - commands.registerCommand( - COMMAND_STORAGE_CREATE_FOLDER, - async (target: StorageTreeNode) => { - await target.createFolder(); - await this.refresh(target); - } - ), - commands.registerCommand( - COMMAND_STORAGE_OPEN_FILE, - async (target: StorageTreeNode) => target.openFile() - ), - commands.registerCommand( - COMMAND_TREEVIEW_LOAD_MORE, - async (target?: StorageTreeNode) => { - if (target) { - await target.loadMore(); - this.onDidChangeTreeDataEmitter.fire(target); - } - } - ), - workspace.onDidSaveTextDocument( - async (doc: TextDocument) => { - const remoteFileEditor: RemoteFileEditor = - await getSingleton(RemoteFileEditor); - await remoteFileEditor.onDidSaveTextDocument(doc); - } - ) - ); - return this.refresh(); - } - - public async openStorage(node?: ClusterExplorerChildNode | IPAICluster): Promise { - let cluster: IPAICluster; - if (!node) { - const manager: ClusterManager = await getSingleton(ClusterManager); - const index: number | undefined = await manager.pick(); - if (index === undefined) { - return; - } - cluster = manager.allConfigurations[index]; - } else if (node instanceof ClusterExplorerChildNode) { - cluster = (await getSingleton(ClusterManager)).allConfigurations[node.index]; - } else { - cluster = node; - } - - for (const currentNode of this.root) { - if (currentNode.contextValue === CONTEXT_STORAGE_CLUSTER_ROOT) { - const clusters: StorageTreeNode[] = await (currentNode).getChildren(); - for (const item of clusters) { - if (item.label === cluster.name!) { - void this.view.reveal(item, { - select: true, - focus: true - }); - } - } - } - } - } - - public getTreeItem(element: TreeNode): TreeItem | Thenable { - return element; - } - - public async getChildren(element?: TreeNode | undefined): Promise { - if (!element) { - return this.root; - } else { - return (element).getChildren(); - } - } - - public getParent(element: TreeNode): TreeNode | undefined { - return element.parent; - } - - public async refresh(element?: StorageTreeNode): Promise { - if (element) { - await element.refresh(); - this.onDidChangeTreeDataEmitter.fire(element); - } else { - for (const item of this.root) { - await (item).refresh(); - } - this.onDidChangeTreeDataEmitter.fire(); - } - } - - public async reset(): Promise { - this.initializeRoot(); - await this.refresh(); - } - - public initializeRoot(): void { - this.root = [ - new ClusterStorageRootNode(), - new PersonalStorageRootNode() - ]; - } -} diff --git a/contrib/pai_vscode/src/pai/container/storage/teamStorageTreeItem.ts b/contrib/pai_vscode/src/pai/container/storage/teamStorageTreeItem.ts deleted file mode 100644 index ff20a42ac1..0000000000 --- a/contrib/pai_vscode/src/pai/container/storage/teamStorageTreeItem.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { IStorageConfig, IStorageServer, OpenPAIClient } from 'openpai-js-sdk'; -import { TreeItemCollapsibleState } from 'vscode'; - -import { - CONTEXT_STORAGE_TEAM_ITEM, ICON_STORAGE -} from '../../../common/constants'; -import { __ } from '../../../common/i18n'; -import { Util } from '../../../common/util'; -import { IPAICluster } from '../../utility/paiInterface'; -import { StorageTreeNode } from '../common/treeNode'; - -import { MountPointTreeNode } from './mountPointTreeItem'; - -/** - * PAI storage mount point tree node. - */ -export class TeamStorageTreeNode extends StorageTreeNode { - public readonly contextValue: string = CONTEXT_STORAGE_TEAM_ITEM; - - private config: IStorageConfig; - private servers: Map; - private client: OpenPAIClient; - private cluster: IPAICluster; - - constructor( - config: IStorageConfig, - servers: Map, - cluster: IPAICluster, - client: OpenPAIClient, - parent?: StorageTreeNode, - collapsibleState: TreeItemCollapsibleState = TreeItemCollapsibleState.Collapsed - ) { - super(config.name, parent, collapsibleState); - this.config = config; - this.servers = servers; - this.client = client; - this.cluster = cluster; - this.iconPath = Util.resolvePath(ICON_STORAGE); - } - - public loadMountPoints(): void { - try { - this.children = this.config.mountInfos.map(mountPoint => - new MountPointTreeNode(mountPoint, this.cluster, this.servers.get(mountPoint.server)!, this)); - } catch (e) { - Util.err('treeview.storage.error', [e.message || e]); - } - } - - public async refresh(): Promise { - try { - this.config = await this.client.storage.getConfigByName(this.config.name); - this.loadMountPoints(); - } catch (e) { - Util.err('treeview.storage.error', [e.message || e]); - } - } -} diff --git a/contrib/pai_vscode/src/pai/paiJobManager.ts b/contrib/pai_vscode/src/pai/paiJobManager.ts deleted file mode 100644 index 8aed73340d..0000000000 --- a/contrib/pai_vscode/src/pai/paiJobManager.ts +++ /dev/null @@ -1,1235 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as fs from 'fs-extra'; -import * as globby from 'globby'; -import { injectable } from 'inversify'; -import * as yaml from 'js-yaml'; -import * as JSONC from 'jsonc-parser'; -import { isEmpty, isNil, range } from 'lodash'; -import { IJobConfigV1 } from 'openpai-js-sdk/lib/models/job'; -import opn = require('opn'); // tslint:disable-line -import * as os from 'os'; -import * as path from 'path'; -import * as request from 'request-promise-native'; -import unixify = require('unixify'); // tslint:disable-line -import * as uuid from 'uuid'; -import * as vscode from 'vscode'; - -import { - COMMAND_CREATE_JOB_CONFIG, - COMMAND_CREATE_JOB_CONFIG_V1, - COMMAND_CREATE_JOB_CONFIG_V2, - COMMAND_SIMULATE_JOB, - COMMAND_SUBMIT_JOB, - OCTICON_CLOUDUPLOAD, - SCHEMA_JOB_CONFIG, - SCHEMA_YAML_JOB_CONFIG, - SETTING_JOB_GENERATEJOBNAME_ENABLED, - SETTING_JOB_UPLOAD_ENABLED, - SETTING_JOB_UPLOAD_EXCLUDE, - SETTING_JOB_UPLOAD_INCLUDE, - SETTING_JOB_V2_UPLOAD, - SETTING_SECTION_JOB -} from '../common/constants'; -import { __ } from '../common/i18n'; -import { getSingleton, Singleton } from '../common/singleton'; -import { Util } from '../common/util'; - -import { getClusterIdentifier, ClusterManager } from './clusterManager'; -import { ClusterExplorerChildNode } from './container/configurationTreeDataProvider'; -import { RecentJobManager } from './recentJobManager'; -import { getHDFSUriAuthority, HDFS, HDFSFileSystemProvider } from './storage/hdfs'; -import { StorageHelper } from './storage/storageHelper'; -import { IPAICluster, IPAIJobConfigV1, IPAIJobConfigV2, IPAIJobV2UploadConfig, IPAITaskRole, IUploadConfig } from './utility/paiInterface'; -import { PAIRestUri, PAIWebPortalUri } from './utility/paiUri'; -import { registerYamlSchemaSupport } from './yaml/yamlSchemaSupport'; - -interface ITokenItem { - token: string; - expireTime: number; -} - -interface IJobParam { - config: IPAIJobConfigV1 | IPAIJobConfigV2; - jobVersion: number; - cluster?: IPAICluster; - workspace: string; - upload?: { - exclude: string[]; - include: string[]; - } | IUploadConfig; - generateJobName: boolean; -} - -interface IJobInput { - jobConfigPath?: string; - clusterIndex?: number; -} - -/** - * Manager class for PAI job submission - */ -@injectable() -export class PAIJobManager extends Singleton { - private static readonly TIMEOUT: number = 60 * 1000; - private static readonly SIMULATION_DOCKERFILE_FOLDER: string = '.pai_simulator'; - private static readonly propertiesToBeReplaced: (keyof IPAIJobConfigV1)[] = [ - 'codeDir', - 'outputDir', - 'dataDir', - 'authFile' - ]; - private static readonly envNeedClusterInfo: string[] = [ - 'PAI_USER_NAME', - 'PAI_CODE_DIR', - 'PAI_OUTPUT_DIR', - 'PAI_DATA_DIR', - 'PAI_DEFAULT_FS_URI' - ]; - private cachedTokens: Map = new Map(); - private simulateTerminal: vscode.Terminal | undefined; - - constructor() { - super(); - this.context.subscriptions.push( - vscode.commands.registerCommand( - COMMAND_CREATE_JOB_CONFIG, - async (input?: ClusterExplorerChildNode | vscode.Uri) => { - await this.generateJobConfig(input); - } - ), - vscode.commands.registerCommand( - COMMAND_CREATE_JOB_CONFIG_V1, - async (input: vscode.Uri) => { - await this.generateJobConfigV1(input.fsPath); - } - ), - vscode.commands.registerCommand( - COMMAND_CREATE_JOB_CONFIG_V2, - async (input: vscode.Uri) => { - await this.generateJobConfigV2(input.fsPath); - } - ), - vscode.commands.registerCommand( - COMMAND_SIMULATE_JOB, - async (input?: ClusterExplorerChildNode | vscode.Uri) => { - if (input instanceof vscode.Uri) { - await this.simulate({ jobConfigPath: input.fsPath }); - } else if (input instanceof ClusterExplorerChildNode) { - await this.simulate({ clusterIndex: input.index }); - } else { - await this.simulate(); - } - } - ), - vscode.commands.registerCommand( - COMMAND_SUBMIT_JOB, - async (input?: ClusterExplorerChildNode | vscode.Uri) => { - if (input instanceof vscode.Uri) { - await this.submitJob({ jobConfigPath: input.fsPath }); - } else if (input instanceof ClusterExplorerChildNode) { - await this.submitJob({ clusterIndex: input.index }); - } else { - await this.submitJob(); - } - } - ) - ); - } - - private static async ensureGenerateJobNameSetting(): Promise { - const settings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(SETTING_SECTION_JOB); - if (settings.get(SETTING_JOB_GENERATEJOBNAME_ENABLED) === null) { - const YES: vscode.QuickPickItem = { - label: __('common.yes'), - description: __('job.prepare.generate-job-name.yes.detail') - }; - const NO: vscode.QuickPickItem = { - label: __('common.no') - }; - const item: vscode.QuickPickItem | undefined = await Util.pick( - [YES, NO], - __('job.prepare.generate-job-name.prompt') - ); - if (item === YES) { - await settings.update(SETTING_JOB_GENERATEJOBNAME_ENABLED, true); - } else if (item === NO) { - await settings.update(SETTING_JOB_GENERATEJOBNAME_ENABLED, false); - } else { - Util.info('job.prepare.generate-job-name.undefined.hint'); - } - } - // reload settings - return vscode.workspace.getConfiguration(SETTING_SECTION_JOB); - } - - private static async ensureSettingsV1(): Promise { - const settings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(SETTING_SECTION_JOB); - if (settings.get(SETTING_JOB_UPLOAD_ENABLED) === null) { - const YES: vscode.QuickPickItem = { - label: __('common.yes'), - description: __('job.prepare.upload.yes.detail') - }; - const NO: vscode.QuickPickItem = { - label: __('common.no') - }; - const item: vscode.QuickPickItem | undefined = await Util.pick( - [YES, NO], - __('job.prepare.upload.prompt') - ); - if (item === YES) { - await settings.update(SETTING_JOB_UPLOAD_ENABLED, true); - await settings.update(SETTING_JOB_UPLOAD_EXCLUDE, []); - await settings.update(SETTING_JOB_UPLOAD_INCLUDE, ['**/*.py']); - } else if (item === NO) { - await settings.update(SETTING_JOB_UPLOAD_ENABLED, false); - } else { - await settings.update(SETTING_JOB_UPLOAD_ENABLED, true); - await settings.update(SETTING_JOB_UPLOAD_EXCLUDE, []); - await settings.update(SETTING_JOB_UPLOAD_INCLUDE, ['**/*.py']); - Util.info('job.prepare.upload.undefined.hint'); - } - } - return await this.ensureGenerateJobNameSetting(); - } - - private static replaceVariables(jobParam: IJobParam): IPAIJobConfigV1 { - // Replace environment variable - const config: IPAIJobConfigV1 = jobParam.config; - const cluster: IPAICluster | undefined = jobParam.cluster; - function replaceVariable(x: string): string { - return x.replace('$PAI_JOB_NAME', config.jobName) - .replace('$PAI_USER_NAME', cluster!.username!); - } - for (const key of PAIJobManager.propertiesToBeReplaced) { - const old: string | IPAITaskRole[] | undefined = config[key]; - if (typeof old === 'string') { - config[key] = replaceVariable(old); - } - } - if (config.taskRoles) { - for (const role of config.taskRoles) { - role.command = replaceVariable(role.command); - } - } - return config; - } - - public async ensureSettingsV2(cluster?: IPAICluster): Promise { - const settings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(SETTING_SECTION_JOB); - if (!settings.get(SETTING_JOB_V2_UPLOAD)) { - const YES: vscode.QuickPickItem = { - label: __('common.yes'), - description: __('job.prepare.upload.yes.detail') - }; - const NO: vscode.QuickPickItem = { - label: __('common.no') - }; - const item: vscode.QuickPickItem | undefined = await Util.pick( - [YES, NO], - __('job.prepare.upload.prompt') - ); - if (!cluster) { - cluster = await this.pickCluster(); - } - if (item === YES) { - let uploadConfig: IPAIJobV2UploadConfig = {}; - uploadConfig = await this.pickUploadStorage(cluster, uploadConfig); - await settings.update(SETTING_JOB_V2_UPLOAD, uploadConfig); - } - } else { - let uploadConfig: any = settings.get(SETTING_JOB_V2_UPLOAD)!; - if (!cluster) { - cluster = await this.pickCluster(); - } - if (!uploadConfig[cluster.name!]) { - const YES: vscode.QuickPickItem = { - label: __('common.yes'), - description: __('job.prepare.upload.yes.detail') - }; - const NO: vscode.QuickPickItem = { - label: __('common.no') - }; - const item: vscode.QuickPickItem | undefined = await Util.pick( - [YES, NO], - __('job.prepare.upload.prompt') - ); - if (item === YES) { - uploadConfig = await this.pickUploadStorage(cluster, uploadConfig); - await settings.update(SETTING_JOB_V2_UPLOAD, uploadConfig); - } - } - } - return await PAIJobManager.ensureGenerateJobNameSetting(); - } - - public async generateJobConfig(input?: ClusterExplorerChildNode | vscode.Uri): Promise { - if (input instanceof ClusterExplorerChildNode) { - const clusterManager: ClusterManager = await getSingleton(ClusterManager); - const cluster: IPAICluster = clusterManager.allConfigurations[input.index]; - - if (cluster.protocol_version === '2') { - await this.generateJobConfigV2(); - } else { - await this.generateJobConfigV1(); - } - } else if (input instanceof vscode.Uri) { - await this.generateJobConfigV2(input.fsPath); - } else { - await this.generateJobConfigV2(); - } - } - - public async generateJobConfigV1(script?: string): Promise { - let defaultSaveDir: string; - let config: IPAIJobConfigV1 | undefined; - if (!script) { - const folders: vscode.WorkspaceFolder[] | undefined = vscode.workspace.workspaceFolders; - let parent: string = os.homedir(); - const name: string = 'new_job'; - if (!isEmpty(folders)) { - const fileFolders: vscode.WorkspaceFolder[] = folders!.filter(x => x.uri.scheme === 'file'); - if (!isEmpty(fileFolders)) { - parent = fileFolders[0].uri.fsPath; - } - } - defaultSaveDir = path.join(parent, `${name}.pai.jsonc`); - config = { - jobName: '', - image: 'aiplatform/pai.build.base', - codeDir: '$PAI_DEFAULT_FS_URI/$PAI_USER_NAME/$PAI_JOB_NAME', - dataDir: '$PAI_DEFAULT_FS_URI/Data/$PAI_JOB_NAME', - outputDir: '$PAI_DEFAULT_FS_URI/Output/$PAI_JOB_NAME', - taskRoles: [ - { - name: 'task', - taskNumber: 1, - cpuNumber: 1, - gpuNumber: 0, - memoryMB: 1000, - command: 'python $PAI_JOB_NAME/' - } - ] - }; - } else { - const workspace: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(script)); - let parent: string; - if (workspace === undefined) { - parent = path.dirname(script); - } else { - parent = workspace.uri.fsPath; - } - script = path.relative(parent, script); - const jobName: string = path.basename(script, path.extname(script)); - defaultSaveDir = path.join(parent, `${jobName}.pai.jsonc`); - config = { - jobName, - image: 'aiplatform/pai.build.base', - codeDir: '$PAI_DEFAULT_FS_URI/$PAI_USER_NAME/$PAI_JOB_NAME', - dataDir: '$PAI_DEFAULT_FS_URI/Data/$PAI_JOB_NAME', - outputDir: '$PAI_DEFAULT_FS_URI/Output/$PAI_JOB_NAME', - taskRoles: [ - { - name: 'task', - taskNumber: 1, - cpuNumber: 1, - gpuNumber: 0, - memoryMB: 1000, - command: `python $PAI_JOB_NAME/${unixify(script)}` - } - ] - }; - } - - const saveDir: vscode.Uri | undefined = await vscode.window.showSaveDialog({ - defaultUri: vscode.Uri.file(defaultSaveDir), - filters: { - JSON: ['json', 'jsonc'] - } - }); - if (saveDir) { - if (saveDir.fsPath.endsWith('.jsonc')) { - await fs.writeFile(saveDir.fsPath, await Util.generateCommentedJSON(config, 'pai_job_config.schema.json')); - } else { - await fs.writeJSON(saveDir.fsPath, config, { spaces: 4 }); - } - await vscode.window.showTextDocument(saveDir); - } - } - - /** - * Generate a YAML job config file. - * @param script the file path. - */ - public async generateJobConfigV2(script?: string): Promise { - const cluster: IPAICluster = await this.pickCluster(); - const settings: vscode.WorkspaceConfiguration = await this.ensureSettingsV2(cluster); - let parent: string; - if (script) { - const workspace: any = script ? - vscode.workspace.getWorkspaceFolder(vscode.Uri.file(script)) : - vscode.workspace.workspaceFolders; - if (workspace === undefined) { - parent = path.dirname(script); - } else { - parent = workspace.uri.fsPath; - } - script = path.relative(parent, script); - } else { - parent = os.homedir(); - const folders: vscode.WorkspaceFolder[] | undefined = vscode.workspace.workspaceFolders; - if (!isEmpty(folders)) { - const fileFolders: vscode.WorkspaceFolder[] = folders!.filter(x => x.uri.scheme === 'file'); - if (!isEmpty(fileFolders)) { - parent = fileFolders[0].uri.fsPath; - } - } - } - - const jobName: string = script ? path.basename(script, path.extname(script)) : 'new_job'; - const defaultSaveDir: string = path.join(parent, `${jobName}.pai.yaml`); - - const runtimeplugin: any[] = [{ - plugin: 'ssh', - parameters: { - jobssh: true - } - }]; - - let sourceCodePath: string = '$PAI_JOB_NAME'; - const commands: string[] = []; - const uploadConfig: IPAIJobV2UploadConfig | undefined = settings.get(SETTING_JOB_V2_UPLOAD); - if (uploadConfig && uploadConfig[cluster.name!] && uploadConfig[cluster.name!].enable) { - const upload: IUploadConfig = uploadConfig[cluster!.name!]; - commands.push(`export PAI_AUTO_UPLOAD_DIR="${upload.storageMountPoint}"`); - sourceCodePath = '$PAI_AUTO_UPLOAD_DIR/$PAI_JOB_NAME'; - - runtimeplugin.push({ - plugin: 'teamwise_storage', - parameters: { - storageConfigNames: [upload.storageName] - } - }); - } - - if (script) { - commands.push(`python ${sourceCodePath}/${unixify(script)}`); - } else { - commands.push('python '); - } - - const config: IPAIJobConfigV2 = { - protocolVersion: 2, - name: jobName, - type: 'job', - prerequisites: [ - { - name: 'image', - type: 'dockerimage', - uri: 'aiplatform/pai.build.base' - } - ], - taskRoles: { - train: { - instances: 1, - dockerImage: 'image', - resourcePerInstance: { - cpu: 1, - memoryMB: 16384, - gpu: 1 - }, - commands: commands - } - }, - extras: { - 'com.microsoft.pai.runtimeplugin': runtimeplugin - } - }; - - const saveDir: vscode.Uri | undefined = await vscode.window.showSaveDialog({ - defaultUri: vscode.Uri.file(defaultSaveDir), - filters: { - YAML: ['yml', 'yaml'] - } - }); - - if (saveDir) { - await fs.writeFile(saveDir.fsPath, yaml.safeDump(config)); - await vscode.window.showTextDocument(saveDir); - } - } - - // tslint:disable-next-line - public async submitJob(input: IJobInput = {}): Promise { - const statusBarItem: vscode.StatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MAX_VALUE); - statusBarItem.text = `${OCTICON_CLOUDUPLOAD} ${__('job.prepare.status')}`; - statusBarItem.show(); - - try { - const param: IJobParam | undefined = await this.prepareJobParam(input); - if (!param) { - // Error message has been shown. - return; - } - if (!param.cluster) { - param.cluster = await this.pickCluster(); - } - - if (param.jobVersion === 2) { - await this.submitJobV2(param, statusBarItem); - } else { - await this.submitJobV1(param, statusBarItem); - } - } catch (e) { - Util.err('job.submission.error', [e.message || e]); - } finally { - statusBarItem.dispose(); - } - } - - public async onActivate(): Promise { - await registerYamlSchemaSupport(); - } - - private async submitJobV1(param: IJobParam, statusBarItem: vscode.StatusBarItem): Promise { - const config: IPAIJobConfigV1 = param.config; - const cluster: IPAICluster = param.cluster!; - - // add job name suffix - if (param.generateJobName) { - config.jobName = `${config.jobName}_${uuid().substring(0, 8)}`; - } else { - try { - await request.get(PAIRestUri.jobDetail(cluster, cluster.username!, config.jobName), { - headers: { Authorization: `Bearer ${await this.getToken(cluster)}` }, - timeout: PAIJobManager.TIMEOUT, - json: true - }); - // job exists - const ENABLE_GENERATE_SUFFIX: string = __('job.submission.name-exist.enable'); - const CANCEL: string = __('common.cancel'); - const res: string | undefined = await vscode.window.showErrorMessage( - __('job.submission.name-exist'), - ENABLE_GENERATE_SUFFIX, - CANCEL - ); - if (res === ENABLE_GENERATE_SUFFIX) { - await vscode.workspace.getConfiguration(SETTING_SECTION_JOB).update(SETTING_JOB_GENERATEJOBNAME_ENABLED, true); - config.jobName = `${config.jobName}_${uuid().substring(0, 8)}`; - } else { - // cancel - return; - } - } catch (e) { - if (e.response.body.code === 'NoJobError') { - // pass - } else { - throw new Error(e.status ? `${e.status}: ${e.response.body.message}` : e); - } - } - } - - // replace env variables - PAIJobManager.replaceVariables(param); - - // auto upload - statusBarItem.text = `${OCTICON_CLOUDUPLOAD} ${__('job.upload.status')}`; - if (param.upload) { - if (!await this.uploadCode(param)) { - return; - } - } - - // send job submission request - statusBarItem.text = `${OCTICON_CLOUDUPLOAD} ${__('job.request.status')}`; - try { - await request.post(PAIRestUri.jobs(cluster, cluster.username), { - headers: { Authorization: `Bearer ${await this.getToken(cluster)}` }, - form: param.config, - timeout: PAIJobManager.TIMEOUT, - json: true - }); - void (await getSingleton(RecentJobManager)).enqueueRecentJobs(cluster, config.jobName); - const open: string = __('job.submission.success.open'); - void vscode.window.showInformationMessage( - __('job.submission.success'), - open - ).then(async res => { - const url: string = await PAIWebPortalUri.jobDetail(param.cluster!, param.cluster!.username!, config.jobName); - if (res === open) { - await Util.openExternally(url); - } - }); - } catch (e) { - throw new Error(e.status ? `${e.status}: ${e.response.body.message}` : e); - } - } - - private async submitJobV2(param: IJobParam, statusBarItem: vscode.StatusBarItem): Promise { - const config: IPAIJobConfigV2 = param.config; - const cluster: IPAICluster = param.cluster!; - - // add job name suffix - if (param.generateJobName) { - config.name = `${config.name}_${uuid().substring(0, 8)}`; - } else { - try { - await request.get(PAIRestUri.jobDetail(cluster, cluster.username!, config.name), { - headers: { Authorization: `Bearer ${await this.getToken(cluster)}` }, - timeout: PAIJobManager.TIMEOUT, - json: true - }); - // job exists - const ENABLE_GENERATE_SUFFIX: string = __('job.submission.name-exist.enable'); - const CANCEL: string = __('common.cancel'); - const res: string | undefined = await vscode.window.showErrorMessage( - __('job.submission.name-exist'), - ENABLE_GENERATE_SUFFIX, - CANCEL - ); - if (res === ENABLE_GENERATE_SUFFIX) { - await vscode.workspace.getConfiguration(SETTING_SECTION_JOB).update(SETTING_JOB_GENERATEJOBNAME_ENABLED, true); - config.name = `${config.name}_${uuid().substring(0, 8)}`; - } else { - // cancel - return; - } - } catch (e) { - if (e.response.body.code === 'NoJobError') { - // pass - } else { - throw new Error(e.status ? `${e.status}: ${e.response.body.message}` : e); - } - } - } - - // auto upload - statusBarItem.text = `${OCTICON_CLOUDUPLOAD} ${__('job.upload.status')}`; - if (param.upload) { - if (!await this.uploadCodeV2(param)) { - return; - } - } - - statusBarItem.text = `${OCTICON_CLOUDUPLOAD} ${__('job.request.status')}`; - try { - await request.post( - PAIRestUri.jobsV2(cluster), - { - headers: { - Authorization: `Bearer ${await this.getToken(cluster)}`, - 'Content-Type': 'text/yaml' - }, - body: yaml.safeDump(config), - timeout: PAIJobManager.TIMEOUT - }); - void (await getSingleton(RecentJobManager)).enqueueRecentJobs(cluster, config.name); - const open: string = __('job.submission.success.open'); - void vscode.window.showInformationMessage( - __('job.submission.success'), - open - ).then(async res => { - const url: string = await PAIWebPortalUri.jobDetail(cluster, cluster.username!, config.name); - if (res === open) { - await Util.openExternally(url); - } - }); - } catch (e) { - throw new Error(e.status ? `${e.status}: ${e.response.body.message}` : e); - } - } - - // tslint:disable-next-line - public async simulate(input: IJobInput = {}): Promise { - const statusBarItem: vscode.StatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MAX_VALUE); - statusBarItem.text = `${OCTICON_CLOUDUPLOAD} ${__('job.simulation.status')}`; - statusBarItem.show(); - - try { - await this.prepareJobConfigPath(input); - const param: IJobParam | undefined = await this.prepareJobParam(input); - if (!param) { - // Error message has been shown. - return; - } - if (param.jobVersion === 2) { - await this.simulateV2(param); - } else { - await this.simulateV1(param); - } - } catch (e) { - Util.err('job.simulation.error', [e.message || e]); - } finally { - statusBarItem.dispose(); - } - } - - // tslint:disable-next-line - public async simulateV1(param: IJobParam): Promise { - const config: IPAIJobConfigV1 = param.config; - if (!param.cluster) { - let pickCluster: boolean = false; - // pick cluster if auto upload is disabled. - if (!param.upload) { - pickCluster = true; - } - if (PAIJobManager.envNeedClusterInfo.some( - x => config.codeDir.includes(`$${x}`) || config.taskRoles.some( - y => y.command.includes(`$${x}`) - ) - )) { - pickCluster = true; - } - - if (pickCluster) { - param.cluster = await this.pickCluster(); - } - } - - // replace env variables if auto upload is disabled - // extension will try to download files from hdfs instead of copying local files - if (!param.upload) { - PAIJobManager.replaceVariables(param); - } - - // generate dockerfile - const dockerfileDir: string = path.join(param.workspace, PAIJobManager.SIMULATION_DOCKERFILE_FOLDER); - const jobDir: string = path.join(dockerfileDir, config.jobName); - await fs.remove(jobDir); - await fs.ensureDir(jobDir); - let scriptName: string; - if (os.platform() === 'win32') { - scriptName = 'run-docker.cmd'; - } else { - scriptName = 'run-docker.sh'; - } - for (const role of config.taskRoles) { - // 0. init - const taskDir: string = path.join(jobDir, role.name); - await fs.ensureDir(taskDir); - const dockerfile: string[] = []; - // 1. comments - dockerfile.push('# Generated by OpenPAI VS Code Client'); - dockerfile.push(`# Job Name: ${config.jobName}`); - dockerfile.push(`# Task Name: ${role.name}`); - dockerfile.push(''); - // 2. from - dockerfile.push(`FROM ${config.image}`); - dockerfile.push(''); - // 3. source code - const codeDir: string = path.join(taskDir, config.jobName); - await fs.ensureDir(codeDir); - if (param.upload) { - // copy from local - const projectFiles: string[] = await globby(param.upload.include, { - cwd: param.workspace, - onlyFiles: true, - absolute: true, - ignore: param.upload.exclude || [] - }); - await Promise.all(projectFiles.map(async file => { - await fs.copy(file, path.join(codeDir, path.relative(param.workspace, file))); - })); - } else { - // copy from remote - const fsProvider: HDFSFileSystemProvider = (await getSingleton(HDFS)).provider!; - let remoteCodeDir: string = config.codeDir; - if (remoteCodeDir.startsWith('$PAI_DEFAULT_FS_URI')) { - remoteCodeDir = remoteCodeDir.substring('$PAI_DEFAULT_FS_URI'.length); - } - const remoteCodeUri: vscode.Uri = vscode.Uri.parse(`webhdfs://${getHDFSUriAuthority(param.cluster!)}${remoteCodeDir}`); - await fsProvider.copy(remoteCodeUri, vscode.Uri.file(codeDir), { overwrite: true }); - } - dockerfile.push('WORKDIR /pai'); - dockerfile.push(`COPY ${config.jobName} /pai/${config.jobName}`); - dockerfile.push(''); - // 4. env var - dockerfile.push('ENV PAI_WORK_DIR /pai'); - dockerfile.push(`ENV PAI_JOB_NAME ${config.jobName}`); - if (param.cluster) { - dockerfile.push(`ENV PAI_DEFAULT_FS_URI ${param.cluster.hdfs_uri}`); - dockerfile.push(`ENV PAI_USER_NAME ${param.cluster.username}`); - dockerfile.push(`ENV PAI_DATA_DIR ${config.dataDir}`); - dockerfile.push(`ENV PAI_CODE_DIR ${config.codeDir}`); - dockerfile.push(`ENV PAI_OUTPUT_DIR ${config.outputDir}`); - } - dockerfile.push(''); - // check unsupported env variables - const supportedEnvList: string[] = [ - 'PAI_WORK_DIR', - 'PAI_JOB_NAME', - 'PAI_DEFAULT_FS_URI', - 'PAI_USER_NAME', - 'PAI_DATA_DIR', - 'PAI_CODE_DIR', - 'PAI_OUTPUT_DIR' - ]; - let command: string = role.command; - for (const env of supportedEnvList) { - command = command.replace(new RegExp(`\\$${env}`, 'g'), ''); - } - if (command.includes('$PAI')) { - Util.warn('job.simulation.unsupported-env-var', role.command); - } - // 5. entrypoint - dockerfile.push(`ENTRYPOINT ["/bin/bash", "-c", "${role.command.replace(/"/g, '\\"')}}"]`); - dockerfile.push(''); - // 6. write dockerfile - await fs.writeFile(path.join(taskDir, 'dockerfile'), dockerfile.join('\n')); - // EX. write shell script - const imageName: string = `pai-simulator-${config.jobName}-${role.name}`; - await fs.writeFile( - path.join(taskDir, scriptName), - [ - `docker build -t ${imageName} ${Util.quote(taskDir)}`, - `docker run --rm ${imageName}`, - `docker rmi ${imageName}`, - os.platform() === 'win32' ? 'pause' : 'read -p "Press [Enter] to continue ..."' - ].join('\n') - ); - } - - const reveal: string = __('job.simulation.success-dialog.reveal'); - const runFirstTask: string = __('job.simulation.success-dialog.run-first-task'); - await vscode.window.showInformationMessage( - __('job.simulation.success', [PAIJobManager.SIMULATION_DOCKERFILE_FOLDER, config.jobName, scriptName]), - runFirstTask, - reveal - ).then((res) => { - if (res === reveal) { - void opn(jobDir); - } else if (res === runFirstTask) { - if (!this.simulateTerminal || !vscode.window.terminals.find(x => x.processId === this.simulateTerminal!.processId)) { - this.simulateTerminal = vscode.window.createTerminal('pai-simulator'); - } - this.simulateTerminal.show(true); - if (os.platform() === 'win32') { - this.simulateTerminal.sendText(`cmd /c "${path.join(jobDir, config.taskRoles[0].name, scriptName)}"`); - } else { - this.simulateTerminal.sendText(`bash '${path.join(jobDir, config.taskRoles[0].name, scriptName)}'`); - } - } - }); - } - - // tslint:disable-next-line - public async simulateV2(param: IJobParam): Promise { - const config: IPAIJobConfigV2 = param.config; - // generate dockerfile - const dockerfileDir: string = path.join(param.workspace, PAIJobManager.SIMULATION_DOCKERFILE_FOLDER); - const jobDir: string = path.join(dockerfileDir, config.name); - await fs.remove(jobDir); - await fs.ensureDir(jobDir); - let scriptName: string; - if (os.platform() === 'win32') { - scriptName = 'run-docker.cmd'; - } else { - scriptName = 'run-docker.sh'; - } - for (const [name, role] of Object.entries(config.taskRoles)) { - // 0. init - const taskDir: string = path.join(jobDir, name); - await fs.ensureDir(taskDir); - const dockerfile: string[] = []; - // 1. comments - dockerfile.push('# Generated by OpenPAI VS Code Client'); - dockerfile.push(`# Job Name: ${config.name}`); - dockerfile.push(`# Task Name: ${name}`); - dockerfile.push(''); - // 2. from - let image: any; - for (const prerequisite of config.prerequisites!) { - if (prerequisite.type === 'dockerimage' && prerequisite.name === role.dockerImage) { - image = prerequisite; - } - } - dockerfile.push(`FROM ${image.uri}`); - dockerfile.push(''); - // 3. source code - const codeDir: string = path.join(taskDir, config.name); - await fs.ensureDir(codeDir); - if (param.upload) { - // copy from local - const projectFiles: string[] = await globby(param.upload.include, { - cwd: param.workspace, - onlyFiles: true, - absolute: true, - ignore: param.upload.exclude || [] - }); - await Promise.all(projectFiles.map(async file => { - await fs.copy(file, path.join(codeDir, path.relative(param.workspace, file))); - })); - } - dockerfile.push('WORKDIR /pai'); - if (param.upload) { - const upload: IUploadConfig = param.upload; - if (upload.enable) { - dockerfile.push(`COPY ${config.name} /${upload.storageMountPoint}/${config.name}`); - } - } else { - dockerfile.push(`COPY ${config.name} /pai/${config.name}`); - } - dockerfile.push(''); - // 4. env var - dockerfile.push('ENV PAI_WORK_DIR /pai'); - dockerfile.push(`ENV PAI_JOB_NAME ${config.name}`); - if (param.cluster) { - dockerfile.push(`ENV PAI_DEFAULT_FS_URI ${param.cluster.hdfs_uri}`); - dockerfile.push(`ENV PAI_USER_NAME ${param.cluster.username}`); - } - // check unsupported env variables - const supportedEnvList: string[] = [ - 'PAI_JOB_NAME', - 'PAI_USER_NAME', - 'PAI_DEFAULT_FS_URI', - 'PAI_TASK_ROLE_COUNT', - 'PAI_TASK_ROLE_LIST', - 'PAI_TASK_ROLE_TASK_COUNT_*', - 'PAI_HOST_IP_*_*', - 'PAI_PORT_LIST_*_*_*', - 'PAI_RESOURCE_*', - 'PAI_MIN_FAILED_TASK_COUNT_*', - 'PAI_MIN_SUCCEEDED_TASK_COUNT_*', - 'PAI_CURRENT_TASK_ROLE_NAME', - 'PAI_CURRENT_TASK_ROLE_CURRENT_TASK_INDEX', - 'PAI_AUTO_UPLOAD_DIR' - ]; - const command: string = role.commands.join(' && ').replace(new RegExp(supportedEnvList.join('|'), 'g'), ''); - if (command.includes('$PAI')) { - Util.warn('job.simulation.unsupported-env-var', role.commands); - } - dockerfile.push(''); - // 5. entrypoint - dockerfile.push(`ENTRYPOINT ["/bin/bash", "-c", "${role.commands.join(' && ').replace(/"/g, '\\"')}"]`); - dockerfile.push(''); - // 6. write dockerfile - await fs.writeFile(path.join(taskDir, 'dockerfile'), dockerfile.join('\n')); - // EX. write shell script - const imageName: string = `pai-simulator-${config.name}-${name}`; - await fs.writeFile( - path.join(taskDir, scriptName), - [ - `docker build -t ${imageName} ${Util.quote(taskDir)}`, - `docker run --rm ${imageName}`, - `docker rmi ${imageName}`, - os.platform() === 'win32' ? 'pause' : 'read -p "Press [Enter] to continue ..."' - ].join('\n') - ); - } - - const reveal: string = __('job.simulation.success-dialog.reveal'); - const runFirstTask: string = __('job.simulation.success-dialog.run-first-task'); - await vscode.window.showInformationMessage( - __('job.simulation.success', [PAIJobManager.SIMULATION_DOCKERFILE_FOLDER, config.name, scriptName]), - runFirstTask, - reveal - ).then((res) => { - if (res === reveal) { - void opn(jobDir); - } else if (res === runFirstTask) { - if (!this.simulateTerminal || !vscode.window.terminals.find(x => x.processId === this.simulateTerminal!.processId)) { - this.simulateTerminal = vscode.window.createTerminal('pai-simulator'); - } - this.simulateTerminal.show(true); - if (os.platform() === 'win32') { - this.simulateTerminal.sendText(`cmd /c "${path.join(jobDir, Object.keys(config.taskRoles)[0], scriptName)}"`); - } else { - this.simulateTerminal.sendText(`bash '${path.join(jobDir, Object.keys(config.taskRoles)[0], scriptName)}'`); - } - } - }); - } - - private async pickCluster(): Promise { - const clusterManager: ClusterManager = await getSingleton(ClusterManager); - const pickResult: number | undefined = await clusterManager.pick(); - if (pickResult === undefined) { - throw new Error(__('job.prepare.cluster.cancelled')); - } - return clusterManager.allConfigurations[pickResult]; - } - - private async pickUploadStorage(cluster: IPAICluster, config: IPAIJobV2UploadConfig): Promise { - const mountPoints: { - storage: string; - mountPoint: string; - }[] = await StorageHelper.getStorageMountPoints(cluster); - - let pickPersonalStorage: boolean = true; - - if (mountPoints.length > 0) { - const CLUSTER: vscode.QuickPickItem = { - label: __('common.cluster.storage') - }; - const PERSONAL: vscode.QuickPickItem = { - label: __('common.personal.storage') - }; - const item: vscode.QuickPickItem | undefined = await Util.pick( - [CLUSTER, PERSONAL], - __('job.prepare.upload.storage.type') - ); - - if (item !== PERSONAL) { - const pickStorage: number | undefined = - await Util.pick(range(mountPoints.length), __('storage.upload.pick.prompt'), (index: number) => { - const str: string = mountPoints[index].storage + ':' + mountPoints[index].mountPoint; - return {label: str}; - }); - if (pickStorage !== undefined) { - config[cluster.name!] = { - enable: true, - include: ['**/*.py'], - exclude: [], - storageType: 'cluster', - storageName: mountPoints[pickStorage].storage, - storageMountPoint: mountPoints[pickStorage].mountPoint - }; - } else { - config[cluster.name!] = { - enable: false, - include: ['**/*.py'], - exclude: [] - }; - } - pickPersonalStorage = false; - } - } - - if (pickPersonalStorage) { - const personalStorages: string[] = await StorageHelper.getPersonalStorages(); - const pickStorage: number | undefined = - await Util.pick(range(personalStorages.length), __('storage.upload.pick.prompt'), (index: number) => { - const str: string = personalStorages[index]; - return {label: str}; - }); - if (pickStorage !== undefined) { - config[cluster.name!] = { - enable: true, - include: ['**/*.py'], - exclude: [], - storageType: 'personal', - storageName: personalStorages[pickStorage] - }; - } else { - config[cluster.name!] = { - enable: false, - include: ['**/*.py'], - exclude: [] - }; - } - } - - return config; - } - - private async prepareJobConfigPath(jobInput: IJobInput): Promise { - if (!jobInput.jobConfigPath) { - Util.info('job.prepare.config.prompt'); - const folders: vscode.WorkspaceFolder[] | undefined = vscode.workspace.workspaceFolders; - const jobConfigUrl: vscode.Uri[] | undefined = await vscode.window.showOpenDialog({ - canSelectFiles: true, - canSelectMany: false, - defaultUri: !isEmpty(folders) ? folders![0].uri : undefined - }); - if (isEmpty(jobConfigUrl)) { - Util.err('job.prepare.cluster.cancelled'); - return; - } - jobInput.jobConfigPath = jobConfigUrl![0].fsPath; - } - } - - private async prepareJobParam(jobInput: IJobInput): Promise { - const result: Partial = {}; - // 1. job config - await this.prepareJobConfigPath(jobInput); - const jobConfigPath: string | undefined = jobInput.jobConfigPath; - const clusterIndex: number | undefined = jobInput.clusterIndex; - const jobVersion: number = (jobConfigPath!.toLowerCase().endsWith('yaml') || jobConfigPath!.toLowerCase().endsWith('yml')) ? 2 : 1; - result.jobVersion = jobVersion; - let config: IPAIJobConfigV1 | IPAIJobConfigV2; - - let error: string | undefined; - config = jobVersion === 2 ? - yaml.safeLoad(await fs.readFile(jobConfigPath!, 'utf8')) : JSONC.parse(await fs.readFile(jobConfigPath!, 'utf8')); - if (isNil(config)) { - Util.err('job.prepare.config.invalid'); - } - error = jobVersion === 2 ? - await Util.validateJSON(config, SCHEMA_YAML_JOB_CONFIG) : await Util.validateJSON(config, SCHEMA_JOB_CONFIG); - if (error) { - throw new Error(error); - } - result.config = config; - - // 2. workspace - const workspace: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(jobConfigPath!)); - if (!workspace) { - throw new Error(__('common.workspace.nofolder')); - } - result.workspace = workspace.uri.fsPath; - - // 3. cluster - if (clusterIndex) { - const clusterManager: ClusterManager = await getSingleton(ClusterManager); - result.cluster = clusterManager.allConfigurations[clusterIndex]; - } else { - result.cluster = await this.pickCluster(); - } - - // 4. settings - const settings: vscode.WorkspaceConfiguration = jobVersion === 1 ? - await PAIJobManager.ensureSettingsV1() : await this.ensureSettingsV2(result.cluster); - if (jobVersion === 1 && settings.get(SETTING_JOB_UPLOAD_ENABLED)) { - result.upload = { - include: settings.get(SETTING_JOB_UPLOAD_INCLUDE)!, - exclude: settings.get(SETTING_JOB_UPLOAD_EXCLUDE)! - }; - } - if (jobVersion === 2) { - const uploadConfig: IPAIJobV2UploadConfig | undefined = settings.get(SETTING_JOB_V2_UPLOAD); - if (uploadConfig && uploadConfig[result.cluster!.name!] && uploadConfig[result.cluster!.name!].enable) { - result.upload = uploadConfig[result.cluster!.name!]; - } - } - result.generateJobName = settings.get(SETTING_JOB_GENERATEJOBNAME_ENABLED); - - return result; - } - - private async getToken(cluster: IPAICluster): Promise { - if (cluster.token) { - return cluster.token; - } - - const id: string = getClusterIdentifier(cluster); - let item: ITokenItem | undefined = this.cachedTokens.get(id); - if (!item || Date.now() > item.expireTime) { - const result: any = await request.post(PAIRestUri.token(cluster), { - form: { - username: cluster.username, - password: cluster.password, - expiration: 4000 - }, - timeout: PAIJobManager.TIMEOUT, - json: true - }); - item = { - token: result.token, - expireTime: Date.now() + 3600 * 1000 - }; - this.cachedTokens.set(id, item); - } - - return item.token; - } - - private async uploadCode(param: IJobParam): Promise { - const config: IJobConfigV1 = param.config; - - if (!param.cluster!.webhdfs_uri) { - Util.err('pai.webhdfs.missing'); - return false; - } - - try { - // Avoid using vscode.workspace.findFiles for now - webhdfs:// folder in workspace will raise exception - const projectFiles: string[] = await globby(param.upload!.include, { - cwd: param.workspace, onlyFiles: true, absolute: true, - ignore: param.upload!.exclude || [] - }); - const fsProvider: HDFSFileSystemProvider = (await getSingleton(HDFS)).provider!; - let codeDir: string = config.codeDir; - if (codeDir.startsWith('hdfs://') || codeDir.startsWith('webhdfs://')) { - throw new Error(__('job.upload.invalid-code-dir')); - } else { - if (codeDir.startsWith('$PAI_DEFAULT_FS_URI')) { - codeDir = codeDir.substring('$PAI_DEFAULT_FS_URI'.length); - } - codeDir = path.posix.resolve('/', codeDir); - } - - const codeUri: vscode.Uri = vscode.Uri.parse(`webhdfs://${getHDFSUriAuthority(param.cluster!)}${codeDir}`); - - const total: number = projectFiles.length; - const createdDirectories: Set = new Set([ codeUri.path ]); - await fsProvider.createDirectory(codeUri); - - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: __('job.upload.status') - }, - async (progress) => { - for (const [i, file] of projectFiles.entries()) { - const suffix: string = path.relative(param.workspace, file); - const baseFolder: string = path.dirname(suffix); - if (baseFolder !== '.') { - const baseFolderUri: vscode.Uri = Util.uriPathAppend(codeUri, path.dirname(suffix)); - if (!createdDirectories.has(baseFolderUri.path)) { - createdDirectories.add(baseFolderUri.path); - await fsProvider.createDirectory(baseFolderUri); - } - } - progress.report({ - message: __('job.upload.progress', [i + 1, total]), - increment: 1 / total * 100 - }); - await fsProvider.copy(vscode.Uri.file(file), Util.uriPathAppend(codeUri, suffix), { overwrite: true }); - } - } - ); - - return true; - } catch (e) { - Util.err('job.upload.error', [e.message]); - return false; - } - } - - private async uploadCodeV2(param: IJobParam): Promise { - const config: IPAIJobConfigV2 = param.config; - - try { - const projectFiles: string[] = await globby(param.upload!.include, { - cwd: param.workspace, onlyFiles: true, absolute: true, - ignore: param.upload!.exclude || [] - }); - - const uploadConfig: IUploadConfig = param.upload; - const total: number = projectFiles.length; - - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: __('job.upload.status') - }, - async (progress) => { - for (const [i, file] of projectFiles.entries()) { - progress.report({ - message: __('job.upload.progress', [i + 1, total]), - increment: 1 / total * 100 - }); - const suffix: string = path.relative(param.workspace, file); - await StorageHelper.uploadFile( - uploadConfig, - param.cluster!.name!, - `${param.cluster!.username!}~${config.name}`, - vscode.Uri.file(file), - suffix - ); - } - } - ); - - return true; - } catch (e) { - Util.err('job.upload.error', [e.message]); - return false; - } - } -} diff --git a/contrib/pai_vscode/src/pai/paiWebpages.ts b/contrib/pai_vscode/src/pai/paiWebpages.ts deleted file mode 100644 index cfce24ad47..0000000000 --- a/contrib/pai_vscode/src/pai/paiWebpages.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { injectable } from 'inversify'; -import * as vscode from 'vscode'; - -import { - COMMAND_LIST_JOB, COMMAND_OPEN_DASHBOARD, COMMAND_TREEVIEW_OPEN_PORTAL, COMMAND_VIEW_JOB -} from '../common/constants'; -import { __ } from '../common/i18n'; -import { getSingleton, Singleton } from '../common/singleton'; -import { Util } from '../common/util'; - -import { getClusterName, ClusterManager } from './clusterManager'; -import { ClusterNode } from './container/common/clusterNode'; -import { ClusterExplorerChildNode } from './container/configurationTreeDataProvider'; -import { IPAICluster, IPAIJobInfo } from './utility/paiInterface'; -import { PAIWebPortalUri } from './utility/paiUri'; - -const paiDashboardPropertyLabelMapping: { [propertyName: string]: string } = { - grafana_uri: 'Grafana', - k8s_dashboard_uri: 'Kubernates', - webhdfs_uri: 'Webhdfs' -}; - -/** - * Provides functionalities to open webpages on PAI - */ -@injectable() -export class PAIWebpages extends Singleton { - - constructor() { - super(); - this.context.subscriptions.push( - vscode.commands.registerCommand( - COMMAND_OPEN_DASHBOARD, - this.openDashboard.bind(this) - ), - vscode.commands.registerCommand( - COMMAND_TREEVIEW_OPEN_PORTAL, - (node: ClusterExplorerChildNode) => this.openDashboardFromTreeView(node.index) - ), - vscode.commands.registerCommand( - COMMAND_LIST_JOB, - (node: ClusterExplorerChildNode | ClusterNode) => this.listJobs(node.index) - ), - vscode.commands.registerCommand( - COMMAND_VIEW_JOB, - this.viewJob.bind(this) - ) - ); - } - - public async openDashboard(): Promise { - const index: number | undefined = await (await getSingleton(ClusterManager)).pick(); - if (index === undefined) { - return; - } - const config: IPAICluster = (await getSingleton(ClusterManager)).allConfigurations![index]; - - const options: vscode.QuickPickItem[] = []; - const paiUrl: string = PAIWebPortalUri.getClusterWebPortalUri(config); - options.push({ - label: __('webpage.dashboard.webportal', [getClusterName(config)]), - detail: paiUrl - }); - - for (const key of Object.keys(paiDashboardPropertyLabelMapping)) { - if (key in config) { - options.push({ - label: paiDashboardPropertyLabelMapping[key], - detail: (config)[key] - }); - } - } - - const result: vscode.QuickPickItem | undefined = await Util.pick( - options, - __('webpage.dashboard.pick.prompt') - ); - if (!result) { - Util.err('webpage.dashboard.pick.error'); - } else if (result.detail) { - await Util.openExternally(result.detail); - } - } - - public async openDashboardFromTreeView(index: number): Promise { - const config: IPAICluster = (await getSingleton(ClusterManager)).allConfigurations![index]; - const url: string = PAIWebPortalUri.getClusterWebPortalUri(config); - await Util.openExternally(url); - } - - public async listJobs(index: number): Promise { - const config: IPAICluster = (await getSingleton(ClusterManager)).allConfigurations![index]; - const url: string = await PAIWebPortalUri.jobs(config); - await Util.openExternally(url); - } - - public async viewJob(jobInfo: IPAIJobInfo, config: IPAICluster): Promise { - const url: string = await PAIWebPortalUri.jobDetail(config, jobInfo.username, jobInfo.name); - await Util.openExternally(url); - } -} diff --git a/contrib/pai_vscode/src/pai/recentJobManager.ts b/contrib/pai_vscode/src/pai/recentJobManager.ts deleted file mode 100644 index 128983a79d..0000000000 --- a/contrib/pai_vscode/src/pai/recentJobManager.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { injectable } from 'inversify'; -import * as vscode from 'vscode'; - -import { - SETTING_JOB_JOBLIST_RECENTJOBSLENGTH, SETTING_SECTION_JOB -} from '../common/constants'; -import { __ } from '../common/i18n'; -import { getSingleton, Singleton } from '../common/singleton'; - -import { ClusterManager } from './clusterManager'; -import { JobListTreeDataProvider } from './container/jobListTreeView'; -import { IPAICluster } from './utility/paiInterface'; - -/** - * Manager class for cluster configurations - */ -@injectable() -export class RecentJobManager extends Singleton { - private static readonly RECENT_JOBS_KEY: string = 'openpai.recentJobs'; - - private onClusterChangeDisposable: vscode.Disposable | undefined; - private recentJobs: (string[] | undefined)[] | undefined; - - public get allRecentJobs(): (string[] | undefined)[] { - return this.recentJobs!; - } - - public async onActivate(): Promise { - this.onClusterChangeDisposable = (await getSingleton(ClusterManager)).onDidChange(modification => { - switch (modification.type) { - case 'EDIT': - this.allRecentJobs[modification.index] = []; - break; - case 'REMOVE': - this.allRecentJobs.splice(modification.index, 1); - break; - case 'RESET': - this.recentJobs = []; - break; - default: - } - void this.saveRecentJobs(); - }); - this.recentJobs = this.context.globalState.get(RecentJobManager.RECENT_JOBS_KEY) || []; - } - - public async saveRecentJobs(index: number = -1): Promise { - await this.context.globalState.update(RecentJobManager.RECENT_JOBS_KEY, this.allRecentJobs); - const provider: JobListTreeDataProvider = await getSingleton(JobListTreeDataProvider); - await provider.refresh(index); - if (index !== -1) { - const latestJobName: string | undefined = (this.allRecentJobs[index] || [])[0]; - if (latestJobName) { - await provider.revealLatestJob(index, latestJobName); - } - } - } - - public async enqueueRecentJobs(cluster: IPAICluster, jobName: string): Promise { - const index: number = (await getSingleton(ClusterManager)).allConfigurations.findIndex(c => c === cluster); - if (index === -1) { - return; - } - const list: string[] = this.allRecentJobs[index] = this.allRecentJobs[index] || []; - list.unshift(jobName); - const settings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(SETTING_SECTION_JOB); - const maxLen: number = settings.get(SETTING_JOB_JOBLIST_RECENTJOBSLENGTH)!; - list.splice(maxLen); // Make sure not longer than maxLen - await this.saveRecentJobs(index); - } - - public onDeactivate(): void { - if (this.onClusterChangeDisposable) { - this.onClusterChangeDisposable.dispose(); - } - } -} \ No newline at end of file diff --git a/contrib/pai_vscode/src/pai/storage/azureBlobManager.ts b/contrib/pai_vscode/src/pai/storage/azureBlobManager.ts deleted file mode 100644 index b676b24ed0..0000000000 --- a/contrib/pai_vscode/src/pai/storage/azureBlobManager.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { - BlobGetPropertiesResponse, - BlockBlobClient, - ContainerClient -} from '@azure/storage-blob'; -import * as path from 'path'; -import { - window, - StatusBarAlignment, - StatusBarItem, - Uri -} from 'vscode'; - -import { - OCTICON_CLOUDDOWNLOAD, - OCTICON_CLOUDUPLOAD -} from '../../common/constants'; -import { __ } from '../../common/i18n'; -import { Util } from '../../common/util'; -import { StorageTreeNode } from '../container/common/treeNode'; -import { - AzureBlobRootItem, AzureBlobTreeItem, BlobEntity, BlobIter, BlobValue -} from '../container/storage/azureBlobTreeItem'; - -/** - * Azure blob management module - */ -// tslint:disable-next-line: no-unnecessary-class -export class AzureBlobManager { - public static async delete(target: AzureBlobTreeItem): Promise { - try { - if (target.blob.kind === 'blob') { - await target.client.deleteBlob(target.blob.name); - } else { - await this.deleteBlobsByHierarchy(target.client, target.blob.name); - } - Util.info('storage.delete.success'); - } catch (err) { - Util.err('storage.delete.error', [err]); - } - - await ( target.parent).refresh(); - } - - public static async deleteBlobsByHierarchy(client: ContainerClient, prefix: string): Promise { - const iter: BlobIter = client.listBlobsByHierarchy('/', { - prefix: prefix - }); - let blobItem: BlobEntity = await iter.next(); - while (!blobItem.done) { - const blob: BlobValue = blobItem.value; - if (blob.kind === 'blob') { - await client.deleteBlob(blob.name); - } else { - try { - await client.deleteBlob(blob.name.slice(0, -1)); - } catch { } - await this.deleteBlobsByHierarchy(client, blob.name); - } - blobItem = await iter.next(); - } - } - - public static async downloadFile(target: AzureBlobTreeItem, filePath?: Uri): Promise { - if (!filePath) { - filePath = await window.showSaveDialog({ - saveLabel: __('storage.dialog.label.download'), - defaultUri: Uri.file(target.blob.name) - }); - if (!filePath || filePath.scheme !== 'file') { - return; - } - } - - const client: BlockBlobClient = target.client.getBlockBlobClient(target.blob.name); - const properties: BlobGetPropertiesResponse = await client.getProperties(); - let totalBytes: number = 1; - if (properties && properties.contentLength) { - totalBytes = properties.contentLength; - } - - const statusBarItem: StatusBarItem = - window.createStatusBarItem(StatusBarAlignment.Right, Number.MAX_VALUE); - statusBarItem.text = - `${OCTICON_CLOUDUPLOAD} ${__('storage.download.status', [0, totalBytes])}`; - statusBarItem.show(); - try { - await client.downloadToFile(filePath.fsPath, undefined, undefined, { - onProgress: event => { - statusBarItem.text = - `${OCTICON_CLOUDDOWNLOAD} ${__('storage.download.status', [event.loadedBytes, totalBytes])}`; - } - }); - Util.info('storage.download.success'); - } catch (err) { - Util.err('storage.download.error', [err]); - } - statusBarItem.dispose(); - } - - public static async createFolder(target: AzureBlobTreeItem | AzureBlobRootItem, folder?: string): Promise { - if (folder) { - const blobName: string = path.join(target.rootPath, folder); - try { - await target.client.getBlockBlobClient(blobName).upload('', 0, { - metadata: { - hdi_isfolder: 'true' - } - }); - } catch (err) { - Util.err('storage.create.folder.error', [err]); - } - } else { - const name: string | undefined = await window.showInputBox({ - prompt: __('container.azure.blob.mkdir.prompt') - }); - if (name === undefined) { - Util.warn('container.azure.blob.mkdir.cancelled'); - return; - } - - const blobName: string = path.join(target.rootPath, name); - try { - await target.client.getBlockBlobClient(blobName).upload('', 0, { - metadata: { - hdi_isfolder: 'true' - } - }); - - Util.info('storage.create.folder.success'); - } catch (err) { - Util.err('storage.create.folder.error', [err]); - } - } - } - - public static async uploadFiles(target: AzureBlobTreeItem | AzureBlobRootItem, files?: Uri[]): Promise { - if (!files) { - files = await window.showOpenDialog({ - canSelectFiles: true, - canSelectMany: true, - openLabel: __('storage.dialog.label.upload-files') - }); - if (!files) { - return; - } - } - - const statusBarItem: StatusBarItem = - window.createStatusBarItem(StatusBarAlignment.Right, Number.MAX_VALUE); - statusBarItem.text = - `${OCTICON_CLOUDUPLOAD} ${__('storage.upload.status', [0, files.length])}`; - statusBarItem.show(); - try { - for (const [i, file] of files.entries()) { - const name: string = path.basename(file.fsPath); - const blobName: string = path.join(target.rootPath, name); - statusBarItem.text = - `${OCTICON_CLOUDUPLOAD} ${__('storage.upload.status', [i, files.length])}`; - const client: BlockBlobClient = target.client.getBlockBlobClient(blobName); - await client.uploadFile(file.fsPath); - } - Util.info('storage.upload.success'); - } catch (err) { - Util.err('storage.upload.error', [err]); - } - statusBarItem.dispose(); - } - - public static async uploadFolders( - target: StorageTreeNode, - folders?: Uri[] - ): Promise { - if (!folders) { - folders = await window.showOpenDialog({ - canSelectFolders: true, - canSelectMany: true, - openLabel: __('storage.dialog.label.upload-folders') - }); - if (!folders) { - return; - } - } - console.log('not implemented.'); - } -} diff --git a/contrib/pai_vscode/src/pai/storage/hdfs.ts b/contrib/pai_vscode/src/pai/storage/hdfs.ts deleted file mode 100644 index 72fdda77b3..0000000000 --- a/contrib/pai_vscode/src/pai/storage/hdfs.ts +++ /dev/null @@ -1,637 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as fs from 'fs-extra'; -import { injectable } from 'inversify'; -import * as path from 'path'; -import { Request } from 'request'; -import { Transform } from 'stream'; -import * as streamifier from 'streamifier'; -import unixify = require('unixify'); // tslint:disable-line -import { promisify } from 'util'; -import * as vscode from 'vscode'; - -import { - COMMAND_HDFS_DOWNLOAD, - COMMAND_HDFS_UPLOAD_FILES, - COMMAND_HDFS_UPLOAD_FOLDERS, - COMMAND_OPEN_HDFS, - ENUM_HDFS_EXPLORER_LOCATION, - OCTICON_CLOUDUPLOAD, - SETTING_HDFS_EXPLORER_LOCATION, - SETTING_SECTION_HDFS -} from '../../common/constants'; -import { __ } from '../../common/i18n'; -import { getSingleton, Singleton } from '../../common/singleton'; -import { Util } from '../../common/util'; -import { ClusterManager } from '../clusterManager'; -import { ClusterExplorerChildNode } from '../container/configurationTreeDataProvider'; -import { HDFSTreeDataProvider } from '../container/hdfsTreeView'; -import { IPAICluster } from '../utility/paiInterface'; - -import { createWebHDFSClient, IHDFSClient, IHDFSStatResult } from './webhdfs-workaround'; - -const stat: (path: string) => Promise = promisify(fs.stat); -const readdir: (path: string) => Promise = promisify(fs.readdir); -const mkdir: (path: string) => Promise = fs.mkdirp; - -export function getHDFSUriAuthority(configuration: IPAICluster): string { - return `${configuration.username}@${configuration.webhdfs_uri && configuration.webhdfs_uri.split('/')[0]}`; -} - -/** - * FileSystemProvider for webhdfs - */ -export class HDFSFileSystemProvider implements vscode.FileSystemProvider { - private static readonly functionsToBePromisified: (keyof IHDFSClient)[] = ['mkdir', 'readdir', 'stat', 'unlink', 'rename']; - - private onDidChangeFileEmitter: vscode.EventEmitter = new vscode.EventEmitter(); - public onDidChangeFile: vscode.Event = this.onDidChangeFileEmitter.event; // tslint:disable-line - - private clientMap: Map = new Map(); - - // `${username}@${host}:${port}` (e.g. user@127.0.0.1:50070) as authority - public async addClient(authority: string): Promise { - if (this.clientMap.has(authority)) { - return; - } - const [user, uri] = authority.split('@'); - const [host, port = '80'] = uri.split(':'); - const allConfigurations: IPAICluster[] = (await getSingleton(ClusterManager)).allConfigurations; - const currentCluster: IPAICluster | undefined = allConfigurations.find(cluster => - !!(cluster.username === user && cluster.webhdfs_uri && cluster.webhdfs_uri.startsWith(uri + '/')) - ); - if (!currentCluster || !currentCluster.webhdfs_uri) { - throw new Error(`Missing PAI cluster configuration for HDFS '${authority}'`); - } - const apiPath: string = currentCluster.webhdfs_uri.substr(currentCluster.webhdfs_uri.indexOf('/')); - const client: IHDFSClient = createWebHDFSClient( - { host, port, user, path: apiPath }, - { timeout: 60 * 1000 } - ); - for (const key of HDFSFileSystemProvider.functionsToBePromisified) { - client[key] = promisify(client[key]); - } - this.clientMap.set(authority, client); - return client; - } - - public async getClient(uri: vscode.Uri): Promise { - let result: IHDFSClient | undefined = this.clientMap.get(uri.authority); - if (!result) { - result = await this.addClient(uri.authority); - if (!result) { - throw vscode.FileSystemError.Unavailable(uri); - } - } - return result; - } - - // Implicitly recursive create - public async createDirectory(uri: vscode.Uri): Promise { - if (uri.scheme === 'file') { - // Extra logic. Not required for vscode - await mkdir(uri.fsPath); - return; - } - - let statResult: vscode.FileStat; - try { - statResult = await this.stat(uri); - if (statResult.type !== vscode.FileType.Directory) { - throw vscode.FileSystemError.FileExists(uri); - } else { - // pass - } - } catch (e) { - if (uri.path === '/') { - throw e; - } - await this.createDirectory(Util.uriPathPop(uri)); - try { - await (await this.getClient(uri)).mkdir(path.join('/', uri.path)); - this.onDidChangeFileEmitter.fire([{ type: vscode.FileChangeType.Created, uri }]); - } catch (ex) { - throw new vscode.FileSystemError(ex); - } - } - } - - public async delete(uri: vscode.Uri, options: {recursive: boolean}): Promise { - try { - await (await this.getClient(uri)).unlink(path.join('/', uri.path), options.recursive); - this.onDidChangeFileEmitter.fire([{ type: vscode.FileChangeType.Deleted, uri }]); - } catch (ex) { - throw new vscode.FileSystemError(ex); - } - } - - public async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { - if (uri.scheme === 'file') { - // Extra logic. Not required for vscode - const rawResult: string[] = await readdir(uri.fsPath); - const result: [string, vscode.FileType][] = []; - for (const file of rawResult) { - const isDirectory: boolean = (await stat(path.join(uri.fsPath, file))).isDirectory(); - result.push([file, isDirectory ? vscode.FileType.Directory : vscode.FileType.File]); - } - return result; - } - - try { - const rawResult: IHDFSStatResult[] = await (await this.getClient(uri)).readdir(path.join('/', uri.path)); - return rawResult.map(item => - Util.tuple([item.pathSuffix, item.type === 'DIRECTORY' ? vscode.FileType.Directory : vscode.FileType.File])); - } catch (ex) { - throw new vscode.FileSystemError(ex); - } - } - - public async readFile(uri: vscode.Uri): Promise { - const client: IHDFSClient = (await this.getClient(uri)); - const filePath: string = path.join('/', uri.path); - return await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: __('hdfs.downloading', [filePath]), - cancellable: true - }, - (progress, cancellationToken) => new Promise(async (resolve, reject) => { - let fileStat: IHDFSStatResult; - try { - fileStat = await client.stat(filePath); - if (fileStat.type === 'DIRECTORY') { - reject(vscode.FileSystemError.FileIsADirectory(uri)); - return; - } - } catch { - reject(vscode.FileSystemError.FileNotFound(uri)); - return; - } - const stream: fs.ReadStream = client.createReadStream(filePath); - const data: Buffer[] = []; - let readAmount: number = 0; - let error: any; - - cancellationToken.onCancellationRequested(() => { - error = true; - stream.close(); - reject(__('hdfs.read.cancelled')); - }); - - stream.once('error', err => { - error = err; - stream.close(); - reject(new vscode.FileSystemError(err)); - }); - - stream.on('data', (chunk: Buffer) => { - data.push(chunk); - readAmount += chunk.byteLength; - progress.report({ - message: __('hdfs.progress', [ - (readAmount / fileStat.length * 100).toFixed(), readAmount, fileStat.length - ]), - increment: chunk.byteLength / fileStat.length * 100 - }); - }); - - stream.once('finish', () => { - if (!error) { - resolve(Buffer.concat(data)); - } - }); - })); - } - - public async rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: {overwrite: boolean}): Promise { - // Check if this operation won't move file out of current folder - if (oldUri.scheme === newUri.scheme && - oldUri.authority === newUri.authority && - path.dirname(oldUri.path) === path.dirname(newUri.path)) { - if (!options.overwrite) { - try { - await this.stat(newUri); - throw vscode.FileSystemError.FileExists(newUri); - } catch { } - } - - const oldPath: string = unixify(path.join('/', oldUri.path)); - const newPath: string = unixify(path.join('/', newUri.path)); - try { - await (await this.getClient(oldUri)).rename(oldPath, newPath); - } catch (ex) { - throw new vscode.FileSystemError(ex); - } - } else { - await this.copy(oldUri, newUri, options); - await this.delete(oldUri, { recursive: true }); - } - } - - public async stat(uri: vscode.Uri): Promise { - try { - if (uri.scheme === 'file') { - // Extra logic. Not required for vscode - const rawStat: fs.Stats = await stat(uri.fsPath); - return { - size: rawStat.size, - ctime: rawStat.ctime.getTime(), - mtime: rawStat.mtime.getTime(), - type: rawStat.isDirectory() ? vscode.FileType.Directory : vscode.FileType.File - }; - } - const result: IHDFSStatResult = await (await this.getClient(uri)).stat(path.join('/', uri.path)); - return { - size: result.length, - ctime: result.modificationTime, - mtime: result.modificationTime, - type: result.type === 'DIRECTORY' ? vscode.FileType.Directory : vscode.FileType.File - }; - } catch (ex) { - throw new vscode.FileSystemError(ex); - } - } - - public watch(uri: vscode.Uri, options: {excludes: string[], recursive: boolean}): vscode.Disposable { - return new vscode.Disposable(() => undefined); - } - - public async writeFile(uri: vscode.Uri, content: Uint8Array, options: {create: boolean, overwrite: boolean}): Promise { - const client: IHDFSClient = (await this.getClient(uri)); - const filePath: string = path.join('/', uri.path); - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: __('hdfs.uploading', [filePath]), - cancellable: true - }, - (progress, cancellationToken) => new Promise(async (resolve, reject) => { - try { - if ((await client.stat(filePath)).type === 'DIRECTORY') { - reject(vscode.FileSystemError.FileIsADirectory(uri)); - return; - } - if (!options.overwrite) { - reject(vscode.FileSystemError.FileExists(uri)); - return; - } - } catch { - if (!options.create) { - reject(vscode.FileSystemError.FileNotFound(uri)); - return; - } - } - const local: fs.ReadStream = streamifier.createReadStream(content); - let writeAmount: number = 0; - const transform: Transform = new Transform({ - transform: (chunk: string | Buffer, encoding: string, callback: Function) => { - writeAmount += chunk.length; - progress.report({ - message: __('hdfs.progress', [ - (writeAmount / content.length * 100).toFixed(0), writeAmount, content.length - ]), - increment: chunk.length / content.length * 100 - }); - callback(null, chunk); - } - }); - const stream: Request = await client.createRobustWriteStream(filePath); - let error: any; - - function cleanup(): void { - local.unpipe(); - transform.unpipe(); - local.destroy(); - transform.destroy(); - stream.destroy(); - } - - cancellationToken.onCancellationRequested(() => { - error = true; - cleanup(); - reject(__('hdfs.write.cancelled')); - }); - - local.once('error', err => { - error = err; - cleanup(); - reject(new vscode.FileSystemError(err)); - }); - stream.once('error', err => { - error = err; - cleanup(); - reject(new vscode.FileSystemError(err)); - }); - - stream.once('finish', () => { - cleanup(); - if (!error) { - resolve(); - } - }); - - // TODO: local.pipe(transform).pipe(stream); is not working due to unknown reason...maybe a bug in node-webhdfs? - local.pipe(transform).pipe(stream); - }) - ); - this.onDidChangeFileEmitter.fire([{ type: vscode.FileChangeType.Created, uri }]); - } - - public async copy(source: vscode.Uri, destination: vscode.Uri, options: { overwrite: boolean }): Promise { - const sourceStat: vscode.FileStat = await this.stat(source); - let destinationStat: vscode.FileStat | undefined; - try { - destinationStat = await this.stat(destination); - } catch { } - const sourceIsDir: boolean = sourceStat.type === vscode.FileType.Directory; - const destinationIsDir: boolean | undefined = destinationStat && destinationStat.type === vscode.FileType.Directory; - if (sourceIsDir) { - if (destinationIsDir === undefined) { - await this.copyFolderToFutureFolder(source, destination, options); - return; - } else if (destinationIsDir) { - await this.copyFolderToFolder(source, destination, options); - return; - } - throw vscode.FileSystemError.FileNotADirectory(destination); - } else { - if (destinationIsDir) { - await this.copyFileToFolder(source, sourceStat.size, destination, options); - return; - } else { - await this.copyFileToFile(source, sourceStat.size, destination, options); - return; - } - } - } - - private async copyFileToFile( - source: vscode.Uri, sourceSize: number, destination: vscode.Uri, options: { overwrite: boolean }): Promise { - - let from: fs.ReadStream; - let to: fs.WriteStream; - if (source.scheme === 'file') { - from = fs.createReadStream(source.fsPath); - } else { - const sourceClient: IHDFSClient = await this.getClient(source); - const sourcePath: string = path.join('/', source.path); - from = sourceClient.createReadStream(sourcePath); - } - - if (destination.scheme === 'file') { - to = fs.createWriteStream(destination.fsPath); - } else { - const destinationClient: IHDFSClient = await this.getClient(destination); - const destinationPath: string = path.join('/', destination.path); - to = destinationClient.createWriteStream(destinationPath); - } - - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: __('hdfs.copying', [source.path, destination.path]), - cancellable: true - }, - (progress, cancellationToken) => new Promise(async (resolve, reject) => { - let writeAmount: number = 0; - const transform: Transform = new Transform({ - transform: (chunk: string | Buffer, encoding: string, callback: Function) => { - writeAmount += chunk.length; - progress.report({ - message: __('hdfs.progress', [ - (writeAmount / sourceSize * 100).toFixed(), writeAmount, sourceSize - ]), - increment: chunk.length / sourceSize * 100 - }); - callback(null, chunk); - } - }); - let error: any; - - function cleanup(): void { - if ('unpipe' in from) { - from.unpipe(); - } - transform.unpipe(); - from.destroy(); - transform.destroy(); - to.destroy(); - } - - cancellationToken.onCancellationRequested(() => { - error = true; - cleanup(); - reject(__('hdfs.write.cancelled')); - }); - - from.once('error', err => { - error = err; - cleanup(); - reject(new vscode.FileSystemError(err)); - }); - to.once('error', err => { - error = err; - cleanup(); - reject(new vscode.FileSystemError(err)); - }); - - to.once('finish', () => { - cleanup(); - if (!error) { - resolve(); - } - }); - - from.pipe(transform).pipe(to); - })); - this.onDidChangeFileEmitter.fire([{ type: vscode.FileChangeType.Created, uri: destination }]); - } - - private copyFileToFolder( - source: vscode.Uri, sourceSize: number, destination: vscode.Uri, options: { overwrite: boolean }): Promise { - - const fileName: string = path.basename(source.path); - destination = Util.uriPathAppend(destination, fileName); - return this.copyFileToFile(source, sourceSize, destination, options); - } - - private copyFolderToFolder(source: vscode.Uri, destination: vscode.Uri, options: { overwrite: boolean }): Promise { - return this.copyFolderToFutureFolder(source, Util.uriPathAppend(destination, path.basename(source.path)), options); - } - - private async copyFolderToFutureFolder(source: vscode.Uri, destination: vscode.Uri, options: { overwrite: boolean }): Promise { - const files: [string, vscode.FileType][] = await this.readDirectory(source); - await this.createDirectory(destination); - this.onDidChangeFileEmitter.fire([{ type: vscode.FileChangeType.Created, uri: destination }]); - for (const file of files) { - await this.copy(Util.uriPathAppend(source, file[0]), destination, options); - } - } -} - -/** - * HDFS management module - */ -@injectable() -export class HDFS extends Singleton { - public readonly provider: HDFSFileSystemProvider; - private UPLOADFILES: string = __('storage.dialog.label.upload-files'); - private UPLOADFOLDER: string = __('storage.dialog.label.upload-folders'); - private DOWNLOADHERE: string = __('storage.dialog.label.download'); - - constructor() { - super(); - console.log('Registering HDFS...'); - this.provider = new HDFSFileSystemProvider(); - this.context.subscriptions.push( - vscode.workspace.registerFileSystemProvider('webhdfs', this.provider, { isCaseSensitive: true }) - ); - console.log('HDFS registered as webhdfs:/...'); - this.context.subscriptions.push( - vscode.commands.registerCommand( - COMMAND_OPEN_HDFS, - async (node?: ClusterExplorerChildNode | IPAICluster) => { - if (!node) { - const manager: ClusterManager = await getSingleton(ClusterManager); - const index: number | undefined = await manager.pick(); - if (index === undefined) { - return; - } - await this.open(manager.allConfigurations[index]); - } else if (node instanceof ClusterExplorerChildNode) { - await this.open((await getSingleton(ClusterManager)).allConfigurations[node.index]); - } else { - await this.open(node); - } - } - ), - vscode.commands.registerCommand(COMMAND_HDFS_UPLOAD_FILES, async (param: vscode.Uri | vscode.TreeItem) => { - await this.uploadFiles(this.unpackParam(param)); - }), - vscode.commands.registerCommand(COMMAND_HDFS_UPLOAD_FOLDERS, async (param: vscode.Uri | vscode.TreeItem) => { - await this.uploadFolders(this.unpackParam(param)); - }), - vscode.commands.registerCommand(COMMAND_HDFS_DOWNLOAD, async (param: vscode.Uri | vscode.TreeItem) => { - await this.download(this.unpackParam(param)); - }) - ); - } - - public async open(conf: IPAICluster): Promise { - if (!conf.webhdfs_uri) { - Util.err('hdfs.initialization.missingconfiguration'); - return; - } - const setting: string | undefined = vscode.workspace.getConfiguration(SETTING_SECTION_HDFS).get(SETTING_HDFS_EXPLORER_LOCATION); - if (setting === ENUM_HDFS_EXPLORER_LOCATION.explorer) { - let start: number = 0; - let deleteCount: number = 0; - if (vscode.workspace.workspaceFolders) { - start = vscode.workspace.workspaceFolders.findIndex(folder => folder.uri.scheme === 'webhdfs'); - if (start >= 0) { - deleteCount = 1; - } else { - start = vscode.workspace.workspaceFolders.length; - } - } - try { - // this.provider!.addClient(getHDFSUriAuthority(configuration)); - await vscode.commands.executeCommand('workbench.view.explorer'); - void vscode.window.showInformationMessage(__('hdfs.open.prompt', [conf.webhdfs_uri])); - vscode.workspace.updateWorkspaceFolders(start, deleteCount, { - uri: vscode.Uri.parse(`webhdfs://${getHDFSUriAuthority(conf)}/`), - name: __('hdfs.workspace.title', [conf.webhdfs_uri]) - }); - // Extension may be reloaded at this point due to workspace changes - } catch (ex) { - Util.err('hdfs.open.error', [ex]); - } - } else { - const provider: HDFSTreeDataProvider = await getSingleton(HDFSTreeDataProvider); - provider.setUri(vscode.Uri.parse(`webhdfs://${getHDFSUriAuthority(conf)}/`)); - } - } - - public async close(index: number): Promise { - const configuration: IPAICluster = (await getSingleton(ClusterManager)).allConfigurations[index]; - const authority: string = getHDFSUriAuthority(configuration); - if (vscode.workspace.workspaceFolders) { - const pos: number = vscode.workspace.workspaceFolders.findIndex( - folder => folder.uri.scheme === 'webhdfs' && folder.uri.authority === authority); - if (pos >= 0) { - vscode.workspace.updateWorkspaceFolders(pos, 1); - } - } - } - - private unpackParam(param: vscode.Uri | vscode.TreeItem): vscode.Uri { - if (param instanceof vscode.TreeItem) { - if (param.resourceUri !== undefined) { - return param.resourceUri; - } else { - throw new Error('Invalid HDFS operation param'); - } - } else { - return param; - } - } - - private async download(from: vscode.Uri): Promise { - const files: vscode.Uri[] | undefined = await vscode.window.showOpenDialog({ - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - openLabel: this.DOWNLOADHERE - }); - if (!files) { - return; - } - await this.provider!.copy(from, files[0], { overwrite: true }); - } - - private async uploadFiles(target: vscode.Uri): Promise { - const files: vscode.Uri[] | undefined = await vscode.window.showOpenDialog({ - canSelectFiles: true, - canSelectMany: true, - openLabel: this.UPLOADFILES - }); - if (!files) { - return; - } - return this.upload(files, target); - } - - private async uploadFolders(target: vscode.Uri): Promise { - const folders: vscode.Uri[] | undefined = await vscode.window.showOpenDialog({ - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: true, - openLabel: this.UPLOADFOLDER - }); - if (!folders) { - return; - } - return this.upload(folders, target); - } - - private async upload(localUris: vscode.Uri[], remote: vscode.Uri): Promise { - const statusBarItem: vscode.StatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MAX_VALUE); - statusBarItem.text = `${OCTICON_CLOUDUPLOAD} ${__('hdfs.upload.status', [0, localUris.length])}`; - statusBarItem.show(); - try { - for (const [i, file] of localUris.entries()) { - const suffix: string = path.basename(file.fsPath); - statusBarItem.text = `${OCTICON_CLOUDUPLOAD} ${__('hdfs.upload.status', [i, localUris.length])}`; - await this.provider!.copy(file, Util.uriPathAppend(remote, suffix), { overwrite: true }); - } - Util.info('hdfs.upload.success'); - } catch (ex) { - Util.err('hdfs.upload.error', [ex]); - } - statusBarItem.dispose(); - } -} diff --git a/contrib/pai_vscode/src/pai/storage/nfsStorageManager.ts b/contrib/pai_vscode/src/pai/storage/nfsStorageManager.ts deleted file mode 100644 index 9ebbf8209a..0000000000 --- a/contrib/pai_vscode/src/pai/storage/nfsStorageManager.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as child from 'child_process'; -import { Dictionary } from 'lodash'; -import { IStorageServer } from 'openpai-js-sdk'; -import * as os from 'os'; -import * as path from 'path'; -import { - commands, window, workspace, Terminal, WorkspaceConfiguration -} from 'vscode'; - -import { - COMMAND_STORAGE_NFS_MOUNT, COMMAND_STORAGE_NFS_MOUNT_POINT, SETTING_SECTION_STORAGE_NFS, SETTING_STORAGE_NFS_MOUNT_POINT -} from '../../common/constants'; -import { __ } from '../../common/i18n'; -import { getSingleton, Singleton } from '../../common/singleton'; -import { Util } from '../../common/util'; -import { StorageTreeNode } from '../container/common/treeNode'; -import { MountPointTreeNode } from '../container/storage/mountPointTreeItem'; -import { NfsRootNode } from '../container/storage/NfsTreeItem'; -import { StorageTreeDataProvider } from '../container/storage/storageTreeView'; - -/** - * Nfs storage management module. - */ -export class NfsStorageManager extends Singleton { - public async onActivate(): Promise { - this.context.subscriptions.push( - commands.registerCommand( - COMMAND_STORAGE_NFS_MOUNT_POINT, - async (node: StorageTreeNode, key: string) => - await this.setupMountPoint(node, key) - ), - commands.registerCommand( - COMMAND_STORAGE_NFS_MOUNT, - async (node: NfsRootNode, mountPath: string) => - await this.mountNfs(node, mountPath) - ) - ); - } - - public async setupMountPoint(node: StorageTreeNode, key: string): Promise { - const settings: WorkspaceConfiguration = - workspace.getConfiguration(SETTING_SECTION_STORAGE_NFS); - const mountPath: string | undefined = await window.showInputBox({ - prompt: __('storage.nfs.mount.point.prompt'), - validateInput: (val: string): string => { - if (!val) { - return __('container.nfs.mount.path.empty'); - } - switch (os.platform()) { - case 'win32': - if (!/[a-zA-Z]:/.test(val)) { - Util.warn('container.nfs.mount.invalid.device.name'); - } - break; - default: - } - return ''; - } - }); - if (mountPath) { - try { - let map: Dictionary | undefined = - await settings.get(SETTING_STORAGE_NFS_MOUNT_POINT); - if (!map) { - map = {}; - } - map[key] = mountPath; - await settings.update(SETTING_STORAGE_NFS_MOUNT_POINT, map); - } catch (ex) { - console.log(ex); - } - await (await getSingleton(StorageTreeDataProvider)).refresh(node.parent); - } - } - - public async mountNfs(node: NfsRootNode, mountPath: string): Promise { - const server: IStorageServer = node.storageServer; - let serverPath: string = path.join(server.data.rootPath, node.mountInfo.path).replace(/\\/g, '/'); - if (serverPath.includes('\${PAI_USER_NAME}')) { - serverPath = serverPath.replace('\${PAI_USER_NAME}', (node.parent).cluster.username!); - } - - let cmdStr: string = ''; - switch (os.platform()) { - case 'win32': - cmdStr = `cmd /c mount -o anon ${server.data.address}:${serverPath} ${mountPath}`; - break; - case 'darwin': - cmdStr = `sudo mkdir -p ${mountPath} && ` + - `sudo mount -t nfs -o resvport,hard,nolock ${server.data.address}:${serverPath} ${mountPath}`; - break; - default: - Util.err('container.nfs.mount.unsupport.os'); - return; - } - const provider: StorageTreeDataProvider = await getSingleton(StorageTreeDataProvider); - - if (os.platform() === 'win32') { - child.exec(cmdStr, (err, stdout, stderr) => { - if (err) { - Util.err('container.nfs.mount.failed', err); - } - if (stdout) { - Util.info(stdout); - } - if (stderr) { - Util.warn(stderr); - } - void provider.refresh(node.parent); - }); - } else { - const terminal: Terminal = window.createTerminal('PAI Mount NFS'); - terminal.show(); - terminal.sendText(cmdStr); - const FINISH: string = __('common.finish'); - const CANCEL: string = __('common.cancel'); - - const result: string | undefined = await window.showWarningMessage( - __('util.editjson.prompt'), - FINISH, - CANCEL - ); - - if (result === FINISH) { - await provider.refresh(node.parent); - } - } - } -} diff --git a/contrib/pai_vscode/src/pai/storage/pathBaseStorageManager.ts b/contrib/pai_vscode/src/pai/storage/pathBaseStorageManager.ts deleted file mode 100644 index 68b328c12e..0000000000 --- a/contrib/pai_vscode/src/pai/storage/pathBaseStorageManager.ts +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as fs from 'fs-extra'; -import * as path from 'path'; -import { - window, StatusBarAlignment, StatusBarItem, Uri -} from 'vscode'; - -import { - OCTICON_CLOUDDOWNLOAD, OCTICON_CLOUDUPLOAD -} from '../../common/constants'; -import { __ } from '../../common/i18n'; -import { Util } from '../../common/util'; -import { MountPointTreeNode } from '../container/storage/mountPointTreeItem'; -import { NfsRootNode } from '../container/storage/NfsTreeItem'; -import { PathBaseTreeNode } from '../container/storage/pathBaseTreeItem'; -import { SambaRootNode } from '../container/storage/SambaTreeItem'; - -/** - * Path base storage management module, used in Samba and NFS. - */ -// tslint:disable-next-line: no-unnecessary-class -export class PathBaseStorageManager { - public static async delete(target: PathBaseTreeNode): Promise { - try { - await fs.remove(target.rootPath); - Util.info('storage.delete.success'); - } catch (err) { - Util.err('storage.delete.error', [err]); - } - } - - public static async downloadFile(target: PathBaseTreeNode, dest?: Uri): Promise { - const uri: Uri | undefined = dest ? dest : await window.showSaveDialog({ - saveLabel: __('storage.dialog.label.download'), - defaultUri: Uri.file(target.name) - }); - if (uri && uri.scheme === 'file') { - const totalBytes: number = 1; - - const statusBarItem: StatusBarItem = - window.createStatusBarItem(StatusBarAlignment.Right, Number.MAX_VALUE); - statusBarItem.text = - `${OCTICON_CLOUDUPLOAD} ${__('storage.download.status', [0, totalBytes])}`; - statusBarItem.show(); - try { - await fs.copy(target.rootPath, uri.fsPath); - statusBarItem.text = - `${OCTICON_CLOUDDOWNLOAD} ${__('storage.download.status', [1, totalBytes])}`; - Util.info('storage.download.success'); - } catch (err) { - Util.err('storage.download.error', [err]); - } - statusBarItem.dispose(); - } - } - - public static async createFolder( - target: PathBaseTreeNode | SambaRootNode | NfsRootNode | MountPointTreeNode, - folder?: string - ): Promise { - if (folder) { - if (target instanceof MountPointTreeNode) { - target = target.data; - } - try { - const dirName: string = path.join(target.rootPath!, folder); - await fs.mkdirp(dirName); - Util.info('storage.create.folder.success'); - } catch (err) { - Util.err('storage.create.folder.error', [err]); - } - } else { - const res: string | undefined = await window.showInputBox({ - prompt: __('container.azure.blob.mkdir.prompt') - }); - if (res === undefined) { - Util.warn('container.azure.blob.mkdir.cancelled'); - return; - } - if (target instanceof MountPointTreeNode) { - target = target.data; - } - - try { - const dirName: string = path.join(target.rootPath!, res); - await fs.mkdirp(dirName); - Util.info('storage.create.folder.success'); - } catch (err) { - Util.err('storage.create.folder.error', [err]); - } - } - } - - public static async uploadFiles( - target: PathBaseTreeNode | SambaRootNode | NfsRootNode |MountPointTreeNode, - files?: Uri[] - ): Promise { - if (!files) { - files = await window.showOpenDialog({ - canSelectFiles: true, - canSelectMany: true, - openLabel: __('storage.dialog.label.upload-files') - }); - } - if (!files) { - return; - } - - if (target instanceof MountPointTreeNode) { - target = target.data; - } - const statusBarItem: StatusBarItem = - window.createStatusBarItem(StatusBarAlignment.Right, Number.MAX_VALUE); - statusBarItem.text = - `${OCTICON_CLOUDUPLOAD} ${__('storage.upload.status', [0, files.length])}`; - statusBarItem.show(); - try { - for (const [i, file] of files.entries()) { - const name: string = path.basename(file.fsPath); - const targetPath: string = path.join((target).rootPath, name); - statusBarItem.text = - `${OCTICON_CLOUDUPLOAD} ${__('storage.upload.status', [i, files.length])}`; - await fs.copy(file.fsPath, targetPath); - } - Util.info('storage.upload.success'); - } catch (err) { - Util.err('storage.upload.error', [err]); - } - statusBarItem.dispose(); - } - - public static async uploadFolders( - target: PathBaseTreeNode | SambaRootNode | NfsRootNode | MountPointTreeNode, - src?: Uri[] - ): Promise { - const folders: Uri[] | undefined = src ? src : await window.showOpenDialog({ - canSelectFolders: true, - canSelectMany: true, - openLabel: __('storage.dialog.label.upload-folders') - }); - if (!folders) { - return; - } - - if (target instanceof MountPointTreeNode) { - target = target.data; - } - const statusBarItem: StatusBarItem = - window.createStatusBarItem(StatusBarAlignment.Right, Number.MAX_VALUE); - statusBarItem.text = - `${OCTICON_CLOUDUPLOAD} ${__('storage.upload.status', [0, folders.length])}`; - statusBarItem.show(); - try { - for (const [i, folder] of folders.entries()) { - const name: string = path.basename(folder.fsPath); - const targetPath: string = path.join((target).rootPath, name); - statusBarItem.text = - `${OCTICON_CLOUDUPLOAD} ${__('storage.upload.status', [i, folders.length])}`; - await fs.copy(folder.fsPath, targetPath); - } - Util.info('storage.upload.success'); - } catch (err) { - Util.err('storage.upload.error', [err]); - } - statusBarItem.dispose(); - } -} diff --git a/contrib/pai_vscode/src/pai/storage/personalStorageManager.ts b/contrib/pai_vscode/src/pai/storage/personalStorageManager.ts deleted file mode 100644 index f140446856..0000000000 --- a/contrib/pai_vscode/src/pai/storage/personalStorageManager.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { injectable } from 'inversify'; -import { IStorageServer } from 'openpai-js-sdk'; -import { commands, window } from 'vscode'; - -import { - COMMAND_ADD_PERSONAL_STORAGE, COMMAND_DELETE_PERSONAL_STORAGE, COMMAND_EDIT_PERSONAL_STORAGE} from '../../common/constants'; -import { __ } from '../../common/i18n'; -import { getSingleton, Singleton } from '../../common/singleton'; -import { Util } from '../../common/util'; -import { PersonalStorageRootNode, PersonalStorageTreeNode } from '../container/storage/personalStorageTreeItem'; -import { StorageTreeDataProvider } from '../container/storage/storageTreeView'; - -export interface IStorageConfiguration { - readonly version: string; - storages: IStorageServer[]; -} - -/** - * Personal storage management module - */ -@injectable() -export class PersonalStorageManager extends Singleton { - private static readonly CONF_KEY: string = 'openpai.personal.storage'; - private static readonly default: IStorageConfiguration = { - version: '0.0.1', - storages: [] - }; - private configuration: IStorageConfiguration | undefined; - - constructor() { - super(); - } - - public get allConfigurations(): IStorageServer[] { - return this.configuration!.storages; - } - - public async onActivate(): Promise { - this.context.subscriptions.push( - commands.registerCommand( - COMMAND_ADD_PERSONAL_STORAGE, - async (node: PersonalStorageRootNode) => { - await this.add(); - const provider: StorageTreeDataProvider = - await getSingleton(StorageTreeDataProvider); - await provider.refresh(node); - }), - commands.registerCommand( - COMMAND_EDIT_PERSONAL_STORAGE, - async (node: PersonalStorageTreeNode) => { - await this.edit(node.index); - const provider: StorageTreeDataProvider = - await getSingleton(StorageTreeDataProvider); - await provider.refresh(node); - }), - commands.registerCommand( - COMMAND_DELETE_PERSONAL_STORAGE, - async (node: PersonalStorageTreeNode) => { - await this.delete(node.index); - const provider: StorageTreeDataProvider = - await getSingleton(StorageTreeDataProvider); - await provider.refresh(node.getParent()); - }) - ); - this.configuration = this.context.globalState.get( - PersonalStorageManager.CONF_KEY) || PersonalStorageManager.default; - try { - await this.validateConfiguration(); - } catch (err) { - Util.err([err.message || err]); - } - } - - public async validateConfiguration(): Promise { - const validateResult: string | undefined = - await Util.validateJSON(this.configuration!, 'pai_personal_storage.schema.json'); - if (validateResult) { - throw validateResult; - } - } - - public async add(): Promise { - const name: string | undefined = await window.showInputBox({ - prompt: __('cluster.add.personal.storage.prompt'), - validateInput: (val: string): string => { - if (!val) { - return __('cluster.add.personal.storage.empty'); - } - if (val.includes('/')) { - return __('cluster.add.personal.storage.invalidchar'); - } - return ''; - } - }); - if (!name) { - return; - } - - const config: IStorageServer = { - spn: name, - type: 'azureblob', - data: { - dataStore: 'dataStore', - containerName: '', - accountName: '', - key: '' - }, - extension: { } - }; - - await this.edit(this.allConfigurations.length, config); - } - - public async edit(index: number, config?: IStorageServer): Promise { - const original: IStorageServer = this.allConfigurations[index] || config; - const editResult: IStorageServer | undefined = await Util.editJSON( - original, - `pai_personal_storage_${original.spn}.json`, - 'pai_personal_storage.schema.json' - ); - if (editResult) { - this.allConfigurations[index] = editResult; - await this.save(); - } - } - - public async save(): Promise { - await this.context.globalState.update(PersonalStorageManager.CONF_KEY, this.configuration!); - await (await getSingleton(StorageTreeDataProvider)).refresh(); - } - - public async delete(index: number): Promise { - this.allConfigurations.splice(index, 1); - await this.save(); - } -} diff --git a/contrib/pai_vscode/src/pai/storage/storageHelper.ts b/contrib/pai_vscode/src/pai/storage/storageHelper.ts deleted file mode 100644 index 7fbc388cf7..0000000000 --- a/contrib/pai_vscode/src/pai/storage/storageHelper.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { injectable } from 'inversify'; -import { IStorageConfig, OpenPAIClient } from 'openpai-js-sdk'; -import * as path from 'path'; -import { Uri } from 'vscode'; - -import { delay, getSingleton, Singleton } from '../../common/singleton'; -import { StorageTreeNode } from '../container/common/treeNode'; -import { StorageTreeDataProvider } from '../container/storage/storageTreeView'; -import { IPAICluster, IUploadConfig } from '../utility/paiInterface'; - -import { PersonalStorageManager } from './personalStorageManager'; - -export let StorageHelper: StorageHelperClass; - -/** - * Storage helper class - */ -@injectable() -export class StorageHelperClass extends Singleton { - constructor() { - super(); - StorageHelper = this; - } - - public async getStorageMountPoints(cluster: IPAICluster): Promise<{storage: string, mountPoint: string}[]> { - const client: OpenPAIClient = new OpenPAIClient({ - rest_server_uri: cluster.rest_server_uri, - token: cluster.token, - username: cluster.username, - password: cluster.password, - https: cluster.https - }); - const storageConfigs: IStorageConfig[] = await client.storage.getConfig(); - const result: { storage: string, mountPoint: string}[] = []; - storageConfigs.forEach(config => { - config.mountInfos.forEach(mountInfo => { - result.push({ - storage: config.name, - mountPoint: mountInfo.mountPoint - }); - }); - }); - - return result; - } - - public async getStorages(cluster: IPAICluster): Promise { - const client: OpenPAIClient = new OpenPAIClient({ - rest_server_uri: cluster.rest_server_uri, - token: cluster.token, - username: cluster.username, - password: cluster.password, - https: cluster.https - }); - const storageConfigs: IStorageConfig[] = await client.storage.getConfig(); - return storageConfigs.map(x => x.name); - } - - public async getPersonalStorages(): Promise { - const personalStorageManager: PersonalStorageManager = await getSingleton(PersonalStorageManager); - return personalStorageManager.allConfigurations.map(config => config.spn); - } - - public async getFolder(baseFolder: StorageTreeNode, target: string): Promise { - for (const name of target.split('/')) { - baseFolder = (await baseFolder.getChildren()).find(child => child.label === name)!; - } - return baseFolder; - } - - public async createFolder( - uploadConfig: IUploadConfig, clusterName: string, baseFolder: string - ): Promise { - const treeView: StorageTreeDataProvider = await getSingleton(StorageTreeDataProvider); - let targetNode: StorageTreeNode | undefined; - if (uploadConfig.storageType === 'cluster') { - const clusterRoot: StorageTreeNode = (await treeView.getChildren())![0]; - const clusterNode: StorageTreeNode = (await clusterRoot.getChildren()).find(child => child.label === clusterName)!; - const storageNode: StorageTreeNode = (await clusterNode.getChildren()).find(child => child.label === uploadConfig.storageName)!; - targetNode = (await storageNode.getChildren()).find(child => child.description === uploadConfig.storageMountPoint); - } else { - const personalRoot: StorageTreeNode = (await treeView.getChildren())![1]; - targetNode = (await personalRoot.getChildren()).find(child => child.label === uploadConfig.storageName)!; - } - - await targetNode!.createFolder(baseFolder); - await treeView.refresh(targetNode); - return targetNode!; - } - - public async uploadFile( - uploadConfig: IUploadConfig, clusterName: string, jobName: string, file: Uri, target: string - ): Promise { - const treeView: StorageTreeDataProvider = await getSingleton(StorageTreeDataProvider); - const dirname: string = path.dirname(target); - const folderName: string = path.join(jobName, dirname).replace(/\\/g, '/'); - const baseNode: StorageTreeNode = - await this.createFolder(uploadConfig, clusterName, folderName); - let targetNode: StorageTreeNode = await this.getFolder(baseNode, folderName); - for (let i: number = 0; i < 10 && targetNode === undefined; ++i) { - await delay(100); - targetNode = await this.getFolder(baseNode, folderName); - } - try { - await targetNode.uploadFile([file]); - await treeView.refresh(targetNode); - } catch (err) { - console.log(err); - throw err; - } - } -} diff --git a/contrib/pai_vscode/src/pai/storage/webhdfs-workaround.ts b/contrib/pai_vscode/src/pai/storage/webhdfs-workaround.ts deleted file mode 100644 index 1e48b35646..0000000000 --- a/contrib/pai_vscode/src/pai/storage/webhdfs-workaround.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as fs from 'fs-extra'; -import * as request from 'request'; -import * as requestPromise from 'request-promise-native'; -import * as webhdfs from 'webhdfs'; - -interface IWebHDFSOptions { - host: string; - port?: string; - user?: string; - path?: string; -} - -interface IWebHDFSOptions2 { - timeout?: number; -} - -export interface IHDFSStatResult { - pathSuffix: string; - permission: string; - accessTime: number; - modificationTime: number; - length: number; - type: 'DIRECTORY' | 'FILE'; -} - -export interface IHDFSClient { - mkdir(path: string, mode?: string): Promise; - readdir(path: string): Promise; - stat(path: string): Promise; - unlink(path: string, recursive: boolean): Promise; - rename(from: string, to: string): Promise; - createReadStream(path: string): fs.ReadStream; - createWriteStream(path: string): fs.WriteStream; - createRobustWriteStream(path: string): Promise; - _getOperationEndpoint(operation: string, path: string, options: object): string; -} - -export function createWebHDFSClient({ host, port = '50070', user, path = '/webhdfs/v1' }: IWebHDFSOptions, - { timeout = 60 * 1000 }: IWebHDFSOptions2): IHDFSClient { - const client: IHDFSClient = webhdfs.createClient({ host, port, user, path }, { timeout }); - - client.createRobustWriteStream = async function (this: IHDFSClient, pathOnWebhdfs: string): Promise { - const endpoint: string = this._getOperationEndpoint('create', pathOnWebhdfs, { - overwrite: true, - permission: '0755' - }); - try { - const redirectResponse: requestPromise.FullResponse = await requestPromise.put({ - url: endpoint, - followAllRedirects: false, - resolveWithFullResponse: true, - simple: false - }); - const realLocation: string | undefined = redirectResponse.headers.location; - if (!realLocation || redirectResponse.statusCode !== 307) { - throw new Error('Malformed webhdfs response'); - } - const stream: request.Request = request.put(realLocation); - stream.once('response', (resp: request.Response) => { - if (resp.statusCode === 201) { - stream.emit('finish'); - } else { - stream.emit('error', resp.statusMessage); - } - }); - return stream; - } catch (ex) { - throw ex; - } - }; - return client; -} \ No newline at end of file diff --git a/contrib/pai_vscode/src/pai/utility/azureADLogin.ts b/contrib/pai_vscode/src/pai/utility/azureADLogin.ts deleted file mode 100644 index 59d25ec938..0000000000 --- a/contrib/pai_vscode/src/pai/utility/azureADLogin.ts +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as crypto from 'crypto'; -import { EventEmitter } from 'events'; -import * as http from 'http'; -import { AddressInfo } from 'net'; -import { ILoginInfo } from 'openpai-js-sdk'; -import { stringify } from 'querystring'; -import * as url from 'url'; -import * as vscode from 'vscode'; - -import { __ } from '../../common/i18n'; - -async function callback(reqUrl: url.Url): Promise { - let error: string | string[] | undefined; - - if (reqUrl && reqUrl.query && typeof(reqUrl.query) !== 'string') { - error = reqUrl.query.error_description || reqUrl.query.error; - - if (!error) { - return reqUrl.query; - } - } - - throw new Error( error || 'No token received.'); -} - -function createServer(nonce: string): { server: http.Server, emitter: EventEmitter } { - const emitter: EventEmitter = new EventEmitter(); - const callbackTimer: NodeJS.Timer = - setTimeout(() => emitter.emit('callback', new Error('Timeout waiting for token')), 5 * 60 * 1000); - - const server: http.Server = http.createServer(); - server.on('request', async (req, res) => { - const reqUrl: url.UrlWithParsedQuery = url.parse(req.url!, true); - - switch (reqUrl.pathname) { - case '/signin': - let receivedNonce: string = ''; - if (typeof(reqUrl.query.nonce) === 'string') { - receivedNonce = reqUrl.query.nonce.replace(/ /g, '+'); - } - if (receivedNonce === nonce) { - emitter.emit('signin', { req, res }); - } else { - const err: Error = new Error('Nonce does not match.'); - emitter.emit('signin', { err, res }); - } - break; - case '/callback': - try { - const loginInfo: ILoginInfo = await callback(reqUrl); - emitter.emit('callback', { loginInfo, res }); - } catch (err) { - emitter.emit('callback', { err, res }); - } - clearTimeout(callbackTimer); - break; - default: - res.writeHead(404); - res.end(); - break; - } - }); - - return { - server, - emitter - }; -} - -async function startServer(server: http.Server): Promise { - let portTimer: NodeJS.Timer; - function cancelPortTimer(): void { - clearTimeout(portTimer); - } - const port: Promise = new Promise((resolve, reject) => { - portTimer = setTimeout(() => { reject(new Error('Timeout waiting for port')); }, 5000); - server.on('listening', () => { - resolve((server.address()).port); - }); - server.on('error', err => { - reject(err); - }); - server.on('close', () => { - reject(new Error('Closed')); - }); - server.listen(0, '127.0.0.1'); - }); - port.then(cancelPortTimer, cancelPortTimer); - return port; -} - -function once(emitter: EventEmitter, name: string): Promise { - return new Promise(resolve => { - emitter.once(name, resolve); - }); -} - -export async function login(restServerUrl: string, webportalUrl: string, redirectTimeout: () => Promise): Promise { - const nonce: string = crypto.randomBytes(16).toString('base64'); - const { server, emitter } = createServer(nonce); - - try { - const port: number = await startServer(server); - await vscode.env.openExternal( - vscode.Uri.parse(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`)); - const redirectTimer: NodeJS.Timer = - setTimeout(() => redirectTimeout().catch(console.error), 10 * 1000); - - const redirectReq: any = await once(emitter, 'signin'); - - if ('err' in redirectReq) { - const { err, res } = redirectReq; - res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` }); - res.end(); - throw err; - } - - clearTimeout(redirectTimer); - const host: any = redirectReq.req.headers.host || ''; - const updatedPortStr: string = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1]; - const updatedPort: number = updatedPortStr ? parseInt(updatedPortStr, 10) : port; - - const signInUrl: string = - `${restServerUrl}/api/v1/authn/oidc/login?` + - `redirect_uri=${encodeURIComponent(`http://localhost:${updatedPort}/callback`)}`; - - redirectReq.res.writeHead(302, { Location: signInUrl }); - redirectReq.res.end(); - - const loginRes: any = await once(emitter, 'callback'); - const response: any = loginRes.res; - try { - if ('err' in loginRes) { - throw loginRes.err; - } - - response.writeHead(200, {'Content-Type': 'text/html'}); - response.write( - '' + - '' + - '' + - '' + - 'Login success' + - '' + - '' + - `` + - '' + - '' - ); - response.end(); - return loginRes.loginInfo; - } catch (err) { - response.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` }); - response.end(); - throw err; - } - } catch (err) { - console.log(err); - } finally { - setTimeout(() => { server.close(); }, 5000); - } -} diff --git a/contrib/pai_vscode/src/pai/utility/paiInterface.ts b/contrib/pai_vscode/src/pai/utility/paiInterface.ts deleted file mode 100644 index 2dbfbcc222..0000000000 --- a/contrib/pai_vscode/src/pai/utility/paiInterface.ts +++ /dev/null @@ -1,239 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -export interface IPAICluster { - name?: string; - username?: string; - password?: string; - token?: string; - https?: boolean; - rest_server_uri: string; - webhdfs_uri?: string; - grafana_uri?: string; - hdfs_uri?: string; - k8s_dashboard_uri?: string; - web_portal_uri?: string; - protocol_version?: string; -} - -export interface IPAITaskRole { - name: string; - taskNumber: number; - cpuNumber: number; - gpuNumber: number; - memoryMB: number; - command: string; -} - -export interface IPAIJobConfigV1 { - jobName: string; - image: string; - dataDir?: string; - authFile?: string; - codeDir: string; - outputDir: string; - taskRoles: IPAITaskRole[]; - [key: string]: any; -} - -export interface IPAIJobInfo { - name: string; - username: string; - state: 'SUCCEEDED' | 'FAILED' | 'WAITING' | 'STOPPED' | 'RUNNING' | 'UNKNOWN'; - subState: 'FRAMEWORK_COMPLETED' | 'FRAMEWORK_WAITING'; - executionType: 'START' | 'STOP'; - retries: number; - createdTime: number; - completedTime: number; - appExitCode: number; - virtualCluster: string; -} - -export interface IPAIJobV2UploadConfig { - [cluster: string]: IUploadConfig; -} - -export interface IUploadConfig { - enable: boolean; - include: string[]; - exclude: string[]; - storageType?: 'cluster' | 'personal'; - storageName?: string; - storageMountPoint?: string; - storagePath?: string; -} - -/** - * OpenPAI Job Protocol. - */ -export interface IPAIJobConfigV2 { - /** Protocol version, current version is 2. */ - protocolVersion: string | number; - name: string; - /** Component type, should be "job" here. */ - type: string; - /** Component version, Default is latest. */ - version?: string; - contributor?: string; - description?: string; - - /** Each item is the protocol for data, script, dockerimage, or output type. */ - prerequisites?: { - /** If omitted, follow the protocolVersion in root. */ - protocolVersion?: string; - name: string; - /** Component type. Must be one of the following: data, script, dockerimage, or output. Prerequisites.type cannot be "job". */ - type: string; - /** Component version, Default is latest. */ - version?: string; - contributor?: string; - description?: string; - /** Only available when the type is dockerimage. */ - auth?: { - username?: string; - /** If a password is needed, it should be referenced as a secret. */ - password?: string; - registryuri?: string; - }; - /** Only when the type is data can the uri be a list. */ - uri: string | string[]; - } []; - - /** - * If specified, the whole parameters object can be referenced as `$parameters`. - * Scope of reference `$parameters`: the reference is shared among all task roles. - */ - parameters?: { - /** - * : value1 - * : value2 - * Specify name and value of all the referencable parameters that will be used in the whole job template. - * Can be referenced by `<% $parameters.param1 %>`, `<% $parameters.param2 %>`. - */ - }; - - /** - * If sensitive information including password or API key is needed in the protocol, - * it should be specified here in secrets section and referenced as `$secrets`. - * Scope of reference `$secrets`: the reference is shared among all task roles and docker image's `auth` field. - * A system that supports PAI protocol should keep the secret information away from - * unauthorized users (how to define unauthorized user is out of the scope of this protocol). - * For example, the yaml file used for job cloning, the stdout/stderr should protect all information marked as secrets. - */ - secrets?: { - /** - * : password - * : key - * Specify name and value of all secrets that will be used in the whole job template. - * Can be referenced by `<% $secrets.secret1 %>`, `<% $secrets.secret2 %>`. - */ - }; - - /** Default is 0. */ - jobRetryCount?: number; - - /** - * Task roles are different types of task in the protocol. - * One job may have one or more task roles, each task role has one or more instances, and each instance runs inside one container. - */ - taskRoles: { - /** Name of the taskRole, string in ^[A-Za-z0-9\-._~]+$ format. */ - [name: string]: { - /** Default is 1, instances of a taskRole, no less than 1. */ - instances?: number; - /** - * Completion poclicy for the job, https:// - * github.com/Microsoft/pai/blob/master/subprojects/frameworklauncher/yarn/doc/USERMANUAL.md#ApplicationCompletionPolicy. - * Number of failed tasks to fail the entire job, null or no less than 1, - * if set to null means the job will always succeed regardless any task failure. - */ - completion?: { - /** - * Number of failed tasks to fail the entire job, null or no less than 1, - * if set to null means the job will always succeed regardless any task failure. - * Default is 1. - */ - minFailedInstances?: number | string; - /** - * Number of succeeded tasks to succeed the entire job, null or no less than 1, - * if set to null means the job will only succeed until all tasks are completed and minFailedInstances is not triggered. - * Default is null. - */ - minSucceededInstances?: number | string; - }; - /** Default is 0. */ - taskRetryCount?: number; - /** Should reference to a dockerimage defined in prerequisites. */ - dockerImage: string; - /** - * Scope of the reference `$data`, `$output`, `$script`: the reference is only valid inside this task role. - * User cannot reference them from another task role. Reference for `$parameters` is global and shared among task roles. - */ - /** Select data defined in prerequisites, target can be referenced as `$data` in this task role. */ - data?: string; - /** Select output defined in prerequisites, target can be referenced as `$output` in this task role. */ - output?: string; - /** Select script defined in prerequisites, target can be referenced as `$script` in this task role. */ - script?: string; - - extraContainerOptions?: { - /** Config the /dev/shm in a docker container, https://docs.docker.com/compose/compose-file/#shm_size. */ - shmMB?: number; - }; - - resourcePerInstance: { - /** CPU number, unit is CPU vcore. */ - cpu: number; - /** Memory number, unit is MB. */ - memoryMB: number; - gpu: number; - ports?: { - /** Port number for the port label. Only for host network, portLabel string in ^[A-Za-z0-9\-._~]+$ format. */ - [portLabel: string]: number; - } - }; - - commands: string[]; - } - }; - - /** - * To handle that a component may interact with different component differently, - * user is encouraged to place the codes handling such difference in the "deployments" field, - * e.g., a job may get input data through wget, hdfc -dfs cp, copy, or just directly read from remote storage. - * This logic can be placed here. - * In summary, the deployments field is responsible to make sure the job to run properly in a deployment specific runtime environment. - * One could have many deployments, but only one deployment can be activated at runtime by specifying in "defaults". - * User can choose the deployment and specify in "defaults" at submission time. - */ - deployments?: { - name: string; - taskRoles: { - /** Should be in taskRoles. */ - [name: string]: { - /** Execute before the taskRole's command. */ - preCommands: string[]; - /** Execute after the taskRole's command. */ - postCommands: string[]; - } - } - } []; - - /** Optional, default cluster specific settings. */ - defaults?: { - virtualCluster?: string; - /** Should reference to deployment defined in deployments */ - deployment?: string; - }; - - /** Optional, extra field, object, save any information that plugin may use. */ - extras?: { - submitFrom?: string; - hivedscheduler?: any; - 'com.microsoft.pai.runtimeplugin'?: any[]; - [name: string]: any; - }; -} diff --git a/contrib/pai_vscode/src/pai/utility/paiUri.ts b/contrib/pai_vscode/src/pai/utility/paiUri.ts deleted file mode 100644 index a81c859a9c..0000000000 --- a/contrib/pai_vscode/src/pai/utility/paiUri.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as querystring from 'querystring'; -import * as request from 'request-promise-native'; - -import { Util } from '../../common/util'; - -import { IPAICluster } from './paiInterface'; - -export namespace PAIRestUri { - const API_PREFIX_V1: string = 'api/v1'; - const API_PREFIX_V2: string = 'api/v2'; - - export function getRestUrlV1(cluster: IPAICluster): string { - return getRestUrlWithApiPrefix(cluster, API_PREFIX_V1); - } - - export function getRestUrlV2(cluster: IPAICluster): string { - return getRestUrlWithApiPrefix(cluster, API_PREFIX_V2); - } - - export function getRestUrlWithApiPrefix(cluster: IPAICluster, apiPrefix: string): string { - let url: string = cluster.rest_server_uri; - if (!url.endsWith('/')) { - url += '/'; - } - if (apiPrefix) { - url += apiPrefix; - } - - return Util.fixURL(url, cluster.https); - } - - export function token(cluster: IPAICluster): string { - return `${getRestUrlV1(cluster)}/token`; - } - - export function jobDetail(cluster: IPAICluster, username: string, jobName: string): string { - return `${getRestUrlV1(cluster)}/user/${username}/jobs/${jobName}`; - } - - export function jobs(cluster: IPAICluster, username?: string): string { - if (username) { - return `${getRestUrlV1(cluster)}/user/${username}/jobs`; - } - return `${getRestUrlV1(cluster)}/jobs`; - } - - export function jobsV2(cluster: IPAICluster): string { - return `${getRestUrlV2(cluster)}/jobs`; - } -} - -export namespace PAIWebPortalUri { - export function getClusterWebPortalUri(conf: IPAICluster): string { - return conf.web_portal_uri || conf.rest_server_uri.split(':')[0]; - } - - export async function isOldJobLinkAvailable(cluster: IPAICluster): Promise { - const link: string = `${getClusterWebPortalUri(cluster)}/view.html`; - try { - await request.get(Util.fixURL(link, cluster.https)); - return true; - } catch { - return false; - } - } - - export async function jobDetail(cluster: IPAICluster, username: string, jobName: string): Promise { - const oldLink: boolean = await isOldJobLinkAvailable(cluster); - const url: string = `${getClusterWebPortalUri(cluster)}/${oldLink ? 'view' : 'job-detail'}.html?${querystring.stringify({ - username, - jobName - })}`; - return Util.fixURL(url, cluster.https); - } - - export async function jobs(cluster: IPAICluster): Promise { - const oldLink: boolean = await isOldJobLinkAvailable(cluster); - return `${getClusterWebPortalUri(cluster)}/${oldLink ? 'view' : 'job-list'}.html`; - } -} \ No newline at end of file diff --git a/contrib/pai_vscode/src/pai/utility/remoteFileEditor.ts b/contrib/pai_vscode/src/pai/utility/remoteFileEditor.ts deleted file mode 100644 index 7f600026ba..0000000000 --- a/contrib/pai_vscode/src/pai/utility/remoteFileEditor.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { injectable } from 'inversify'; -import * as path from 'path'; -import { window, workspace, TextDocument, Uri } from 'vscode'; - -import { __ } from '../../common/i18n'; -import { Singleton } from '../../common/singleton'; -import { Util } from '../../common/util'; -import { StorageTreeNode } from '../container/common/treeNode'; - -/** - * Remote file editor - */ -@injectable() -export class RemoteFileEditor extends Singleton { - public fileMap: { [key: string]: [TextDocument, StorageTreeNode] } = {}; - - public async showEditor(item: StorageTreeNode): Promise { - const fileName: string = item.label!; - - try { - const parsedPath: path.ParsedPath = path.posix.parse(fileName); - const temporaryFilePath: string = await Util.createTemporaryFile(parsedPath.base); - await item.download(Uri.file(temporaryFilePath)); - const document: TextDocument | undefined = - await workspace.openTextDocument(temporaryFilePath); - if (document) { - this.fileMap[temporaryFilePath] = [document, item]; - await window.showTextDocument(document); - } else { - Util.err('storage.download.error', 'Unable to open'); - } - } catch (err) { - Util.err('storage.download.error', [err]); - } - } - - public async onDidSaveTextDocument(doc: TextDocument): Promise { - const filePath: string | undefined = - Object.keys(this.fileMap).find(fp => path.relative(doc.uri.fsPath, fp) === ''); - if (filePath) { - const item: StorageTreeNode = this.fileMap[filePath][1]; - try { - while (true) { - // Only error message won't be collapsed automatically by vscode. - const upload: string = __('common.upload'); - const result: string | undefined = await window.showInformationMessage( - __('util.remote.editor.save.prompt'), - upload, - __('common.cancel') - ); - if (result === upload) { - await doc.save(); - await item.parent!.uploadFile([doc.uri]); - } - return; - } - } catch (err) { - console.log(err); - } - } - } -} \ No newline at end of file diff --git a/contrib/pai_vscode/src/pai/yaml/yamlCommands.ts b/contrib/pai_vscode/src/pai/yaml/yamlCommands.ts deleted file mode 100644 index 01b27398be..0000000000 --- a/contrib/pai_vscode/src/pai/yaml/yamlCommands.ts +++ /dev/null @@ -1,300 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as fs from 'fs-extra'; -import { injectable } from 'inversify'; -import * as yaml from 'js-yaml'; -import { range } from 'lodash'; -import * as NodeRSA from 'node-rsa'; -import * as os from 'os'; -import * as path from 'path'; -import * as SshPK from 'sshpk'; -import { - commands, - languages, - window, - Position, - QuickPickItem, - SnippetString, - TextDocument, - TextEditor, - Uri -} from 'vscode'; - -import { - COMMAND_INSERT_JOB_CONFIG, - COMMAND_JOB_CONFIG_INSERT_RUNTIME_PLUGIN -} from '../../common/constants'; -import { __ } from '../../common/i18n'; -import { getSingleton, Singleton } from '../../common/singleton'; -import { Util } from '../../common/util'; -import { ClusterManager } from '../clusterManager'; -import { StorageHelper } from '../storage/storageHelper'; -import { IPAICluster } from '../utility/paiInterface'; - -import { - IYamlJobConfigSnippet, - YamlJobConfigRuntimePlugins, - YamlJobConfigSnippets -} from './yamlJobConfigCompletionProvider'; - -export interface IKeyPair { - public: string; - private: string; -} - -export function generateSSHKeyPair(bits: number = 1024): IKeyPair { - const key: NodeRSA = new NodeRSA({ b: bits }); - const pemPub: string = key.exportKey('pkcs1-public-pem'); - const pemPri: string = key.exportKey('pkcs1-private-pem'); - - const sshKey: SshPK.Key = SshPK.parseKey(pemPub, 'pem'); - sshKey.comment = 'pai-job-ssh'; - const sshPub: string = sshKey.toString('ssh'); - return { public: sshPub, private: pemPri }; -} - -/** - * Manager class for cluster configurations - */ -@injectable() -export class YamlCommands extends Singleton { - private runtimePluginSnippets: IYamlJobConfigSnippet[] = []; - private componentSnippets: IYamlJobConfigSnippet[] = []; - - constructor() { - super(); - this.loadSnippets(); - } - - public async onActivate(): Promise { - this.context.subscriptions.push( - commands.registerCommand( - COMMAND_INSERT_JOB_CONFIG, - async (document: TextDocument, position: Position) => - this.insertJobConfig(document, position) - ), - commands.registerCommand( - COMMAND_JOB_CONFIG_INSERT_RUNTIME_PLUGIN, - async (document: TextDocument, position: Position, withoutPrefix?: boolean) => - this.insertRuntimePlugin(document, position, withoutPrefix) - ), - languages.registerCompletionItemProvider('yaml', new YamlJobConfigSnippets()), - languages.registerCompletionItemProvider('yaml', new YamlJobConfigRuntimePlugins(), ':') - ); - } - - public async insertJobConfig(document: TextDocument, position: Position): Promise { - const items: QuickPickItem[] = this.componentSnippets.map(component => { - return { - label: component.label, - description: component.documentation - }; - }); - - const pluginItemLabel: string = 'OpenPAI Runtime Plugin Item'; - items.push({ - label: pluginItemLabel, - description: 'OpenPAI Runtime Plugin Item.' - }); - - const pick: number | undefined = await Util.pick( - range(items.length), - __('job.config.component.select'), - (index: number) => items[index] - ); - - if (pick !== undefined) { - const item: QuickPickItem = items[pick]; - if (item.label === pluginItemLabel) { - await this.insertRuntimePlugin(document, position); - } else { - const component: IYamlJobConfigSnippet = this.componentSnippets[pick]; - const editor: TextEditor = await window.showTextDocument(document); - await editor.insertSnippet(new SnippetString(component.insertText.trimRight())); - if (component.name === 'openPaiRunetimePlugin') { - await this.insertRuntimePlugin(document, position, true); - } - } - } - } - - public async insertRuntimePlugin( - document: TextDocument, position: Position, withoutPrefix?: boolean - ): Promise { - if (document) { - const linePrefix: string = position !== undefined ? - document.lineAt(position).text.substr(0, position.character) : ''; - let prefix: string = ''; - if (!linePrefix.endsWith('- plugin:') && !withoutPrefix) { - prefix = '- plugin:'; - } - - const pick: number | undefined = await this.pickPlugin(); - const editor: TextEditor = await window.showTextDocument(document); - if (pick !== undefined) { - const plugin: IYamlJobConfigSnippet = this.runtimePluginSnippets[pick]; - if (plugin.label === 'ssh') { - if (await this.enableUserSSH()) { - const publicKey: string | undefined = await this.getSshPublicKey(); - if (publicKey === undefined) { - await editor.insertSnippet(new SnippetString(prefix + plugin.insertText)); - } else { - await editor.insertSnippet( - new SnippetString(prefix + plugin.insertText.replace('\${1:}', publicKey) - )); - } - } else { - await editor.insertSnippet( - new SnippetString(prefix + plugin.insertText.substr(0, plugin.insertText.search('userssh:')) - )); - } - } else if (plugin.label === 'teamwise_storage') { - const storage: string | undefined = await this.pickStorage(); - if (storage === undefined) { - await editor.insertSnippet(new SnippetString(prefix + plugin.insertText)); - } else { - await editor.insertSnippet( - new SnippetString(prefix + plugin.insertText.replace('\${1:}', storage)) - ); - } - } else if (plugin.label === 'tensorboard') { - // tslint:disable-next-line:insecure-random - const port: number = Math.floor(Math.random() * 5000 + 10000); - await editor.insertSnippet( - new SnippetString(prefix + plugin.insertText.replace('{port}', port.toString(10))) - ); - } - } - } - } - - public async pickPlugin(): Promise { - return await Util.pick( - range(this.runtimePluginSnippets.length), - __('job.runtime.plugin.select'), - (index: number) => { - const snippet: IYamlJobConfigSnippet = this.runtimePluginSnippets[index]; - return { - label: snippet.label, - detail: snippet.documentation - }; - } - ); - } - - private async pickCluster(): Promise { - const clusterManager: ClusterManager = await getSingleton(ClusterManager); - const pickResult: number | undefined = await clusterManager.pick(); - if (pickResult === undefined) { - return undefined; - } - return clusterManager.allConfigurations[pickResult]; - } - - private async pickStorage(): Promise { - const cluster: IPAICluster | undefined = await this.pickCluster(); - if (cluster) { - const storages: string[] = await StorageHelper.getStorages(cluster); - if (storages.length === 0) { - return undefined; - } else if (storages.length === 1) { - return storages[0]; - } - - const pick: number | undefined = await Util.pick( - range(storages.length), - __('storage.upload.pick.prompt'), - (index: number) => { - return { label: storages[index] }; - }); - if (pick !== undefined) { - return storages[pick]; - } else { - return undefined; - } - } else { - return undefined; - } - } - - private async enableUserSSH(): Promise { - const YES: QuickPickItem = { label: __('common.yes') }; - const NO: QuickPickItem = { label: __('common.no') }; - const item: QuickPickItem | undefined = await Util.pick( - [YES, NO], - __('job.runtime.plugin.user-ssh.enable') - ); - if (item === YES) { - return true; - } else { - return false; - } - } - - private async getSshPublicKey(): Promise { - const GENERATE_NEW: QuickPickItem = { label: __('job.runtime.plugin.ssh.key.generator') }; - const IMPORT_FROM_FILE: QuickPickItem = { label: __('job.runtime.plugin.ssh.key.import') }; - const INPUT_MANULLY: QuickPickItem = { label: __('job.runtime.plugin.ssh.key.input') }; - - const item: QuickPickItem | undefined = await Util.pick( - [GENERATE_NEW, IMPORT_FROM_FILE, INPUT_MANULLY], - __('job.runtime.plugin.ssh.key.select') - ); - - if (item === GENERATE_NEW) { - const keyPair: IKeyPair = generateSSHKeyPair(); - const privateKeySaveDir: string = path.join(os.homedir(), 'privateKey.pem'); - const publicKeySaveDir: string = path.join(os.homedir(), 'publicKey.pem'); - - let saveDir: Uri | undefined = await window.showSaveDialog({ - defaultUri: Uri.file(privateKeySaveDir), - filters: { PEM: ['pem'] } - }); - - if (saveDir) { - await fs.writeFile(saveDir.fsPath, keyPair.private); - } - - saveDir = await window.showSaveDialog({ - defaultUri: Uri.file(publicKeySaveDir), - filters: { PEM: ['pem'] } - }); - - if (saveDir) { - await fs.writeFile(saveDir.fsPath, keyPair.public); - } - - return keyPair.public; - } else if (item === IMPORT_FROM_FILE) { - const importDir: Uri[] | undefined = await window.showOpenDialog({ - defaultUri: Uri.parse(os.homedir()), - filters: { - PEM: ['pem', '*'] - } - }); - - if (importDir && importDir.length === 1) { - return fs.readFileSync(importDir[0].fsPath, 'utf8'); - } - } else { - return undefined; - } - } - - private loadSnippets(): void { - const runtimePluginRoot: string = path.join(__dirname, '../../../snippets/runtimePlugins'); - this.runtimePluginSnippets = fs - .readdirSync(runtimePluginRoot) - .filter(x => x.endsWith('.yaml')) - .map(f => yaml.safeLoad(fs.readFileSync(path.join(runtimePluginRoot, f), 'utf-8'))); - const componentRoot: string = path.join(__dirname, '../../../snippets'); - this.componentSnippets = fs - .readdirSync(componentRoot) - .filter(x => x.endsWith('.yaml')) - .map(f => yaml.safeLoad(fs.readFileSync(path.join(componentRoot, f), 'utf-8'))); - } -} diff --git a/contrib/pai_vscode/src/pai/yaml/yamlJobConfigCompletionProvider.ts b/contrib/pai_vscode/src/pai/yaml/yamlJobConfigCompletionProvider.ts deleted file mode 100644 index 3baee1f7c8..0000000000 --- a/contrib/pai_vscode/src/pai/yaml/yamlJobConfigCompletionProvider.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import * as path from 'path'; -import { - Command, - CompletionItem, - CompletionItemKind, - CompletionItemProvider, - Position, - SnippetString, - TextDocument -} from 'vscode'; - -import { COMMAND_JOB_CONFIG_INSERT_RUNTIME_PLUGIN } from '../../common/constants'; -import { __ } from '../../common/i18n'; - -export interface IYamlJobConfigSnippet { - readonly name: string; - readonly label: string; - readonly documentation: string; - readonly insertText: string; -} - -/** - * An OpenPAI completion provider provides yaml code snippets for job config. - */ -export class YamlJobConfigSnippets implements CompletionItemProvider { - private snippets: IYamlJobConfigSnippet[] = []; - - public constructor() { - this.loadSnippets(); - } - - public provideCompletionItems(document: TextDocument): CompletionItem[] | undefined { - const command: Command = { - command: COMMAND_JOB_CONFIG_INSERT_RUNTIME_PLUGIN, - arguments: [document, undefined, true], - title: __('job.runtime.plugin.insert') - }; - - return this.snippets.map(x => { - const item: CompletionItem = new CompletionItem(x.label, CompletionItemKind.Snippet); - item.insertText = new SnippetString(x.insertText.trimRight()); - item.documentation = x.documentation; - if (x.name === 'openPaiRunetimePlugin') { - item.command = command; - } - return item; - }); - } - - private loadSnippets(): void { - const root: string = path.join(__dirname, '../../../snippets'); - this.snippets = fs - .readdirSync(root) - .filter(x => x.endsWith('.yaml')) - .map(f => yaml.safeLoad(fs.readFileSync(path.join(root, f), 'utf-8'))); - } -} - -/** - * An OpenPAI completion provider provides pai runtime plugin completion for job config. - */ -export class YamlJobConfigRuntimePlugins implements CompletionItemProvider { - public async provideCompletionItems( - document: TextDocument, position: Position - ): Promise { - const linePrefix: string = - document.lineAt(position).text.substr(0, position.character); - if (!linePrefix.endsWith('- plugin:')) { - return undefined; - } - - const item: CompletionItem = new CompletionItem(__('job.runtime.plugin.insert'), CompletionItemKind.Operator); - item.insertText = ''; - item.command = { - command: COMMAND_JOB_CONFIG_INSERT_RUNTIME_PLUGIN, - arguments: [document, position], - title: __('job.runtime.plugin.insert') - }; - return [item]; - } -} diff --git a/contrib/pai_vscode/src/pai/yaml/yamlSchemaSupport.ts b/contrib/pai_vscode/src/pai/yaml/yamlSchemaSupport.ts deleted file mode 100644 index 409c01bafe..0000000000 --- a/contrib/pai_vscode/src/pai/yaml/yamlSchemaSupport.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as fs from 'fs-extra'; -import * as _ from 'lodash'; -import { parse, util, YamlDocument, YamlNode } from 'node-yaml-parser'; -import * as vscode from 'vscode'; - -import { - OPENPAI_SCHEMA, - OPENPAI_YAML_SCHEMA_PREFIX, - SCHEMA_YAML_JOB_CONFIG, - SCHEMA_YAML_JOB_CONFIG_PATH, - YAML_EXTENSION_ID -} from '../../common/constants'; - -/** - * The yaml schema holder. - */ -class OpenPaiYamlSchemaHolder { - private _schema: string | undefined; - - public async loadSchema(schemaPath: string): Promise { - this._schema = await fs.readFile(schemaPath, 'utf8'); - } - - public schema(): string { - return this._schema!; - } -} - -const openPaiYamlSchemaHolder: OpenPaiYamlSchemaHolder = new OpenPaiYamlSchemaHolder(); - -export async function registerYamlSchemaSupport(): Promise { - await openPaiYamlSchemaHolder.loadSchema(SCHEMA_YAML_JOB_CONFIG_PATH); - const yamlPlugin: any = await activateYamlExtension(); - if (!yamlPlugin || !yamlPlugin.registerContributor) { - // activateYamlExtension has already alerted to users for errors. - return; - } - - // register for openpai yaml schema provider - yamlPlugin.registerContributor(OPENPAI_SCHEMA, requestYamlSchemaUriCallback, requestYamlSchemaContentCallback); -} - -async function activateYamlExtension(): Promise { - const ext: any = vscode.extensions.getExtension(YAML_EXTENSION_ID); - - if (!ext) { - await vscode.window.showWarningMessage('Please install \'YAML Support by Red Hat\' via the Extensions pane.'); - return undefined; - } - const yamlPlugin: any = await ext.activate(); - - if (!yamlPlugin || !yamlPlugin.registerContributor) { - await vscode.window.showWarningMessage('Please upgrade \'YAML Support by Red Hat\' via the Extensions pane.'); - return undefined; - } - - return yamlPlugin; -} - -function requestYamlSchemaUriCallback(resource: string): string | undefined { - const textEditor: vscode.TextEditor | undefined = - vscode.window.visibleTextEditors.find((editor) => editor.document.uri.toString() === resource); - const paiYamlJobConfigSchemaUri: string = OPENPAI_YAML_SCHEMA_PREFIX + SCHEMA_YAML_JOB_CONFIG; - - if (textEditor) { - if (textEditor.document.uri.toString().toLowerCase().endsWith('.pai.yaml')) { - // If the yaml file name match '*.pai.yaml', it will report it is a pai job conifg file. - return paiYamlJobConfigSchemaUri; - } - - const docs: YamlDocument[] = parse(textEditor.document.getText()).documents; - for (const [, x] of docs.entries()) { - const top: YamlNode | undefined = x.nodes.find(util.isMapping); - if (top) { - // If the yaml document contains 'protocolVersion: 2', it will report it is a pai job conifg file. - const item: any = top.mappings.find( - (mapping: { key: { raw: string; }; }) => mapping.key && - _.isString(mapping.key.raw) && - mapping.key.raw.toLowerCase() === 'protocolversion'); - - if (item && (item.value.raw === '2' || item.value.raw === 2)) { - return paiYamlJobConfigSchemaUri; - } - } - } - return undefined; - } - return undefined; -} - -function requestYamlSchemaContentCallback(uri: string): string | undefined { - const parsedUri: vscode.Uri = vscode.Uri.parse(uri); - if (parsedUri.scheme !== OPENPAI_SCHEMA) { - return undefined; - } - if (!parsedUri.path || !parsedUri.path.startsWith('/')) { - return undefined; - } - - return openPaiYamlSchemaHolder.schema(); -} diff --git a/contrib/pai_vscode/src/root.ts b/contrib/pai_vscode/src/root.ts deleted file mode 100644 index 3eb7a9cf74..0000000000 --- a/contrib/pai_vscode/src/root.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import { Singleton } from './common/singleton'; -import { TreeViewHelper } from './common/treeViewHelper'; -import { UtilClass } from './common/util'; -import { ClusterManager } from './pai/clusterManager'; -import { ConfigurationTreeDataProvider } from './pai/container/configurationTreeDataProvider'; -import { HDFSTreeDataProvider } from './pai/container/hdfsTreeView'; -import { JobListTreeDataProvider } from './pai/container/jobListTreeView'; -import { StorageTreeDataProvider } from './pai/container/storage/storageTreeView'; -import { PAIJobManager } from './pai/paiJobManager'; -import { PAIWebpages } from './pai/paiWebpages'; -import { RecentJobManager } from './pai/recentJobManager'; -import { HDFS } from './pai/storage/hdfs'; -import { NfsStorageManager } from './pai/storage/nfsStorageManager'; -import { PersonalStorageManager } from './pai/storage/personalStorageManager'; -import { StorageHelperClass } from './pai/storage/storageHelper'; -import { RemoteFileEditor } from './pai/utility/remoteFileEditor'; -import { YamlCommands } from './pai/yaml/yamlCommands'; - -export const allSingletonClasses: (new(...arg: any[]) => Singleton)[] = [ - UtilClass, - ClusterManager, - RecentJobManager, - TreeViewHelper, - ConfigurationTreeDataProvider, - PAIJobManager, - PAIWebpages, - HDFS, - NfsStorageManager, - PersonalStorageManager, - HDFSTreeDataProvider, - StorageTreeDataProvider, - JobListTreeDataProvider, - RemoteFileEditor, - StorageHelperClass, - YamlCommands -]; diff --git a/contrib/pai_vscode/src/test/clusterConfiguration.test.ts b/contrib/pai_vscode/src/test/clusterConfiguration.test.ts deleted file mode 100644 index 439050a955..0000000000 --- a/contrib/pai_vscode/src/test/clusterConfiguration.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ -// tslint:disable:align -import * as assert from 'assert'; - -import { getSingleton, waitForAllSingletonFinish } from '../common/singleton'; -import { ClusterManager } from '../pai/clusterManager'; - -async function asyncAssertThrows(fn: (...args: any[]) => Promise, message: string): Promise { - try { - await fn(); - assert.fail(message); - } catch { } -} - -async function asyncAssertDoesNotThrow(fn: (...args: any[]) => Promise, message: string): Promise { - try { - await fn(); - } catch { - assert.fail(message); - } -} - -suite('PAI Cluster Configurations', () => { - test('Configuration Validation', async () => { - await waitForAllSingletonFinish(); - const clusterManager: ClusterManager = await getSingleton(ClusterManager); - clusterManager.allConfigurations[0] = {}; - await asyncAssertThrows(async () => { - await clusterManager.validateConfiguration(); - }, 'Invalid configuration should not pass validation'); - clusterManager.allConfigurations[0] = null; - await asyncAssertThrows(async () => { - await clusterManager.validateConfiguration(); - }, 'Null configuration should not pass validation'); - clusterManager.allConfigurations[0] = { - username: 'openmindstudio', - password: 'Passq1w2e3r4', - rest_server_uri: '10.151.40.234:9186', - webhdfs_uri: '10.151.40.234:50070/webhdfs/v1', - grafana_uri: '10.151.40.234:3000', - k8s_dashboard_uri: '10.151.40.234:9090' - }; - await asyncAssertDoesNotThrow(async () => { - await clusterManager.validateConfiguration(); - }, 'Valid configuration should not trigger error'); - }); -}); \ No newline at end of file diff --git a/contrib/pai_vscode/src/test/fixtures/hdfs.filecrud.json b/contrib/pai_vscode/src/test/fixtures/hdfs.filecrud.json deleted file mode 100644 index f4241b18f6..0000000000 --- a/contrib/pai_vscode/src/test/fixtures/hdfs.filecrud.json +++ /dev/null @@ -1,311 +0,0 @@ -[ - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfile?op=getfilestatus&user.name=openmindstudio", - "body": "", - "status": 404, - "response": { - "RemoteException": { - "exception": "FileNotFoundException", - "javaClassName": "java.io.FileNotFoundException", - "message": "File does not exist: /testfile" - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:27 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:27 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:27 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326327132&s=gk5VVv2FU8E1gFEvG1568VFjmoM=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "PUT", - "path": "/webhdfs/api/v1/testfile?op=create&user.name=openmindstudio&overwrite=true&permission=0755", - "body": "", - "status": 307, - "response": "\r\n307 Temporary Redirect\r\n\r\n

307 Temporary Redirect

\r\n
nginx/1.13.8
\r\n\r\n\r\n", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:27 GMT", - "Content-Type", - "text/html", - "Content-Length", - "187", - "Connection", - "close", - "Location", - "http://10.151.40.179/a/10.151.40.173:5075/webhdfs/v1/testfile?op=CREATE&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&createflag=&createparent=true&overwrite=true&permission=755" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "PUT", - "path": "/a/10.151.40.173:5075/webhdfs/v1/testfile?op=CREATE&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&createflag=&createparent=true&overwrite=true&permission=755", - "body": "fa", - "status": 201, - "response": "", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:28 GMT", - "Content-Length", - "0", - "Connection", - "close", - "Location", - "hdfs://10.151.40.179:9000/testfile", - "Access-Control-Allow-Origin", - "*" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/?op=liststatus&user.name=openmindstudio", - "body": "", - "status": 200, - "response": { - "FileStatuses": { - "FileStatus": [ - { - "accessTime": 1540290328498, - "blockSize": 134217728, - "childrenNum": 0, - "fileId": 1462363, - "group": "supergroup", - "length": 2, - "modificationTime": 1540290328561, - "owner": "openmindstudio", - "pathSuffix": "testfile", - "permission": "755", - "replication": 2, - "storagePolicy": 0, - "type": "FILE" - } - ] - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:29 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:29 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:29 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326329131&s=/oM4SlZTxgdsV9rMa8qSvPltWGs=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfile?op=getfilestatus&user.name=openmindstudio", - "body": "", - "status": 200, - "response": { - "FileStatus": { - "accessTime": 1540290328498, - "blockSize": 134217728, - "childrenNum": 0, - "fileId": 1462363, - "group": "supergroup", - "length": 2, - "modificationTime": 1540290328561, - "owner": "openmindstudio", - "pathSuffix": "", - "permission": "755", - "replication": 2, - "storagePolicy": 0, - "type": "FILE" - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:29 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:29 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:29 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326329678&s=ztLCG83pRcYK5HPcXeciuam13t4=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfile?op=open&user.name=openmindstudio", - "body": "", - "status": 307, - "response": "\r\n307 Temporary Redirect\r\n\r\n

307 Temporary Redirect

\r\n
nginx/1.13.8
\r\n\r\n\r\n", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:30 GMT", - "Content-Type", - "text/html", - "Content-Length", - "187", - "Connection", - "close", - "Location", - "http://10.151.40.179/a/10.151.40.192:5075/webhdfs/v1/testfile?op=OPEN&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&offset=0" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/a/10.151.40.192:5075/webhdfs/v1/testfile?op=OPEN&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&offset=0", - "body": "", - "status": 200, - "response": "fa", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:30 GMT", - "Content-Type", - "application/octet-stream", - "Content-Length", - "2", - "Connection", - "close", - "Access-Control-Allow-Methods", - "GET", - "Access-Control-Allow-Origin", - "*" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "DELETE", - "path": "/webhdfs/api/v1/testfile?op=delete&user.name=openmindstudio&recursive=false", - "body": "", - "status": 200, - "response": { - "boolean": true - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:31 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:31 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:31 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326331321&s=5bGdVMMD5Ms1MtMs3de1iWsRhsM=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/?op=liststatus&user.name=openmindstudio", - "body": "", - "status": 200, - "response": { - "FileStatuses": { - "FileStatus": [ - ] - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:31 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:31 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:31 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326331877&s=yenDxednKO6xQad9BGHVgLKlRpI=\"; Path=/; HttpOnly" - ] - } -] \ No newline at end of file diff --git a/contrib/pai_vscode/src/test/fixtures/hdfs.foldercrud.json b/contrib/pai_vscode/src/test/fixtures/hdfs.foldercrud.json deleted file mode 100644 index dd1cd98dca..0000000000 --- a/contrib/pai_vscode/src/test/fixtures/hdfs.foldercrud.json +++ /dev/null @@ -1,984 +0,0 @@ -[ - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfolder?op=getfilestatus&user.name=openmindstudio", - "body": "", - "status": 404, - "response": { - "RemoteException": { - "exception": "FileNotFoundException", - "javaClassName": "java.io.FileNotFoundException", - "message": "File does not exist: /testfolder" - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:32 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:32 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:32 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326332434&s=Q9Fc+bmH/RWv37z+C9BjbUxYrIg=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/?op=getfilestatus&user.name=openmindstudio", - "body": "", - "status": 200, - "response": { - "FileStatus": { - "accessTime": 0, - "blockSize": 0, - "childrenNum": 43, - "fileId": 16385, - "group": "supergroup", - "length": 0, - "modificationTime": 1540290331322, - "owner": "root", - "pathSuffix": "", - "permission": "777", - "replication": 0, - "storagePolicy": 0, - "type": "DIRECTORY" - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:32 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:32 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:32 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326332980&s=Ddp574MNEvCQLI3uVrhtOvEQY40=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "PUT", - "path": "/webhdfs/api/v1/testfolder?op=mkdirs&user.name=openmindstudio&permission=0755", - "body": "", - "status": 200, - "response": { - "boolean": true - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:33 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:33 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:33 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326333524&s=qP0MSTMAYYHAZ08nYzgOzEsvCOc=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/?op=liststatus&user.name=openmindstudio", - "body": "", - "status": 200, - "response": { - "FileStatuses": { - "FileStatus": [ - { - "accessTime": 0, - "blockSize": 0, - "childrenNum": 0, - "fileId": 1462364, - "group": "supergroup", - "length": 0, - "modificationTime": 1540290333525, - "owner": "openmindstudio", - "pathSuffix": "testfolder", - "permission": "755", - "replication": 0, - "storagePolicy": 0, - "type": "DIRECTORY" - } - ] - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:36 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:36 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:36 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326336952&s=1dZdmusWrgzIxi7I2sEXklgQRBQ=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfolder/testfile?op=getfilestatus&user.name=openmindstudio", - "body": "", - "status": 404, - "response": { - "RemoteException": { - "exception": "FileNotFoundException", - "javaClassName": "java.io.FileNotFoundException", - "message": "File does not exist: /testfolder/testfile" - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:37 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:37 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:37 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326337499&s=xDswav++C2c643LPgkMeEb4QX64=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "PUT", - "path": "/webhdfs/api/v1/testfolder/testfile?op=create&user.name=openmindstudio&overwrite=true&permission=0755", - "body": "", - "status": 307, - "response": "\r\n307 Temporary Redirect\r\n\r\n

307 Temporary Redirect

\r\n
nginx/1.13.8
\r\n\r\n\r\n", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:38 GMT", - "Content-Type", - "text/html", - "Content-Length", - "187", - "Connection", - "close", - "Location", - "http://10.151.40.179/a/10.151.40.197:5075/webhdfs/v1/testfolder/testfile?op=CREATE&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&createflag=&createparent=true&overwrite=true&permission=755" - ] - }, - { - "scope": "https://go.microsoft.com:443", - "method": "GET", - "path": "/fwlink/?linkid=2011061", - "body": "", - "status": 302, - "response": "", - "rawHeaders": [ - "Location", - "https://raw.githubusercontent.com/Microsoft/samples-for-ai/master/examples/resourcePanel.vscode.zh-cn.json", - "Server", - "Kestrel", - "Request-Context", - "appId=cid-v1:9b037ab9-fa5a-4c09-81bd-41ffa859f01e", - "X-Response-Cache-Status", - "True", - "X-Powered-By", - "ASP.NET", - "Content-Length", - "0", - "Expires", - "Tue, 23 Oct 2018 10:25:38 GMT", - "Cache-Control", - "max-age=0, no-cache, no-store", - "Pragma", - "no-cache", - "Date", - "Tue, 23 Oct 2018 10:25:38 GMT", - "Connection", - "close", - "Strict-Transport-Security", - "max-age=31536000 ; includeSubDomains" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "PUT", - "path": "/a/10.151.40.197:5075/webhdfs/v1/testfolder/testfile?op=CREATE&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&createflag=&createparent=true&overwrite=true&permission=755", - "body": "fa", - "status": 201, - "response": "", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:38 GMT", - "Content-Length", - "0", - "Connection", - "close", - "Location", - "hdfs://10.151.40.179:9000/testfolder/testfile", - "Access-Control-Allow-Origin", - "*" - ] - }, - { - "scope": "https://raw.githubusercontent.com:443", - "method": "GET", - "path": "/Microsoft/samples-for-ai/master/examples/resourcePanel.vscode.zh-cn.json", - "body": "", - "status": 200, - "response": [ - "1f8b0800000000000003b5544d4bc34010bdf757849cdb2e8807e9ad8a480e4a510f8278d8acdb6625c984dd8d54c583a82751bc090a6245a417db837a68c1fa679a7efc0bf3a1b54a2b49d53d2c6c32f3f6bd37b3b39e52fcb517eec15225932655738a9ad714efe1beddb8e93d1f759ae76afa3386802da92dfda8f5c1c7af3083501b5b219ad7aaf55eea8a0fdaabb7bad59321b841accbcd20d490d2113984043100cc2cde7539cd121b119773465cd3b5049a9a190540b72356aa01167570892270a89dd7e6c0e582aa5f12f6d3c9a97ba7975eed360675dd8492c85a8c70105094590216c20c25e13ceb437c633c386da486148caadd029386ab2bddc387fe453560deaf34ba57b55fd67065492bcc2fc7905f0aaf0f555bd8d8c1dcc65b4c67683cc068172219050e5b94c849cb67614293715e631816191a97f88f5c39e820b04c6ab1a34319fd909b9c71fc56eb5c3d7977977f322322a8a05d3bd58a777dd2bd6bb65b7126c590198b1f6f0e096c3926159922f08cfff624a7d4b74a48ca112d47ffe27bb512254ce7b5c8b58967c9f163bb79a6acfa834d283eb3506c28bbfd5ae91cd42715bb2d086cd28c0c70df15c71717b21927ed7b27a436de00b221d14033060000" - ], - "rawHeaders": [ - "Content-Security-Policy", - "default-src 'none'; style-src 'unsafe-inline'; sandbox", - "Strict-Transport-Security", - "max-age=31536000", - "X-Content-Type-Options", - "nosniff", - "X-Frame-Options", - "deny", - "X-XSS-Protection", - "1; mode=block", - "ETag", - "\"f047237da3a1e0a412a2167cc835b1c2774094b4\"", - "Content-Type", - "text/plain; charset=utf-8", - "Cache-Control", - "max-age=300", - "X-Geo-Block-List", - "", - "X-GitHub-Request-Id", - "A782:92A8:2C9E200:2EA7993:5BCEF722", - "Content-Encoding", - "gzip", - "Content-Length", - "458", - "Accept-Ranges", - "bytes", - "Date", - "Tue, 23 Oct 2018 10:25:39 GMT", - "Via", - "1.1 varnish", - "Connection", - "close", - "X-Served-By", - "cache-nrt6135-NRT", - "X-Cache", - "MISS", - "X-Cache-Hits", - "0", - "X-Timer", - "S1540290339.103086,VS0,VE209", - "Vary", - "Authorization,Accept-Encoding", - "Access-Control-Allow-Origin", - "*", - "X-Fastly-Request-ID", - "af3612b2eb0b8fdfb304077dfd62a7d9014dc623", - "Expires", - "Tue, 23 Oct 2018 10:30:39 GMT", - "Source-Age", - "0" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfolder?op=liststatus&user.name=openmindstudio", - "body": "", - "status": 200, - "response": { - "FileStatuses": { - "FileStatus": [ - { - "accessTime": 1540290338874, - "blockSize": 134217728, - "childrenNum": 0, - "fileId": 1462365, - "group": "supergroup", - "length": 2, - "modificationTime": 1540290338954, - "owner": "openmindstudio", - "pathSuffix": "testfile", - "permission": "755", - "replication": 2, - "storagePolicy": 0, - "type": "FILE" - } - ] - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:39 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:39 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:39 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326339524&s=vdMK+btmPV3SHQZ/JJY3IpgrDfo=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfolder/testfile?op=getfilestatus&user.name=openmindstudio", - "body": "", - "status": 200, - "response": { - "FileStatus": { - "accessTime": 1540290338874, - "blockSize": 134217728, - "childrenNum": 0, - "fileId": 1462365, - "group": "supergroup", - "length": 2, - "modificationTime": 1540290338954, - "owner": "openmindstudio", - "pathSuffix": "", - "permission": "755", - "replication": 2, - "storagePolicy": 0, - "type": "FILE" - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:40 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:40 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:40 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326340075&s=naX/5t/++PtpdkRKpgaeuuSxx1g=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfolder/testfile?op=open&user.name=openmindstudio", - "body": "", - "status": 307, - "response": "\r\n307 Temporary Redirect\r\n\r\n

307 Temporary Redirect

\r\n
nginx/1.13.8
\r\n\r\n\r\n", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:40 GMT", - "Content-Type", - "text/html", - "Content-Length", - "187", - "Connection", - "close", - "Location", - "http://10.151.40.179/a/10.151.40.163:5075/webhdfs/v1/testfolder/testfile?op=OPEN&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&offset=0" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/a/10.151.40.163:5075/webhdfs/v1/testfolder/testfile?op=OPEN&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&offset=0", - "body": "", - "status": 200, - "response": "fa", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:41 GMT", - "Content-Type", - "application/octet-stream", - "Content-Length", - "2", - "Connection", - "close", - "Access-Control-Allow-Methods", - "GET", - "Access-Control-Allow-Origin", - "*" - ] - }, - { - "scope": "https://vortex.data.microsoft.com:443", - "method": "POST", - "path": "/collect/v1", - "body": "1f8b080000000000000aed54cb6edb3010bcf72b0a9e2d96929ff24db16cc0c8c38623a4682f0625ae6322922890b49d20f0bf7729abf1a3688f458ae62241b3b3b3a3e52e5fc9163419fa2d627851e5b0e016f093b116b1fcd190e12be192f2aaca65c6ad5425adf92460fe8086d4272d1717b095195029307240b25c6d04d52a8769692c2f335425ddc97d1c5f056739ca3c8036288cf1afb2146a679677c9679f5146fd5e1086977a8e07e98546a4b3b5b490d98d7684e75ee78230cfb95d295d607027cb76e341961674c9736ac4d3d146a9040c7dacdf25fb1611dc72d786941b485e2a273fde42696387b76a386e28756b8216a9b4aa405b0975ffa0e4690eae352b9e1bc09c4c150576529913370d56353eb76f6ece3ad1b0e0d996bc70560ae3552f76ed32ebd719e72872725c4d786b32fccd8263df4aa80fcea8021e78be01daa053714937609ce005bd417fa59ffc040d06581ddbf91bdf5f46b3dbf9cd3899ceee6814c7cbab4534ba1e27f7648f39f21a5e30279a4e3c11a67d96898e9786abd0eb883ef3c2204c3dcefa7ed6612c0dfc3e79ab722b33ad8c5a591a1d271807523eaeada1a8f753cea93931a7752a45eba346412b6b41d747cf675ed04e7c360cbac3a047835eef3bd97f7afdd8a48f4d7a0f9b34992d6ea3841e5ecbd9dd32f9361fff137bd4ffd8a3bfb247992a053faa7768973a7758b990581ba14aed30bc863c47fc3057477e9bf63063555371a89ce34a033e1b3323278f919dd24fa6e2194c542e408fd4064760481886d6dccc6bd53602566ffef75d2e801b1c94024fb1392101378a0b593e26f5c6f8ace336c2726da3ccca2d2ec52110b4fdb08537a33887c381dffec31d318ea7c96c416f6651fcde6f86769f0e060c6f861f330464f2ab0a0000", - "status": 200, - "response": { - "ipv": false, - "pvm": null, - "rej": 0, - "bln": 0, - "acc": 3, - "efi": [] - }, - "rawHeaders": [ - "Cache-Control", - "no-cache, no-store", - "Content-Length", - "57", - "Content-Type", - "application/json", - "X-Content-Type-Options", - "nosniff", - "MS-CV", - "BFLAOkmqL0WKBtdeaQTsyw.0", - "Date", - "Tue, 23 Oct 2018 10:25:41 GMT", - "Connection", - "close" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfolder/testfile?op=getfilestatus&user.name=openmindstudio", - "body": "", - "status": 200, - "response": { - "FileStatus": { - "accessTime": 1540290338874, - "blockSize": 134217728, - "childrenNum": 0, - "fileId": 1462365, - "group": "supergroup", - "length": 2, - "modificationTime": 1540290338954, - "owner": "openmindstudio", - "pathSuffix": "", - "permission": "755", - "replication": 2, - "storagePolicy": 0, - "type": "FILE" - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:41 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:41 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:41 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326341716&s=2F6mlqNQ4PzTG9uOHqNsRm8ffZc=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfile?op=getfilestatus&user.name=openmindstudio", - "body": "", - "status": 404, - "response": { - "RemoteException": { - "exception": "FileNotFoundException", - "javaClassName": "java.io.FileNotFoundException", - "message": "File does not exist: /testfile" - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:42 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:42 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:42 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326342261&s=rlPchznXTNlYGCetg1O4JTyKMYo=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "PUT", - "path": "/webhdfs/api/v1/testfile?op=create&user.name=openmindstudio&overwrite=true&permission=0755", - "body": "", - "status": 307, - "response": "\r\n307 Temporary Redirect\r\n\r\n

307 Temporary Redirect

\r\n
nginx/1.13.8
\r\n\r\n\r\n", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:42 GMT", - "Content-Type", - "text/html", - "Content-Length", - "187", - "Connection", - "close", - "Location", - "http://10.151.40.179/a/10.151.40.155:5075/webhdfs/v1/testfile?op=CREATE&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&createflag=&createparent=true&overwrite=true&permission=755" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfolder/testfile?op=open&user.name=openmindstudio", - "body": "", - "status": 307, - "response": "\r\n307 Temporary Redirect\r\n\r\n

307 Temporary Redirect

\r\n
nginx/1.13.8
\r\n\r\n\r\n", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:42 GMT", - "Content-Type", - "text/html", - "Content-Length", - "187", - "Connection", - "close", - "Location", - "http://10.151.40.179/a/10.151.40.163:5075/webhdfs/v1/testfolder/testfile?op=OPEN&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&offset=0" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/a/10.151.40.163:5075/webhdfs/v1/testfolder/testfile?op=OPEN&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&offset=0", - "body": "", - "status": 200, - "response": "fa", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:43 GMT", - "Content-Type", - "application/octet-stream", - "Content-Length", - "2", - "Connection", - "close", - "Access-Control-Allow-Methods", - "GET", - "Access-Control-Allow-Origin", - "*" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "PUT", - "path": "/a/10.151.40.155:5075/webhdfs/v1/testfile?op=CREATE&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&createflag=&createparent=true&overwrite=true&permission=755", - "body": "fa", - "status": 201, - "response": "", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:44 GMT", - "Content-Length", - "0", - "Connection", - "close", - "Location", - "hdfs://10.151.40.179:9000/testfile", - "Access-Control-Allow-Origin", - "*" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfile?op=getfilestatus&user.name=openmindstudio", - "body": "", - "status": 200, - "response": { - "FileStatus": { - "accessTime": 1540290344229, - "blockSize": 134217728, - "childrenNum": 0, - "fileId": 1462371, - "group": "supergroup", - "length": 2, - "modificationTime": 1540290344278, - "owner": "openmindstudio", - "pathSuffix": "", - "permission": "755", - "replication": 2, - "storagePolicy": 0, - "type": "FILE" - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:44 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:44 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:44 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326344848&s=r127SsQb4khRDPJPtj3QG9DKRmA=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/testfile?op=open&user.name=openmindstudio", - "body": "", - "status": 307, - "response": "\r\n307 Temporary Redirect\r\n\r\n

307 Temporary Redirect

\r\n
nginx/1.13.8
\r\n\r\n\r\n", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:45 GMT", - "Content-Type", - "text/html", - "Content-Length", - "187", - "Connection", - "close", - "Location", - "http://10.151.40.179/a/10.151.40.155:5075/webhdfs/v1/testfile?op=OPEN&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&offset=0" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/a/10.151.40.155:5075/webhdfs/v1/testfile?op=OPEN&user.name=openmindstudio&namenoderpcaddress=10.151.40.179:9000&offset=0", - "body": "", - "status": 200, - "response": "fa", - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:45 GMT", - "Content-Type", - "application/octet-stream", - "Content-Length", - "2", - "Connection", - "close", - "Access-Control-Allow-Methods", - "GET", - "Access-Control-Allow-Origin", - "*" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "DELETE", - "path": "/webhdfs/api/v1/testfolder?op=delete&user.name=openmindstudio&recursive=true", - "body": "", - "status": 200, - "response": { - "boolean": true - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:46 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:46 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:46 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326346490&s=8XnwemCLLd/P6I9EwzN//aSrQWE=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/?op=liststatus&user.name=openmindstudio", - "body": "", - "status": 200, - "response": { - "FileStatuses": { - "FileStatus": [ - { - "accessTime": 1540290344229, - "blockSize": 134217728, - "childrenNum": 0, - "fileId": 1462371, - "group": "supergroup", - "length": 2, - "modificationTime": 1540290344278, - "owner": "openmindstudio", - "pathSuffix": "testfile", - "permission": "755", - "replication": 2, - "storagePolicy": 0, - "type": "FILE" - } - ] - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:47 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:47 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:47 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326347054&s=alV2N5Y+EMV0EpKPX6N2WKWOSgQ=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "DELETE", - "path": "/webhdfs/api/v1/testfile?op=delete&user.name=openmindstudio&recursive=false", - "body": "", - "status": 200, - "response": { - "boolean": true - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:47 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:47 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:47 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326347601&s=0mElqQZsRHKt0BxNFklCEISWXS4=\"; Path=/; HttpOnly" - ] - }, - { - "scope": "http://10.151.40.179:80", - "method": "GET", - "path": "/webhdfs/api/v1/?op=liststatus&user.name=openmindstudio", - "body": "", - "status": 200, - "response": { - "FileStatuses": { - "FileStatus": [ - ] - } - }, - "rawHeaders": [ - "Server", - "nginx/1.13.8", - "Date", - "Tue, 23 Oct 2018 10:25:48 GMT", - "Content-Type", - "application/json", - "Transfer-Encoding", - "chunked", - "Connection", - "close", - "Cache-Control", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:48 GMT", - "Pragma", - "no-cache", - "Expires", - "Tue, 23 Oct 2018 10:25:48 GMT", - "Pragma", - "no-cache", - "X-FRAME-OPTIONS", - "SAMEORIGIN", - "Set-Cookie", - "hadoop.auth=\"u=openmindstudio&p=openmindstudio&t=simple&e=1540326348176&s=+hTXVxWY8OQdvca34KUognl77go=\"; Path=/; HttpOnly" - ] - } -] \ No newline at end of file diff --git a/contrib/pai_vscode/src/test/hdfs.test.ts b/contrib/pai_vscode/src/test/hdfs.test.ts deleted file mode 100644 index 9340fe1cdf..0000000000 --- a/contrib/pai_vscode/src/test/hdfs.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as assert from 'assert'; -import { ISuiteCallbackContext } from 'mocha'; -import { back as nockBack } from 'nock'; -import { join } from 'path'; -import { FileType, Uri } from 'vscode'; - -import { getSingleton, waitForAllSingletonFinish } from '../common/singleton'; -import { ClusterManager } from '../pai/clusterManager'; -import { HDFSFileSystemProvider } from '../pai/storage/hdfs'; - -// /out/test/../../src/test/fixures -nockBack.fixtures = join(__dirname, '../../src/test', 'fixtures'); -nockBack.setMode('record'); - -function mockUri(path: string): Uri { - return Uri.parse('webhdfs://openmindstudio@10.151.40.179' + path); -} - -suite('HDFS Client', function (this: ISuiteCallbackContext): void { - this.timeout(0); - - suiteSetup(async () => { - await waitForAllSingletonFinish(); - (await getSingleton(ClusterManager)).allConfigurations.push({ - name: 'Sample Cluster', - username: 'openmindstudio', - rest_server_uri: '10.151.40.179/rest-server', - webhdfs_uri: '10.151.40.179/webhdfs/api/v1', - grafana_uri: '10.151.40.179/grafana', - k8s_dashboard_uri: '10.151.40.179/kubernetes-dashboard', - web_portal_uri: '10.151.40.179/' - }); - }); - - test('File CRUD', async () => { - const hdfsProvider: HDFSFileSystemProvider = new HDFSFileSystemProvider(); - const { nockDone } = await nockBack('hdfs.filecrud.json'); - - await hdfsProvider.writeFile(mockUri('/testfile'), new Buffer('fa'), { create: true, overwrite: false }); - assert.deepEqual( - (await hdfsProvider.readDirectory(mockUri('/'))).find(file => file[0] === 'testfile'), - ['testfile', FileType.File], - 'Cannot find created file when listing directory' - ); - assert.equal( - (await hdfsProvider.readFile(mockUri('/testfile'))).toString(), - 'fa', - 'File content is different from original' - ); - await hdfsProvider.delete(mockUri('/testfile'), { recursive: false }); - assert.equal( - (await hdfsProvider.readDirectory(mockUri('/'))).find(file => file[0] === 'testfile'), - undefined, - 'Deleted file is still found when listing directory' - ); - - nockDone(); - }); - - test('Folder CRUD', async () => { - const hdfsProvider: HDFSFileSystemProvider = new HDFSFileSystemProvider(); - const { nockDone } = await nockBack('hdfs.foldercrud.json'); - await hdfsProvider.createDirectory(mockUri('/testfolder')); - assert.deepEqual( - (await hdfsProvider.readDirectory(mockUri('/'))).find(file => file[0] === 'testfolder'), - ['testfolder', FileType.Directory], - 'Cannot find created directory when listing directory' - ); - await hdfsProvider.writeFile(mockUri('/testfolder/testfile'), new Buffer('fa'), { create: true, overwrite: false }); - assert.deepEqual( - (await hdfsProvider.readDirectory(mockUri('/testfolder'))).find(file => file[0] === 'testfile'), - ['testfile', FileType.File], - 'Cannot find created file when listing directory' - ); - assert.equal( - (await hdfsProvider.readFile(mockUri('/testfolder/testfile'))).toString(), - 'fa', - 'File content is different from original' - ); - await hdfsProvider.copy(mockUri('/testfolder/testfile'), mockUri('/testfile'), { overwrite: false }); - assert.equal( - (await hdfsProvider.readFile(mockUri('/testfile'))).toString(), - 'fa', - 'Copied file content is different from original' - ); - await hdfsProvider.delete(mockUri('/testfolder'), { recursive: true }); - assert.equal( - (await hdfsProvider.readDirectory(mockUri('/'))).find(file => file[0] === 'testfolder'), - undefined, - 'Deleted folder is still found when listing directory' - ); - await hdfsProvider.delete(mockUri('/testfile'), { recursive: false }); - assert.equal( - (await hdfsProvider.readDirectory(mockUri('/'))).find(file => file[0] === 'testfile'), - undefined, - 'Deleted file is still found when listing directory' - ); - - nockDone(); - }); - - suiteTeardown(async () => { - (await getSingleton(ClusterManager)).allConfigurations.pop(); - }); -}); \ No newline at end of file diff --git a/contrib/pai_vscode/src/test/index.ts b/contrib/pai_vscode/src/test/index.ts deleted file mode 100644 index 1609019d6a..0000000000 --- a/contrib/pai_vscode/src/test/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as glob from 'glob'; -import * as Mocha from 'mocha'; -import * as path from 'path'; - -export function run(): Promise { - const mocha: Mocha = new Mocha({ - ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) - useColors: true // colored output from test results - }); - - const testsRoot: string = path.resolve(__dirname, '.'); - return new Promise((c, e) => { - glob('**/**.test.js', { cwd: testsRoot}, (err, files) => { - if (err) { - return e(err); - } - - files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); - - try { - mocha.run(failures => { - if (failures > 0) { - e(new Error(`${failures} tests failed.`)); - } else { - c(); - } - }); - } catch (err) { - console.error(err); - e(err); - } - }); - }); -} diff --git a/contrib/pai_vscode/src/test/runTest.ts b/contrib/pai_vscode/src/test/runTest.ts deleted file mode 100644 index 85463213ad..0000000000 --- a/contrib/pai_vscode/src/test/runTest.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License in the project root for license information. - * @author Microsoft - */ - -import * as path from 'path'; -import { runTests } from 'vscode-test'; - -async function main(): Promise { - try { - // The folder containing the Extension Manifest package.json - // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath: string = path.resolve(__dirname, '../../'); - - // The path to the extension test script - // Passed to --extensionTestsPath - const extensionTestsPath: string = path.resolve(__dirname, './index.js'); - - // Download VS Code, unzip it and run the integration test - await runTests({ extensionDevelopmentPath, extensionTestsPath }); - } catch (err) { - console.error('Failed to run tests'); - process.exit(1); - } -} - -void main(); \ No newline at end of file diff --git a/contrib/pai_vscode/tsconfig.json b/contrib/pai_vscode/tsconfig.json deleted file mode 100644 index 0ef336e976..0000000000 --- a/contrib/pai_vscode/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es2017", - "outDir": "out", - "lib": [ - "es2017" - ], - "sourceMap": true, - "rootDir": "src", - "strict": true, - "skipLibCheck": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "plugins": [{ - "name": "typescript-tslint-plugin", - "alwaysShowRuleFailuresAsWarnings": false, - "ignoreDefinitionFiles": true - }] - }, - "exclude": [ - "node_modules", - ".vscode-test" - ] -} \ No newline at end of file diff --git a/contrib/pai_vscode/tslint.json b/contrib/pai_vscode/tslint.json deleted file mode 100644 index f4bb5e9753..0000000000 --- a/contrib/pai_vscode/tslint.json +++ /dev/null @@ -1,281 +0,0 @@ -{ - "rules": { - "insecure-random": true, - "no-banned-terms": false, - "no-cookies": true, - "no-delete-expression": true, - "no-disable-auto-sanitization": true, - "no-document-domain": true, - "no-document-write": true, - "no-eval": true, - "no-exec-script": true, - "no-function-constructor-with-string-args": true, - "no-http-string": false, - "no-inner-html": true, - "no-octal-literal": true, - "no-reserved-keywords": false, - "no-string-based-set-immediate": true, - "no-string-based-set-interval": true, - "no-string-based-set-timeout": true, - "non-literal-require": true, - "possible-timing-attack": true, - "react-anchor-blank-noopener": true, - "react-iframe-missing-sandbox": true, - "react-no-dangerous-html": true, - "await-promise": [true, "Thenable", "PromiseLike", "RequestPromise"], - "forin": true, - "jquery-deferred-must-complete": true, - "label-position": true, - "match-default-export-name": true, - "mocha-avoid-only": true, - "mocha-no-side-effect-code": true, - "no-any": false, - "no-arg": true, - "no-backbone-get-set-outside-model": false, - "no-bitwise": true, - "no-conditional-assignment": true, - "no-console": false, - "no-constant-condition": false, - "no-control-regex": true, - "no-debugger": true, - "no-duplicate-super": true, - "no-duplicate-switch-case": true, - "no-duplicate-variable": true, - "no-empty": false, - "no-floating-promises": [true, "Thenable"], - "no-for-in-array": true, - "no-import-side-effect": true, - "no-increment-decrement": false, - "no-invalid-regexp": true, - "no-invalid-template-strings": true, - "no-invalid-this": true, - "no-jquery-raw-elements": true, - "no-misused-new": true, - "no-non-null-assertion": false, - "no-reference-import": true, - "no-regex-spaces": true, - "no-sparse-arrays": true, - "no-string-literal": true, - "no-string-throw": true, - "no-unnecessary-bind": true, - "no-unnecessary-class": true, - "no-unnecessary-callback-wrapper": true, - "no-unnecessary-initializer": true, - "no-unnecessary-override": true, - "no-unsafe-any": false, - "no-unsafe-finally": true, - "no-unused-expression": true, - "no-with-statement": true, - "promise-function-async": false, - "promise-must-complete": true, - "radix": true, - "react-this-binding-issue": [ true, { "allow-anonymous-listeners": true } ], - "react-unused-props-and-state": true, - "restrict-plus-operands": true, - "strict-boolean-expressions": false, - "switch-default": true, - "triple-equals": [ - true, - "allow-null-check" - ], - "use-isnan": true, - "use-named-parameter": true, - "adjacent-overload-signatures": true, - "array-type": [ - true, - "array" - ], - "arrow-parens": false, - "callable-types": true, - "chai-prefer-contains-to-index-of": true, - "chai-vague-errors": true, - "class-name": true, - "comment-format": true, - "completed-docs": [ - true, - "classes" - ], - "export-name": false, - "function-name": [ true, { - "method-regex": "^[a-z][\\w\\d]+$", - "private-method-regex": "^[a-z][\\w\\d]+$", - "protected-method-regex": "^[a-z][\\w\\d]+$", - "static-method-regex": "^[\\w\\d]+$", - "function-regex": "^[\\w\\d]+$" - }], - "import-name": true, - "interface-name": true, - "jsdoc-format": true, - "max-classes-per-file": [ - true, - 3 - ], - "max-file-line-count": true, - "max-func-body-length": [ - true, - 100, - { - "ignore-parameters-to-function-regex": "describe" - } - ], - "max-line-length": [ - true, - 140 - ], - "member-access": true, - "member-ordering": [ - true, - { - "order": "fields-first" - } - ], - "missing-jsdoc": true, - "mocha-unneeded-done": true, - "new-parens": true, - "no-construct": true, - "no-default-export": true, - "no-empty-interface": true, - "no-for-in": true, - "no-function-expression": true, - "no-inferrable-types": false, - "no-multiline-string": true, - "no-null-keyword": false, - "no-parameter-properties": true, - "no-relative-imports": false, - "no-require-imports": true, - "no-shadowed-variable": true, - "no-suspicious-comment": false, - "no-typeof-undefined": true, - "no-unnecessary-field-initialization": true, - "no-unnecessary-local-variable": true, - "no-unnecessary-qualifier": true, - "no-unsupported-browser-code": true, - "no-useless-files": true, - "no-var-keyword": true, - "no-var-requires": true, - "no-void-expression": false, - "object-literal-sort-keys": false, - "one-variable-per-declaration": true, - "only-arrow-functions": false, - "ordered-imports": [ - true, - { - "named-imports-order": "lowercase-first", - "grouped-imports": true - } - ], - "prefer-array-literal": false, - "prefer-const": true, - "prefer-for-of": true, - "prefer-method-signature": true, - "prefer-template": false, - "return-undefined": false, - "typedef": [ - true, - "call-signature", - "parameter", - "property-declaration", - "variable-declaration", - "member-variable-declaration" - ], - "underscore-consistent-invocation": true, - "unified-signatures": true, - "variable-name": [true, "check-format", "allow-leading-underscore", "allow-pascal-case"], - "react-a11y-anchors": true, - "react-a11y-aria-unsupported-elements": true, - "react-a11y-event-has-role": true, - "react-a11y-image-button-has-alt": true, - "react-a11y-img-has-alt": true, - "react-a11y-lang": true, - "react-a11y-meta": true, - "react-a11y-props": true, - "react-a11y-proptypes": true, - "react-a11y-role": true, - "react-a11y-role-has-required-aria-props": true, - "react-a11y-role-supports-aria-props": true, - "react-a11y-tabindex-no-positive": true, - "react-a11y-titles": true, - "align": [ - true, - "parameters", - "arguments", - "statements" - ], - "curly": true, - "eofline": false, - "import-spacing": true, - "indent": [ - true, - "spaces", - 4 - ], - "linebreak-style": true, - "newline-before-return": false, - "no-consecutive-blank-lines": true, - "no-empty-line-after-opening-brace": false, - "no-single-line-block-comment": false, - "no-trailing-whitespace": true, - "no-unnecessary-semicolons": true, - "object-literal-key-quotes": [ - true, - "as-needed" - ], - "one-line": [ - true, - "check-open-brace", - "check-catch", - "check-else", - "check-whitespace" - ], - "quotemark": [ - true, - "single" - ], - "react-tsx-curly-spacing": false, - "semicolon": [ - true, - "always" - ], - "trailing-comma": [ - true, - { - "singleline": "never", - "multiline": "never" - } - ], - "typedef-whitespace": false, - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ], - "ban": false, - "ban-types": true, - "cyclomatic-complexity": true, - "file-header": false, - "import-blacklist": false, - "interface-over-type-literal": false, - "no-angle-bracket-type-assertion": false, - "no-inferred-empty-object-type": false, - "no-internal-module": false, - "no-magic-numbers": false, - "no-mergeable-namespace": false, - "no-namespace": false, - "no-reference": true, - "no-unexternalized-strings": false, - "object-literal-shorthand": false, - "prefer-type-cast": true, - "space-before-function-paren": false, - "missing-optional-annotation": false, - "no-duplicate-parameter-names": false, - "no-empty-interfaces": false, - "no-missing-visibility-modifiers": false, - "no-multiple-var-decl": false, - "no-switch-case-fall-through": false, - "no-this-assignment": true - }, - "rulesDirectory": "node_modules/tslint-microsoft-contrib" -} \ No newline at end of file diff --git a/contrib/pai_vscode/yarn.lock b/contrib/pai_vscode/yarn.lock deleted file mode 100644 index d4eeb8c7a1..0000000000 --- a/contrib/pai_vscode/yarn.lock +++ /dev/null @@ -1,2388 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@azure/abort-controller@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.1.tgz#8510935b25ac051e58920300e9d7b511ca6e656a" - integrity sha512-wP2Jw6uPp8DEDy0n4KNidvwzDjyVV2xnycEIq7nPzj1rHyb/r+t3OPeNT1INZePP2wy5ZqlwyuyOMTi0ePyY1A== - dependencies: - tslib "^1.9.3" - -"@azure/core-asynciterator-polyfill@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz#dcccebb88406e5c76e0e1d52e8cc4c43a68b3ee7" - integrity sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg== - -"@azure/core-auth@^1.0.0": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.0.2.tgz#7377c0cacf0e3c988ce321295bf5d2c174e0e288" - integrity sha512-zhPJObdrhz2ymIqGL1x8i3meEuaLz0UPjH9mOq9RGOlJB2Pb6K6xPtkHbRsfElgoO9USR4hH2XU5pLa4/JHHIw== - dependencies: - "@azure/abort-controller" "^1.0.0" - "@azure/core-tracing" "1.0.0-preview.7" - "@opentelemetry/types" "^0.2.0" - tslib "^1.9.3" - -"@azure/core-http@^1.0.0": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@azure/core-http/-/core-http-1.0.2.tgz#e281861492cf96459edd035c98ae234401cf8bfd" - integrity sha512-wjtY0Ks0+/4SEcIiMAvXdYvZZpyYj0rgs4dbd1MfgBlXgvvzpDOsPpcvI6ty5pyPBrjAyqrpyQeFud6hqsAnDQ== - dependencies: - "@azure/abort-controller" "^1.0.0" - "@azure/core-auth" "^1.0.0" - "@azure/core-tracing" "1.0.0-preview.7" - "@azure/logger" "^1.0.0" - "@opentelemetry/types" "^0.2.0" - "@types/node-fetch" "^2.5.0" - "@types/tunnel" "^0.0.1" - form-data "^3.0.0" - node-fetch "^2.6.0" - process "^0.11.10" - tough-cookie "^3.0.1" - tslib "^1.9.3" - tunnel "^0.0.6" - uuid "^3.3.2" - xml2js "^0.4.19" - -"@azure/core-lro@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-1.0.0.tgz#9837398e03aa04b5b0b09158f4338861348dcce4" - integrity sha512-l4abIb8S9qmlv3bJkonLvgGSVQcSXq5jByA7Z28GRGJaQN/mSFal9YQOuLvVag+JXQJsoftuxJFrZiggF2TwOg== - dependencies: - "@azure/abort-controller" "^1.0.0" - "@azure/core-http" "^1.0.0" - events "^3.0.0" - tslib "^1.9.3" - -"@azure/core-paging@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.0.0.tgz#3aa3855582154260326feea97f9f8322cbfe56d9" - integrity sha512-CzaT7LwxU97PZ+/Pn7uAbNGXY2mJ/3b56kmLsZzbR9stfrNfzlILxR94WHG/D1jZEQOk4lUNiaqJ2zP7nSGJhA== - dependencies: - "@azure/core-asynciterator-polyfill" "^1.0.0" - -"@azure/core-tracing@1.0.0-preview.7": - version "1.0.0-preview.7" - resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.0-preview.7.tgz#e9ee9c88f0dcf50d8e5b468fc827203165ecbc3f" - integrity sha512-pkFCw6OiJrpR+aH1VQe6DYm3fK2KWCC5Jf3m/Pv1RxF08M1Xm08RCyQ5Qe0YyW5L16yYT2nnV48krVhYZ6SGFA== - dependencies: - "@opencensus/web-types" "0.0.7" - "@opentelemetry/types" "^0.2.0" - tslib "^1.9.3" - -"@azure/logger@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.0.tgz#48b371dfb34288c8797e5c104f6c4fb45bf1772c" - integrity sha512-g2qLDgvmhyIxR3JVS8N67CyIOeFRKQlX/llxYJQr1OSGQqM3HTpVP8MjmjcEKbL/OIt2N9C9UFaNQuKOw1laOA== - dependencies: - tslib "^1.9.3" - -"@azure/storage-blob@^12.0.1": - version "12.0.1" - resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.0.1.tgz#9478f62e475347b0bf5edd773826fd9f07e4784a" - integrity sha512-NxMpOnN2jmWXgCZJqlGLuN5axXpi379PmVuCXWX6WLDwoY/jSJ1WOwWbS9h0Lgs/Lnd7rPrIijJEwUNQeGEtFw== - dependencies: - "@azure/abort-controller" "^1.0.0" - "@azure/core-http" "^1.0.0" - "@azure/core-lro" "^1.0.0" - "@azure/core-paging" "^1.0.0" - "@azure/core-tracing" "1.0.0-preview.7" - "@azure/logger" "^1.0.0" - "@opentelemetry/types" "^0.2.0" - events "^3.0.0" - tslib "^1.9.3" - -"@babel/code-frame@^7.0.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" - integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== - dependencies: - "@babel/highlight" "^7.8.3" - -"@babel/highlight@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" - integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== - dependencies: - chalk "^2.0.0" - esutils "^2.0.2" - js-tokens "^4.0.0" - -"@nodelib/fs.scandir@2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.2.tgz#1f981cd5b83e85cfdeb386fc693d4baab392fa54" - integrity sha512-wrIBsjA5pl13f0RN4Zx4FNWmU71lv03meGKnqRUoCyan17s4V3WL92f3w3AIuWbNnpcrQyFBU5qMavJoB8d27w== - dependencies: - "@nodelib/fs.stat" "2.0.2" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.2", "@nodelib/fs.stat@^2.0.1": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.2.tgz#2762aea8fe78ea256860182dcb52d61ee4b8fda6" - integrity sha512-z8+wGWV2dgUhLqrtRYa03yDx4HWMvXKi1z8g3m2JyxAx8F7xk74asqPk5LAETjqDSGLFML/6CDl0+yFunSYicw== - -"@nodelib/fs.walk@^1.2.1": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.3.tgz#a555dc256acaf00c62b0db29529028dd4d4cb141" - integrity sha512-l6t8xEhfK9Sa4YO5mIRdau7XSOADfmh3jCr0evNHdY+HNkW6xuQhgMH7D73VV6WpZOagrW0UludvMTiifiwTfA== - dependencies: - "@nodelib/fs.scandir" "2.1.2" - fastq "^1.6.0" - -"@opencensus/web-types@0.0.7": - version "0.0.7" - resolved "https://registry.yarnpkg.com/@opencensus/web-types/-/web-types-0.0.7.tgz#4426de1fe5aa8f624db395d2152b902874f0570a" - integrity sha512-xB+w7ZDAu3YBzqH44rCmG9/RlrOmFuDPt/bpf17eJr8eZSrLt7nc7LnWdxM9Mmoj/YKMHpxRg28txu3TcpiL+g== - -"@opentelemetry/types@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/types/-/types-0.2.0.tgz#2a0afd40fa7026e39ea56a454642bda72b172f80" - integrity sha512-GtwNB6BNDdsIPAYEdpp3JnOGO/3AJxjPvny53s3HERBdXSJTGQw8IRhiaTEX0b3w9P8+FwFZde4k+qkjn67aVw== - -"@types/caseless@*": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.1.tgz#9794c69c8385d0192acc471a540d1f8e0d16218a" - integrity sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A== - -"@types/cldrjs@*": - version "0.4.20" - resolved "https://registry.yarnpkg.com/@types/cldrjs/-/cldrjs-0.4.20.tgz#b661657dac028a9e5131358db793e7f58db231dd" - integrity sha512-vQe6BQF9QCHSLUlNjRa/1zicRCnQnTRwhW/FqgVv26A85COY1jfkkO6JjogDv22U3LRhu9pY4uPQOlxGnsuJPA== - -"@types/events@*": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" - integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA== - -"@types/form-data@*": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" - integrity sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ== - dependencies: - "@types/node" "*" - -"@types/fs-extra@^5.0.4": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.4.tgz#b971134d162cc0497d221adde3dbb67502225599" - integrity sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g== - dependencies: - "@types/node" "*" - -"@types/glob@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" - integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== - dependencies: - "@types/events" "*" - "@types/minimatch" "*" - "@types/node" "*" - -"@types/globalize@^0.0.34": - version "0.0.34" - resolved "https://registry.yarnpkg.com/@types/globalize/-/globalize-0.0.34.tgz#6b61c8a49b1cd83c7026c6987d50be2eb54d2b4f" - integrity sha512-FQTLuqZxqf+T1Ao6RzaIP7HcTcNvgDf0YQfK90YGYt1N6KeU5GE0M/hsxdQlpqvuztxjEwEQqIO3paSO/tZ4Pw== - dependencies: - "@types/cldrjs" "*" - -"@types/globby@^9.1.0": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@types/globby/-/globby-9.1.0.tgz#08e2cf99c64f8e45c6cfbe05e9d8ac763aca6482" - integrity sha512-9du/HCA71EBz7syHRnM4Q/u4Fbx3SyN/Uu+4Of9lyPX4A6Xi+A8VMxvx8j5/CMTfrae2Zwdwg0fAaKvKXfRbAw== - dependencies: - globby "*" - -"@types/inversify@^2.0.33": - version "2.0.33" - resolved "https://registry.yarnpkg.com/@types/inversify/-/inversify-2.0.33.tgz#0770dae2299aaee57803f75235e64a99b74f6de0" - integrity sha1-B3Da4imaruV4A/dSNeZKmbdPbeA= - dependencies: - inversify "*" - -"@types/js-yaml@^3.12.1": - version "3.12.1" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.1.tgz#5c6f4a1eabca84792fbd916f0cb40847f123c656" - integrity sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA== - -"@types/lodash@^4.14.117": - version "4.14.119" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.119.tgz#be847e5f4bc3e35e46d041c394ead8b603ad8b39" - integrity sha512-Z3TNyBL8Vd/M9D9Ms2S3LmFq2sSMzahodD6rCS9V2N44HUMINb75jNkSuwAx7eo2ufqTdfOdtGQpNbieUjPQmw== - -"@types/minimatch@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== - -"@types/mocha@^5.2.5": - version "5.2.5" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" - integrity sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww== - -"@types/nock@^9.3.0": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@types/nock/-/nock-9.3.0.tgz#9d34358fdcc08afd07144e0784ac9e951d412dd4" - integrity sha512-ZHf/X8rTQ5Tb1rHjxIJYqm55uO265agE3G7NoSXVa2ep+EcJXgB2fsme+zBvK7MhrxTwkC/xkB6THyv50u0MGw== - dependencies: - "@types/node" "*" - -"@types/node-fetch@^2.5.0": - version "2.5.4" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.4.tgz#5245b6d8841fc3a6208b82291119bc11c4e0ce44" - integrity sha512-Oz6id++2qAOFuOlE1j0ouk1dzl3mmI1+qINPNBhi9nt/gVOz0G+13Ao6qjhdF0Ys+eOkhu6JnFmt38bR3H0POQ== - dependencies: - "@types/node" "*" - -"@types/node-rsa@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/node-rsa/-/node-rsa-1.0.0.tgz#4432df6227c5de734f5f0fbea2420ffb51c51e44" - integrity sha512-9hTSXhGDKotpq5XCm+r7wMgt5gl6bGH6VS/sV9bsKda4JmJYyOCdTenSkTUh7zsOihm2oCm5o2ZdfpUIGYKLzQ== - dependencies: - "@types/node" "*" - -"@types/node@*": - version "10.12.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47" - integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A== - -"@types/node@^12.12.5": - version "12.12.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.5.tgz#66103d2eddc543d44a04394abb7be52506d7f290" - integrity sha512-KEjODidV4XYUlJBF3XdjSH5FWoMCtO0utnhtdLf1AgeuZLOrRbvmU/gaRCVg7ZaQDjVf3l84egiY0mRNe5xE4A== - -"@types/opn@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/opn/-/opn-5.1.0.tgz#bff7bc371677f4bdbb37884400e03fd81f743927" - integrity sha512-TNPrB7Y1xl06zDI0aGyqkgxjhIev3oJ+cdqlZ52MTAHauWpEL/gIUdHebIfRHFZk9IqSBpE2ci1DT48iZH81yg== - dependencies: - "@types/node" "*" - -"@types/request-promise-native@^1.0.15": - version "1.0.15" - resolved "https://registry.yarnpkg.com/@types/request-promise-native/-/request-promise-native-1.0.15.tgz#5b3369fc6aaf9e7fef7b6b688aef4c5759623e16" - integrity sha512-uYPjTChD9TpjlvbBjNpZfNc64TBejBS52u7pbxhQLnlxw+5Em7wLb6DU2wdJVhJ2Mou7v50N0qgL4Gia5mmRYg== - dependencies: - "@types/request" "*" - -"@types/request@*", "@types/request@^2.47.1": - version "2.48.1" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.1.tgz#e402d691aa6670fbbff1957b15f1270230ab42fa" - integrity sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg== - dependencies: - "@types/caseless" "*" - "@types/form-data" "*" - "@types/node" "*" - "@types/tough-cookie" "*" - -"@types/semver-compare@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/semver-compare/-/semver-compare-1.0.0.tgz#b6e39ec7d0d3f03e5e74ec654bfbbfbc3bbe1b19" - integrity sha512-zEVoxKekBLngwvsGP85ct5U3NWgoOWf614Cgjtm1KjFfyyWyb9/RakDK8+Sf3UDBkoWqDjvvBDhqQ5565UjDKQ== - -"@types/sshpk@^1.10.4": - version "1.10.4" - resolved "https://registry.yarnpkg.com/@types/sshpk/-/sshpk-1.10.4.tgz#61871614349a3ecd9fafdec5ed992736408df8a1" - integrity sha512-ly9tBXvJ3XQZH0ehBmbi5TA8SRxVYyIqj9EHqLjh9Ec/3V0FGOjiIsxO1n9/OPFKB6QEg98JqMJ19KOOkSnfbA== - dependencies: - "@types/node" "*" - -"@types/tough-cookie@*": - version "2.3.4" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.4.tgz#821878b81bfab971b93a265a561d54ea61f9059f" - integrity sha512-Set5ZdrAaKI/qHdFlVMgm/GsAv/wkXhSTuZFkJ+JI7HK+wIkIlOaUXSXieIvJ0+OvGIqtREFoE+NHJtEq0gtEw== - -"@types/tunnel@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c" - integrity sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A== - dependencies: - "@types/node" "*" - -"@types/uuid@^3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.4.tgz#7af69360fa65ef0decb41fd150bf4ca5c0cefdf5" - integrity sha512-tPIgT0GUmdJQNSHxp0X2jnpQfBSTfGxUMc/2CXBU2mnyTFVYVa2ojpoQ74w0U2yn2vw3jnC640+77lkFFpdVDw== - dependencies: - "@types/node" "*" - -"@types/vscode@^1.40.0": - version "1.42.0" - resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.42.0.tgz#0ad891a9487e91e34be7c56985058a179031eb76" - integrity sha512-ds6TceMsh77Fs0Mq0Vap6Y72JbGWB8Bay4DrnJlf5d9ui2RSe1wis13oQm+XhguOeH1HUfLGzaDAoupTUtgabw== - -agent-base@4, agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - -ajv@^6.5.4, ajv@^6.5.5: - version "6.6.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61" - integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-colors@3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" - integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -asn1@^0.2.4, asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== - -axios@^0.19.0: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== - dependencies: - follow-redirects "1.5.10" - -azure-devops-node-api@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/azure-devops-node-api/-/azure-devops-node-api-7.2.0.tgz#131d4e01cf12ebc6e45569b5e0c5c249e4114d6d" - integrity sha512-pMfGJ6gAQ7LRKTHgiRF+8iaUUeGAI0c8puLaqHLc7B8AR7W6GJLozK9RFeUHFjEGybC9/EB3r67WPd7e46zQ8w== - dependencies: - os "0.1.1" - tunnel "0.0.4" - typed-rest-client "1.2.0" - underscore "1.8.3" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - -big-integer@^1.6.17: - version "1.6.44" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.44.tgz#4ee9ae5f5839fc11ade338fea216b4513454a539" - integrity sha512-7MzElZPTyJ2fNvBkPxtFQ2fWIkVmuzw41+BZHSzpEq3ymB2MfeKp1+yXl/tS75xCx+WnyV+yb0kp+K1C3UNwmQ== - -binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== - -binary@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" - integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= - dependencies: - buffers "~0.1.1" - chainsaw "~0.1.0" - -bluebird@~3.4.1: - version "3.4.7" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" - integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= - -boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.1, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -buffer-indexof-polyfill@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz#a9fb806ce8145d5428510ce72f278bb363a638bf" - integrity sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8= - -buffer-stream-reader@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/buffer-stream-reader/-/buffer-stream-reader-0.1.1.tgz#ca8bf93631deedd8b8f8c3bb44991cc30951e259" - integrity sha1-yov5NjHe7di4+MO7RJkcwwlR4lk= - -buffers@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" - integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= - -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= - -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -chainsaw@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" - integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= - dependencies: - traverse ">=0.3.0 <0.4" - -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^2.3.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" - integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -cheerio@^1.0.0-rc.1: - version "1.0.0-rc.2" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" - integrity sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs= - dependencies: - css-select "~1.2.0" - dom-serializer "~0.1.0" - entities "~1.1.1" - htmlparser2 "^3.9.1" - lodash "^4.15.0" - parse5 "^3.0.1" - -chokidar@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" - integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.2.0" - optionalDependencies: - fsevents "~2.1.1" - -cldrjs@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cldrjs/-/cldrjs-0.5.0.tgz#37be92d8d1a8e66c8ee12f1303ed316d85d8eb37" - integrity sha1-N76S2NGo5myO4S8TA+0xbYXY6zc= - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" - integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== - dependencies: - delayed-stream "~1.0.0" - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^2.12.1, commander@^2.8.1: - version "2.19.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -css-select@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" - -css-what@2.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d" - integrity sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ== - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -debug@3.1.0, debug@=3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -debug@3.2.6, debug@^3.1.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -debug@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" - integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== - dependencies: - ms "^2.1.1" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -define-properties@^1.1.2, define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -denodeify@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" - integrity sha1-OjYof1A05pnnV3kBBSwubJQlFjE= - -didyoumean@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.1.tgz#e92edfdada6537d484d73c0172fd1eba0c4976ff" - integrity sha1-6S7f2tplN9SE1zwBcv0eugxJdv8= - -diff@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -dom-serializer@0, dom-serializer@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" - integrity sha1-BzxpdUbOB4DOI75KKOKT5AvDDII= - dependencies: - domelementtype "~1.1.1" - entities "~1.1.1" - -domelementtype@1, domelementtype@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@~1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" - integrity sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs= - -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== - dependencies: - domelementtype "1" - -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^1.5.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - -duplexer2@~0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= - dependencies: - readable-stream "^2.0.2" - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -entities@^1.1.1, entities@~1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== - -es-abstract@^1.17.0-next.1: - version "1.17.4" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" - integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esutils@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" - integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= - -events@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" - integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA== - -extend@^3.0.0, extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= - -fast-glob@^3.0.3: - version "3.0.4" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.0.4.tgz#d484a41005cb6faeb399b951fd1bd70ddaebb602" - integrity sha512-wkIbV6qg37xTJwqSsdnIphL1e+LaGz4AIQqr00mIubMaEhv1/HEmJ0uuCGZRNRUkZZmOB5mJKO0ZUTVq+SxMQg== - dependencies: - "@nodelib/fs.stat" "^2.0.1" - "@nodelib/fs.walk" "^1.2.1" - glob-parent "^5.0.0" - is-glob "^4.0.1" - merge2 "^1.2.3" - micromatch "^4.0.2" - -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= - -fastq@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" - integrity sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA== - dependencies: - reusify "^1.0.0" - -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= - dependencies: - pend "~1.2.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@3.0.0, find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -flat@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" - integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== - dependencies: - is-buffer "~2.0.3" - -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" - integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -fs-extra@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" - integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@~2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" - integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== - -fstream@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" - integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -get-caller-file@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== - -get-caller-file@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -glob-parent@^5.0.0, glob-parent@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" - integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== - dependencies: - is-glob "^4.0.1" - -glob@7.1.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.3: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globalize@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/globalize/-/globalize-1.4.0.tgz#4c00a79de67d739a9b7ff83b66b90d0257c27493" - integrity sha1-TACnneZ9c5qbf/g7ZrkNAlfCdJM= - dependencies: - cldrjs "^0.5.0" - -globby@*, globby@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.1.tgz#4782c34cb75dd683351335c5829cc3420e606b22" - integrity sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A== - dependencies: - "@types/glob" "^7.1.1" - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.0.3" - glob "^7.1.3" - ignore "^5.1.1" - merge2 "^1.2.3" - slash "^3.0.0" - -graceful-fs@^4.1.2, graceful-fs@^4.1.6: - version "4.1.15" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" - integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== - -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -htmlparser2@^3.9.1: - version "3.10.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" - integrity sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ== - dependencies: - domelementtype "^1.3.0" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.0.6" - -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -https-proxy-agent@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - -ignore@^5.1.1: - version "5.1.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" - integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -inversify@*, inversify@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/inversify/-/inversify-5.0.1.tgz#500d709b1434896ce5a0d58915c4a4210e34fb6e" - integrity sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ== - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-buffer@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" - integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== - -is-callable@^1.1.4, is-callable@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" - integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== - -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-regex@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" - integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== - dependencies: - has "^1.0.3" - -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@3.13.1, js-yaml@^3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -json-inline-doc@^2.0.1: - version "2.0.1" - resolved "http://registry.npm.taobao.org/json-inline-doc/download/json-inline-doc-2.0.1.tgz#91ab6fc5de16a77e4a9a0d0c091246f1fc3fcec1" - integrity sha1-katvxd4Wp35Kmg0MCRJG8fw/zsE= - dependencies: - tslib "^1.9.3" - word-wrap "^1.2.3" - -json-schema-ref-parser@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/json-schema-ref-parser/-/json-schema-ref-parser-7.1.3.tgz#21468cd180b2f1939ce93fe291f743b441e97d49" - integrity sha512-/Lmyl0PW27dOmCO03PI339+1gs4Z2PlqIyUgzIOtoRp08zkkMCB30TRbdppbPO7WWzZX0uT98HqkDiZSujkmbA== - dependencies: - call-me-maybe "^1.0.1" - js-yaml "^3.13.1" - ono "^6.0.0" - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -jsonc-parser@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" - integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -linkify-it@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db" - integrity sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg== - dependencies: - uc.micro "^1.0.1" - -listenercount@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" - integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -lodash@^4.15.0, lodash@^4.17.10: - version "4.17.11" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" - integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== - -lodash@^4.17.13, lodash@^4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== - -log-symbols@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" - -markdown-it@^8.3.1: - version "8.4.2" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54" - integrity sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ== - dependencies: - argparse "^1.0.7" - entities "~1.1.1" - linkify-it "^2.0.0" - mdurl "^1.0.1" - uc.micro "^1.0.5" - -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= - -merge2@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5" - integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA== - -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - -mime-db@~1.37.0: - version "1.37.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" - integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.21" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" - integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== - dependencies: - mime-db "~1.37.0" - -mime@^1.3.4: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -mocha@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.0.1.tgz#276186d35a4852f6249808c6dd4a1376cbf6c6ce" - integrity sha512-9eWmWTdHLXh72rGrdZjNbG3aa1/3NRPpul1z0D979QpEnFdCG0Q5tv834N+94QEN2cysfV72YocQ3fn87s70fg== - dependencies: - ansi-colors "3.2.3" - browser-stdout "1.3.1" - chokidar "3.3.0" - debug "3.2.6" - diff "3.5.0" - escape-string-regexp "1.0.5" - find-up "3.0.0" - glob "7.1.3" - growl "1.10.5" - he "1.2.0" - js-yaml "3.13.1" - log-symbols "2.2.0" - minimatch "3.0.4" - mkdirp "0.5.1" - ms "2.1.1" - node-environment-flags "1.0.6" - object.assign "4.1.0" - strip-json-comments "2.0.1" - supports-color "6.0.0" - which "1.3.1" - wide-align "1.1.3" - yargs "13.3.0" - yargs-parser "13.1.1" - yargs-unparser "1.6.0" - -mock-require@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/mock-require/-/mock-require-3.0.3.tgz#ccd544d9eae81dd576b3f219f69ec867318a1946" - integrity sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg== - dependencies: - get-caller-file "^1.0.2" - normalize-path "^2.1.1" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.1, ms@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -mute-stream@~0.0.4: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= - -nock@^11.8.2: - version "11.8.2" - resolved "https://registry.yarnpkg.com/nock/-/nock-11.8.2.tgz#7845608d57769f41375b63521a7e6b554f4cdbb2" - integrity sha512-udrFXJ/aqPM9NmrKOcNJ67lvrs/zroNq2sbumhaMPW5JLNy/6LsWiZEwU9DiQIUHOcOCR4MPeqIG7uQNbDGExA== - dependencies: - debug "^4.1.0" - json-stringify-safe "^5.0.1" - lodash "^4.17.13" - mkdirp "^0.5.0" - propagate "^2.0.0" - -node-environment-flags@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" - integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== - dependencies: - object.getownpropertydescriptors "^2.0.3" - semver "^5.7.0" - -node-fetch@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" - integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== - -node-rsa@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.0.7.tgz#85b7a6d6fa8ee624be6402a6b41be49272d58055" - integrity sha512-idwRXma6scFufZmbaKkHpJoLL93yynRefP6yur13wZ5i9FR35ex451KCoF2OORDeJanyRVahmjjiwmUlCnTqJA== - dependencies: - asn1 "^0.2.4" - -node-yaml-parser@^0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/node-yaml-parser/-/node-yaml-parser-0.0.9.tgz#1fd5fd1f7f2a471447769142949f2e2827286ab8" - integrity sha512-bFFmAdUs9sdD+TwF/A91b4ozU2KJSDuK7HiBV0QDhdG7Cunu/4PakBLn3wWWAJurAJd7GqAeEwYhu32bTHOvQg== - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -nth-check@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@4.1.0, object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.getownpropertydescriptors@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -ono@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ono/-/ono-6.0.1.tgz#1bc14ffb8af1e5db3f7397f75b88e4a2d64bbd71" - integrity sha512-5rdYW/106kHqLeG22GE2MHKq+FlsxMERZev9DCzQX1zwkxnFwBivSn5i17a5O/rDmOJOdf4Wyt80UZljzx9+DA== - -openpai-js-sdk@microsoft/pai#openpai-js-sdk: - version "0.0.0" - resolved "https://codeload.github.com/microsoft/pai/tar.gz/6e6b256db384d6743e8236617e79d7565b12744e" - dependencies: - axios "^0.19.0" - js-yaml "^3.13.1" - request "^2.88.0" - request-promise-native "^1.0.7" - -opn@^5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.4.0.tgz#cb545e7aab78562beb11aa3bfabc7042e1761035" - integrity sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw== - dependencies: - is-wsl "^1.1.0" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0, os-tmpdir@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -os@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/os/-/os-0.1.1.tgz#208845e89e193ad4d971474b93947736a56d13f3" - integrity sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M= - -osenv@^0.1.3: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-limit@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" - integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== - dependencies: - p-try "^2.0.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parse-semver@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/parse-semver/-/parse-semver-1.1.1.tgz#9a4afd6df063dc4826f93fba4a99cf223f666cb8" - integrity sha1-mkr9bfBj3Egm+T+6SpnPIj9mbLg= - dependencies: - semver "^5.1.0" - -parse5@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" - integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== - dependencies: - "@types/node" "*" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-parse@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -picomatch@^2.0.4: - version "2.2.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" - integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== - -picomatch@^2.0.5: - version "2.0.7" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" - integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== - -process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - -propagate@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" - integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== - -psl@^1.1.24, psl@^1.1.28: - version "1.1.29" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" - integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - -read@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" - integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= - dependencies: - mute-stream "~0.0.4" - -readable-stream@^2.0.2, readable-stream@~2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.0.6.tgz#351302e4c68b5abd6a2ed55376a7f9a25be3057a" - integrity sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" - integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== - dependencies: - picomatch "^2.0.4" - -reflect-metadata@^0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2" - integrity sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A== - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -request-promise-core@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" - integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== - dependencies: - lodash "^4.17.15" - -request-promise-native@^1.0.7, request-promise-native@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" - integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== - dependencies: - request-promise-core "1.1.3" - stealthy-require "^1.1.1" - tough-cookie "^2.3.3" - -request@^2.74.0, request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -resolve@^1.3.2: - version "1.8.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" - integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== - dependencies: - path-parse "^1.0.5" - -reusify@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== - dependencies: - glob "^7.0.5" - -rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -run-parallel@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" - integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== - -safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@>=0.6.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - -semver@^5.1.0, semver@^5.3.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== - -semver@^5.7.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -setimmediate@~1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -sshpk@^1.16.1: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -sshpk@^1.7.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629" - integrity sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -stealthy-require@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" - integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= - -streamifier@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/streamifier/-/streamifier-0.1.1.tgz#97e98d8fa4d105d62a2691d1dc07e820db8dfc4f" - integrity sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8= - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string.prototype.trimleft@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" - integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== - dependencies: - define-properties "^1.1.3" - function-bind "^1.1.1" - -string.prototype.trimright@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" - integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== - dependencies: - define-properties "^1.1.3" - function-bind "^1.1.1" - -string_decoder@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" - integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== - dependencies: - safe-buffer "~5.1.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-json-comments@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -supports-color@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" - integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== - dependencies: - has-flag "^3.0.0" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -tmp@0.0.29: - version "0.0.29" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" - integrity sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA= - dependencies: - os-tmpdir "~1.0.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -tough-cookie@^2.3.3: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -tough-cookie@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" - integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== - dependencies: - ip-regex "^2.1.0" - psl "^1.1.28" - punycode "^2.1.1" - -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - -"traverse@>=0.3.0 <0.4": - version "0.3.9" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" - integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= - -tslib@^1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== - -tslib@^1.8.1, tslib@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" - integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== - -tslint-microsoft-contrib@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.2.1.tgz#a6286839f800e2591d041ea2800c77487844ad81" - integrity sha512-PDYjvpo0gN9IfMULwKk0KpVOPMhU6cNoT9VwCOLeDl/QS8v8W2yspRpFFuUS7/c5EIH/n8ApMi8TxJAz1tfFUA== - dependencies: - tsutils "^2.27.2 <2.29.0" - -tslint@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.0.0.tgz#1c0148beac4779924216302f192cdaa153618310" - integrity sha512-9nLya8GBtlFmmFMW7oXXwoXS1NkrccqTqAtwXzdPV9e2mqSEvCki6iHL/Fbzi5oqbugshzgGPk7KBb2qNP1DSA== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.1" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.10.0" - tsutils "^2.29.0" - -"tsutils@^2.27.2 <2.29.0": - version "2.28.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.28.0.tgz#6bd71e160828f9d019b6f4e844742228f85169a1" - integrity sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA== - dependencies: - tslib "^1.8.1" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tunnel@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.4.tgz#2d3785a158c174c9a16dc2c046ec5fc5f1742213" - integrity sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM= - -tunnel@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" - integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -typed-rest-client@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/typed-rest-client/-/typed-rest-client-1.2.0.tgz#723085d203f38d7d147271e5ed3a75488eb44a02" - integrity sha512-FrUshzZ1yxH8YwGR29PWWnfksLEILbWJydU7zfIRkyH7kAEzB62uMAl2WY6EyolWpLpVHeJGgQm45/MaruaHpw== - dependencies: - tunnel "0.0.4" - underscore "1.8.3" - -typescript-tslint-plugin@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/typescript-tslint-plugin/-/typescript-tslint-plugin-0.4.0.tgz#af1729e72b770489b2b912809694f46c3281bb72" - integrity sha512-83zipyk5bCqu/LHifaWPmJ7Xnl6lLn/3KmWINgr+XND79BrVWmkV8CudKc+a9Jhjs2opvVKq0n0P4O4NhHOixw== - dependencies: - minimatch "^3.0.4" - mock-require "^3.0.2" - vscode-languageserver "^5.1.0" - -typescript@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" - integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== - -uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376" - integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg== - -underscore@1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" - integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -unixify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unixify/-/unixify-1.0.0.tgz#3a641c8c2ffbce4da683a5c70f03a462940c2090" - integrity sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA= - dependencies: - normalize-path "^2.1.1" - -unzipper@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.1.tgz#8c1f089fcb7f4b2d0582e5b159ea1d7ace342215" - integrity sha512-ft0sDT8S/LVNKSOP2dYS3zl4aSw9lAtLY9w9XdNy3Cnt//+SvmTYo+OT7dT9b0C/eg/Ky+BARgWgaibq19ewcA== - dependencies: - big-integer "^1.6.17" - binary "~0.3.0" - bluebird "~3.4.1" - buffer-indexof-polyfill "~1.0.0" - duplexer2 "~0.1.4" - fstream "^1.0.12" - listenercount "~1.0.1" - readable-stream "~2.3.6" - setimmediate "~1.0.4" - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - -url-join@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78" - integrity sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg= - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vsce@^1.73.0: - version "1.73.0" - resolved "https://registry.yarnpkg.com/vsce/-/vsce-1.73.0.tgz#f65b501f0d1e45e50b220b284a47df164c4e847b" - integrity sha512-6W37Ebbkj3uF3WhT+SCfRtsneRQEFcGvf/XYz+b6OAgDCj4gPurWyDVrqw/HLsbP1WflGIyUfVZ8t5M7kQp6Uw== - dependencies: - azure-devops-node-api "^7.2.0" - chalk "^2.4.2" - cheerio "^1.0.0-rc.1" - commander "^2.8.1" - denodeify "^1.2.1" - didyoumean "^1.2.1" - glob "^7.0.6" - lodash "^4.17.10" - markdown-it "^8.3.1" - mime "^1.3.4" - minimatch "^3.0.3" - osenv "^0.1.3" - parse-semver "^1.1.1" - read "^1.0.7" - semver "^5.1.0" - tmp "0.0.29" - typed-rest-client "1.2.0" - url-join "^1.1.0" - yauzl "^2.3.1" - yazl "^2.2.2" - -vscode-jsonrpc@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz#a7bf74ef3254d0a0c272fab15c82128e378b3be9" - integrity sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg== - -vscode-languageserver-protocol@3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz#b8aab6afae2849c84a8983d39a1cf742417afe2f" - integrity sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g== - dependencies: - vscode-jsonrpc "^4.0.0" - vscode-languageserver-types "3.14.0" - -vscode-languageserver-types@3.14.0: - version "3.14.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743" - integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A== - -vscode-languageserver@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-5.2.1.tgz#0d2feddd33f92aadf5da32450df498d52f6f14eb" - integrity sha512-GuayqdKZqAwwaCUjDvMTAVRPJOp/SLON3mJ07eGsx/Iq9HjRymhKWztX41rISqDKhHVVyFM+IywICyZDla6U3A== - dependencies: - vscode-languageserver-protocol "3.14.1" - vscode-uri "^1.0.6" - -vscode-test@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-1.3.0.tgz#3310ab385d9b887b4c82e8f52be1030e7cf9493d" - integrity sha512-LddukcBiSU2FVTDr3c1D8lwkiOvwlJdDL2hqVbn6gIz+rpTqUCkMZSKYm94Y1v0WXlHSDQBsXyY+tchWQgGVsw== - dependencies: - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.4" - rimraf "^2.6.3" - -vscode-uri@^1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.8.tgz#9769aaececae4026fb6e22359cb38946580ded59" - integrity sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ== - -webhdfs@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/webhdfs/-/webhdfs-1.2.0.tgz#c41b08ae33944a0220863bfd4b6719b9aaec1d37" - integrity sha512-h8D/NT7ruDMuGCdJNEJHJh8vDTEtZ5hBL+eRzXTq/INTd92LKOhsTCwlQI+8kTt79qPZq5O8ev7j/Y19VeYCHQ== - dependencies: - buffer-stream-reader "^0.1.1" - extend "^3.0.0" - request "^2.74.0" - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -wide-align@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -word-wrap@^1.2.3: - version "1.2.3" - resolved "http://registry.npm.taobao.org/word-wrap/download/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha1-YQY29rH3A4kb00dxzLF/uTtHB5w= - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -xml2js@^0.4.19: - version "0.4.23" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" - integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - -y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== - -yargs-parser@13.1.1, yargs-parser@^13.1.1: - version "13.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" - integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-unparser@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" - integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== - dependencies: - flat "^4.1.0" - lodash "^4.17.15" - yargs "^13.3.0" - -yargs@13.3.0, yargs@^13.3.0: - version "13.3.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" - integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.1" - -yauzl@^2.3.1: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - -yazl@^2.2.2: - version "2.5.1" - resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" - integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== - dependencies: - buffer-crc32 "~0.2.3" diff --git a/docs/user/job_submission.md b/docs/user/job_submission.md index 47cee173ba..b4b89051ba 100644 --- a/docs/user/job_submission.md +++ b/docs/user/job_submission.md @@ -34,7 +34,7 @@ This document is a tutorial for job submission on OpenPAI (If you are using OpenPAI <= 0.13.0, please refer to [this document](./training.md)). Before learning this document, make sure you have an OpenPAI cluster already. If there isn't yet, refer to [here](../../README.md#deploy-openpai) to deploy one. -There are several ways of submitting pai job, including webportal, [OpenPAI VS Code Client](https://github.com/microsoft/pai/tree/master/contrib/pai_vscode), and [python sdk](https://github.com/microsoft/pai/tree/master/contrib/python-sdk). And all the job configs follow [OpenPAI Job Protocol](https://github.com/microsoft/openpai-protocol/blob/master/schemas/v2/schema.yaml). Here we use webportal to submit a hello world job. +There are several ways of submitting pai job, including webportal, [OpenPAI VS Code Client](https://github.com/microsoft/openpaivscode/tree/master), and [python sdk](https://github.com/microsoft/pai/tree/master/contrib/python-sdk). And all the job configs follow [OpenPAI Job Protocol](https://github.com/microsoft/openpai-protocol/blob/master/schemas/v2/schema.yaml). Here we use webportal to submit a hello world job. ## Submit a Hello World Job @@ -165,7 +165,7 @@ In OpenPAI, all jobs are represented by [YAML](https://yaml.org/), a markup lang ## Job Workflow -Once job configuration is ready, next step is to submit it to OpenPAI. Besides webportal, it's also recommended to use [Visual Studio Code Client](https://github.com/microsoft/pai/tree/master/contrib/pai_vscode) or [python sdk](https://github.com/microsoft/pai/tree/master/contrib/python-sdk) to submit jobs. +Once job configuration is ready, next step is to submit it to OpenPAI. Besides webportal, it's also recommended to use [Visual Studio Code Client](https://github.com/microsoft/openpaivscode/tree/master) or [python sdk](https://github.com/microsoft/pai/tree/master/contrib/python-sdk) to submit jobs. After receiving job configuration, OpenPAI processes it as below steps: diff --git a/docs/user/training.md b/docs/user/training.md index 1d20486159..b116f7e062 100644 --- a/docs/user/training.md +++ b/docs/user/training.md @@ -37,7 +37,7 @@ The **job** of OpenPAI defines how to execute command(s) in specified environmen Follow to submit a very simple job like hello-world during learning a program language. It trains a model, which is implemented by TensorFlow on CIFAR-10 dataset. It downloads data and code from internet and doesn't copy model out. It helps getting started with OpenPAI. Next sections include more details to help on submitting real jobs. -**Note**, web portal is one of ways to submit jobs. It's the simplest way to begin, but's not most efficient way to submit and manage jobs. [OpenPAI VS Code Client](../../contrib/pai_vscode/VSCodeExt.md) is recommended, as it provides best experience. +**Note**, web portal is one of ways to submit jobs. It's the simplest way to begin, but's not most efficient way to submit and manage jobs. [OpenPAI VS Code Client](https://github.com/Microsoft/openpaivscode/blob/master/VSCodeExt.md) is recommended, as it provides best experience. 1. Navigate to OpenPAI web portal. Input IP address or domain name of OpenPAI, which is from administrator of the OpenPAI cluster. Click *sign in* and input username, password, once login page shows. @@ -136,7 +136,7 @@ It's better to check with administrator of the OpenPAI cluster about how to tran ### Job workflow -Once job configuration is ready, next step is to submit it to OpenPAI. To submit a job, it's recommended to use [Visual Studio Code Client](../../contrib/pai_vscode/VSCodeExt.md). +Once job configuration is ready, next step is to submit it to OpenPAI. To submit a job, it's recommended to use [Visual Studio Code Client](https://github.com/Microsoft/openpaivscode/blob/master/VSCodeExt.md). Note, both web UI and the Visual Studio Code Client through [RESTful API](../rest-server/API.md) to access OpenPAI. The RESTful API can be used to customize the client experience. diff --git a/docs/user/troubleshooting_job.md b/docs/user/troubleshooting_job.md index 02b1d4062a..5291de8fe2 100644 --- a/docs/user/troubleshooting_job.md +++ b/docs/user/troubleshooting_job.md @@ -74,7 +74,7 @@ This simulation can cover most situations at remote, but still limited. For exa - The resource specification in configuration is ignored, as in most case, the local computer is not powerful like a GPU server. - When code is simulating locally, it may be much slower, or out of memory. The code or command should be modified to avoid this kind of issues and reduce training times to disclose more potential issues quickly. -Before using the simulator, [Docker](https://www.docker.com/get-started) needs to be installed. Refer to learn how to [install Visual Studio Code Client](../../contrib/pai_vscode/VSCodeExt.md) and [simulate job running](../../contrib/pai_vscode/README.md#local-simulation). +Before using the simulator, [Docker](https://www.docker.com/get-started) needs to be installed. Refer to learn how to [install Visual Studio Code Client](https://github.com/Microsoft/openpaivscode/blob/master/VSCodeExt.md) and [simulate job running](https://github.com/microsoft/openpaivscode/blob/master/README.md#local-simulation). Note, as Docker on Windows doesn't support GPU, so TensorFlow needs a docker image with TensorFlow CPU edition for local simulation. diff --git a/docs/zh_CN/user/job_submission.md b/docs/zh_CN/user/job_submission.md index 89435a46a7..b89019bcfc 100644 --- a/docs/zh_CN/user/job_submission.md +++ b/docs/zh_CN/user/job_submission.md @@ -34,7 +34,7 @@ This document is a tutorial for job submission on OpenPAI (If you are using OpenPAI <= 0.13.0, please refer to [this document](./training.md)). Before learning this document, make sure you have an OpenPAI cluster already. 如果还没安装 OpenPAI 集群,参考[这里](../../../README_zh_CN.md#部署)进行部署。 -There are several ways of submitting pai job, including webportal, [OpenPAI VS Code Client](https://github.com/microsoft/pai/tree/master/contrib/pai_vscode), and [python sdk](https://github.com/microsoft/pai/tree/master/contrib/python-sdk). And all the job configs follow [OpenPAI Job Protocol](https://github.com/microsoft/openpai-protocol/blob/master/schemas/v2/schema.yaml). Here we use webportal to submit a hello world job. +There are several ways of submitting pai job, including webportal, [OpenPAI VS Code Client](https://github.com/microsoft/openpaivscode/tree/master), and [python sdk](https://github.com/microsoft/pai/tree/master/contrib/python-sdk). And all the job configs follow [OpenPAI Job Protocol](https://github.com/microsoft/openpai-protocol/blob/master/schemas/v2/schema.yaml). Here we use webportal to submit a hello world job. ## 提交 Hello World Job @@ -163,7 +163,7 @@ In OpenPAI, all jobs are represented by [YAML](https://yaml.org/), a markup lang ## Job Workflow -Once job configuration is ready, next step is to submit it to OpenPAI. Besides webportal, it's also recommended to use [Visual Studio Code Client](https://github.com/microsoft/pai/tree/master/contrib/pai_vscode) or [python sdk](https://github.com/microsoft/pai/tree/master/contrib/python-sdk) to submit jobs. +Once job configuration is ready, next step is to submit it to OpenPAI. Besides webportal, it's also recommended to use [Visual Studio Code Client](https://github.com/microsoft/openpaivscode/tree/master) or [python sdk](https://github.com/microsoft/pai/tree/master/contrib/python-sdk) to submit jobs. After receiving job configuration, OpenPAI processes it as below steps: @@ -179,4 +179,4 @@ When a job is submitted to OpenPAI, the job's status changes from waiting, to ru ## 参考 -- [调研 Job 错误](troubleshooting_job.md) \ No newline at end of file +- [调研 Job 错误](troubleshooting_job.md) diff --git a/docs/zh_CN/user/training.md b/docs/zh_CN/user/training.md index a530c2a007..3f2e3dd0a6 100644 --- a/docs/zh_CN/user/training.md +++ b/docs/zh_CN/user/training.md @@ -19,9 +19,9 @@ # 在 OpenPAI 上训练模型 -- [在 OpenPAI 上训练模型](#在-openpai-上训练模型) +- [在 OpenPAI 上训练模型](#在-openpai-上训练模型) - [提交 hello-world Job](#提交-hello-world-job) - - [理解 Job](#理解-job) + - [理解 Job](#理解-job) - [了解 hello-world Job](#了解-hello-world-job) - [传入/传出文件](#传入传出文件) - [Job 流程](#job-流程) @@ -37,22 +37,22 @@ 本节介绍了如何提交一个非常简单的 Job,这就像在学习编程语言时,从 hello-world 示例开始一样。 此示例使用 TensorFlow 在 CIFAR-10 数据集上训练模型。 其从互联网下载数据和代码,且没有将训练完的模型复制出来。 通过此示例可初步了解 OpenPAI。 接下来的章节会介绍更多内容,以便于提交真正实用的 Job。 -**注意**, Web 界面是提交 Job 的方法之一。 它学起来非常简单,但却不是最高效的提交和管理 Job 的方法。 推荐使用 [OpenPAI VS Code Client](../../../contrib/pai_vscode/VSCodeExt_zh_CN.md),来获得最好的体验。 +**注意**, Web 界面是提交 Job 的方法之一。 它学起来非常简单,但却不是最高效的提交和管理 Job 的方法。 推荐使用 [OpenPAI VS Code Client](https://github.com/microsoft/openpaivscode/blob/master/VSCodeExt_zh_CN.md),来获得最好的体验。 1. 浏览至 OpenPAI 的 Web 界面。 可从 OpenPAI 管理员那里获取 IP 地址或域名。 在登录页面中,点击 *sign in*,输入用户名、密码。 - + 之后,OpenPAI 会显示如下的 Job 列表。 - + ![Job 列表](imgs/web_job_list.png) 2. 单击左边的的 **Submit Job** 并转到此页面。 - + ![提交 Job](imgs/web_submit_job.png) 3. 点击 **JSON** 按钮。 在弹出的文本框中,清除现有内容并粘贴下面的内容,然后单击“保存”。 - + 内容将在下一节中介绍。 - + json { "jobName": "tensorflow-cifar10", @@ -68,19 +68,19 @@ } ] } - + ![粘贴 Job](imgs/web_paste_json.png) 4. 然后点击 **Submit** 按钮将 Job 提交到 OpenPAI 平台。 - + ![点击提交 Job](imgs/web_click_submit_job.png) 5. 提交后,页面重定向到作业列表,提交的作业在列表中为 **Waiting** 状态。 单击左边的 **Jobs** 也可以到达此页面。 - + ![Job 列表](imgs/web_job_list.png) 6. 单击 Job 名称查看详细信息。 开始运行后,Job 状态会变为 *Running*,并且会在下面显示分配给 Task Role 的 IP 地址。 除此之外,还有更多的信息及操作,如状态、查看日志等。 - + ![Job 列表](imgs/web_job_details.png) ## 理解 Job @@ -98,21 +98,21 @@ JSON 文件中的字段有两个级别。 顶级节点是此 Job 的共享信息 - **jobName** 是当前 Job 的名称。 在每个用户账号中,其必需是唯一的。 有意义的名称有助于管理 Job。 - **image** - + [Docker](https://www.docker.com/why-docker) 是在服务器上提供虚拟环境的常用技术。 OpenPAI 用 Docker 来提供一致、干净的环境。 通过 Docker,OpenPAI 可以在一台服务器上同时服务多个资源请求。 - + **image** 字段是 Docker 映像的标识,其中已经安装好了定制的 Python 和系统的组件包。 - + hub.docker.com 是共享的 Docker 存储库,有很多 Docker 映像。 深度学习训练任务推荐使用 hub.docker.com 上的 [ufoym/deepo](https://hub.docker.com/r/ufoym/deepo)。 在 hello-world 示例中,使用了 ufoym/deepo 中的 Tensorflow 映像:*ufoym/deepo:tensorflow-py36-cu90*。 管理员可以设置专用的 Docker 存储库。 - + 如果没有找到合适的 Docker 映像,可参考[构建 Docker 映像](../job_docker_env.md),能很容易的定制一个 Docker 映像。 - + 注意,如果 Docker 映像没有包括 *openssh-server* 和 *curl* 包,则无法使用 OpenPAI 的 SSH 功能。 如果需要 SSH 功能,可在已有的 Docker 映像上构造一个包含 *openssh-server* 和 *curl* 的新 Docker 映像。 - **taskRoles** 定义了 Job 中的不同角色。 - + 对于单机运行的 Job,在 taskRoles 中只有一个角色。 - + 对于分布式的 Job,taskRoles 中可能会有多个角色。 例如,在使用 TensorFlow 来运行分布式 Job 时,需要两个角色,包括参数服务器和工作节点。 相应的在 Job 配置中需要两个任务角色,参考[示例](../job_tutorial.md#a-complete-example),来了解详细信息。 - **taskRoles/name** 是任务角色的名称,还会被用于分布式 Job 的环境变量中。 @@ -122,7 +122,7 @@ JSON 文件中的字段有两个级别。 顶级节点是此 Job 的共享信息 - **taskRoles/cpuNumber**,**taskRoles/memoryMB**,**taskRoles/gpuNumber** 非常容易理解。 它们指定了相应的硬件资源,包括 CPU 核数量,内存(MB),以及 GPU 数量。 - **taskRoles/command** 是此任务角色要运行的命令。 可以是多个命令,像在终端中一样,通过 `&&` 组合到一起。 例如,在 hello-world Job 中,命令会从 GitHub 中克隆代码,下载数据,然后执行训练过程。 - + 像 hello-world Job 一样,用户需要构造命令来获取代码、数据,并开始执行。 ### 传入/传出文件 @@ -135,7 +135,7 @@ OpenPAI 会管理计算资源,但不会管理持久化的存储资源。 [如 ### Job 流程 -Job 配置准备好后,下一步则需要将其提交到 OpenPAI。 推荐使用 [Visual Studio Code OpenPAI Client](../../../contrib/pai_vscode/VSCodeExt_zh_CN.md) 来提交 Job。 +Job 配置准备好后,下一步则需要将其提交到 OpenPAI。 推荐使用 [Visual Studio Code OpenPAI Client](https://github.com/microsoft/openpaivscode/blob/master/VSCodeExt_zh_CN.md) 来提交 Job。 注意,WEB 界面和 Visual Studio Code Client 都通过 [RESTful API](../rest-server/API.md) 来访问 OpenPAI。 可使用 RESTful API 来定制客户端体验。 @@ -156,4 +156,4 @@ Job 配置准备好后,下一步则需要将其提交到 OpenPAI。 推荐使 - [Job 配置的完整说明](../job_tutorial.md) - [示例](../../../examples) - [调研 Job 错误](troubleshooting_job.md) -- [如何使用存储](storage.md) \ No newline at end of file +- [如何使用存储](storage.md) diff --git a/docs/zh_CN/user/troubleshooting_job.md b/docs/zh_CN/user/troubleshooting_job.md index 95b2cd7e9d..abf424ae60 100644 --- a/docs/zh_CN/user/troubleshooting_job.md +++ b/docs/zh_CN/user/troubleshooting_job.md @@ -21,18 +21,18 @@ 与其它远程平台一样,OpenPAI 中 Job 失败的诊断和调试上需要更多精力。 本文有助于诊断 OpenPAI 上发生的问题。 -- [诊断调试 Job](#诊断调试-job) - - [最佳实践](#最佳实践) +- [诊断调试 Job](#诊断调试-job) + - [最佳实践](#最佳实践) - [在本机修复问题](#在本机修复问题) - [编写易于理解的日志](#编写易于理解的日志) - [通过本机模拟验证 Job](#通过本机模拟验证-job) - [充分了解资源瓶颈](#充分了解资源瓶颈) - - [诊断问题](#诊断问题) + - [诊断问题](#诊断问题) - [Job 等待了数小时](#job-等待了数小时) - [Job 重试了很多次](#job-重试了很多次) - [Job 执行较慢](#job-执行较慢) - [Job 失败](#job-失败) - - [指南](#指南) + - [指南](#指南) - [查看 Job 指标](#查看-job-指标) - [查看 Job 日志](#查看-job-日志) - [使用 SSH 远程连接](#使用-ssh-远程连接) @@ -74,7 +74,7 @@ OpenPAI Visual Studio Code Client 可以解析 OpenPAI Job 配置文件,并在 - 例如,配置文件中的资源请求数量会被忽略掉,因为本机通常不会像远端 GPU 服务器那样强大。 - 在本地模拟运行代码时,可能会非常慢,或者内存不够。 这时候,需要修改一下代码或命令行来避免这类问题,并减少训练时间来更快的发现更多问题。 -在使用模拟器之前,需要先安装 [Docker](https://www.docker.com/get-started)。 参考如何[安装 Visual Studio Code Client](../../../contrib/pai_vscode/VSCodeExt_zh_CN.md) 以及[运行模拟 Job](../../../contrib/pai_vscode/README_zh_CN.md#本机模拟)。 +在使用模拟器之前,需要先安装 [Docker](https://www.docker.com/get-started)。 参考如何[安装 Visual Studio Code Client](https://github.com/microsoft/openpaivscode/blob/master/VSCodeExt_zh_CN.md) 以及[运行模拟 Job](https://github.com/microsoft/openpaivscode/blob/master/README_zh_CN.md#本机模拟)。 注意,由于 Docker 在 Windows上不支持 GPU,因此在本机模拟时 TensorFlow 需要使用 CPU 版本的 Docker 映像。 @@ -115,21 +115,21 @@ Job 运行快慢是主观的,因此在试着“修复”这个问题前,需 Job 失败的原因很多。 一般根据它发生的阶段,将其归为两种类型。 1. **运行之前的失败**,例如,请求的资源超过了限制。 如果 Job 请求的资源超过了集群可提供的,Job 很快就会失败。 例如,如果服务器只有 24 个 CPU 内核,但 Job 配置中请求了 48 个内核,就会造成 Job 失败。 - + 这种系统级的失败,错误类型为 *System Error*。 - + ![over requested 1](imgs/web_job_details_over1.png) - + 点击 *application summary* 可看到如下的错误详情。 这里解释了哪项资源超出了限制。 - + ![over requested 1](imgs/web_job_details_over2.png) 2. **Job 运行时的失败**。 如果错误类型是 *User Error*,标准输出 stdout 和 stderr 可提供失败的更多细节。 通过[查看 Job 日志](#查看-job-日志)来了解更多细节。 - + 注意,OpenPAI 通过 Task 实例的退出代码来决定 Job 是否运行成功。 退出代码通常是 Job 配置中由用户所编写的 command 返回的。 但偶尔也会是 OpenPAI 的系统错误代码。 - + 错误代码的意义取决于具体的命令。 Linux 的系统命令,可参考[退出代码规范](http://www.tldp.org/LDP/abs/html/exitcodes.html)。 - + ![job user error](imgs/web_job_details_exitcode.png) ## 指南 @@ -155,15 +155,15 @@ Job 失败的原因很多。 一般根据它发生的阶段,将其归为两种 ### 查看 Job 日志 - 点击 Job 详情页面的 *stdout* 或 *stderr*。 - + ![job link](imgs/web_job_details_loglink.png) - 会显示如下内容,包含了最新的 4096 字节。 它每 10 秒会自动刷新。 - + 如果需要查看完整日志,点击 *View Full Log*。 - + ![job link](imgs/web_job_details_logview.png) - + *stderr* 和 *stdout* 都是 Task 实例的屏幕输出。 所有输出到屏幕的内容都会近实时的显示在这里。 大多数 Job 运行时的错误都能在这两个文件中找到。 注意,如果 Task 实例还被未分配资源,就不会有日志文件。 @@ -204,4 +204,4 @@ Job 失败的原因很多。 一般根据它发生的阶段,将其归为两种 如果本文无法解决问题,可寻找 OpenPAI 集群管理员的帮助。 -如果管理员无法修复此问题,或者你就是管理员,欢迎[提交问题或建议](../../../README_zh_CN.md#寻求帮助)。 \ No newline at end of file +如果管理员无法修复此问题,或者你就是管理员,欢迎[提交问题或建议](../../../README_zh_CN.md#寻求帮助)。 diff --git a/src/webportal/src/app/home/index/bottom.jsx b/src/webportal/src/app/home/index/bottom.jsx index 6dcefe02a0..d443dc8db3 100644 --- a/src/webportal/src/app/home/index/bottom.jsx +++ b/src/webportal/src/app/home/index/bottom.jsx @@ -127,7 +127,7 @@ const Bottom = () => (