├── tests ├── __init__.py ├── unit │ ├── __init__.py │ ├── flows │ │ ├── normalized_flows │ │ │ ├── flow5.yml │ │ │ ├── flow3.yml │ │ │ ├── flow4.yml │ │ │ ├── flow1.yml │ │ │ ├── flow8.yml │ │ │ ├── flow7.yml │ │ │ ├── flow9.yml │ │ │ ├── flow.yml │ │ │ ├── flow2.yml │ │ │ └── flow6.yml │ │ ├── failed_flows │ │ │ └── failed_flow.yml │ │ ├── not │ │ │ ├── flow4.yml │ │ │ ├── flow1.yml │ │ │ ├── flow3.yml │ │ │ └── flow2.yml │ │ ├── local_flow │ │ │ ├── executor1 │ │ │ │ ├── config.yml │ │ │ │ └── executor.py │ │ │ ├── executor2 │ │ │ │ ├── config.yml │ │ │ │ └── executor.py │ │ │ └── flow.yml │ │ ├── mixed_flow │ │ │ ├── executor1 │ │ │ │ ├── config.yml │ │ │ │ └── executor.py │ │ │ └── flow.yml │ │ ├── flow2.yml │ │ ├── flow1.yml │ │ ├── flow-with-obj-label.yml │ │ ├── flow-with-labels.yml │ │ └── flow1-test.yml │ ├── test_flow.py │ └── test_normalize.py ├── utils │ ├── __init__.py │ └── utils.py └── integration │ ├── __init__.py │ ├── flow │ ├── basic │ │ ├── __init__.py │ │ ├── flows │ │ │ ├── grpc-flow.yml │ │ │ ├── http-flow.yml │ │ │ ├── websocket-flow.yml │ │ │ └── http-flow-new-syntax.yml │ │ ├── test_basic_grpc.py │ │ ├── test_basic_http.py │ │ ├── test_basic_websocket.py │ │ └── test_basic_http_new_syntax.py │ ├── envvars │ │ ├── __init__.py │ │ ├── flows │ │ │ └── envs-in-flow.yml │ │ ├── test_yaml_env_file.py │ │ ├── test_envvars_context_syntax.py │ │ └── test_envvars_default_file.py │ ├── expose │ │ ├── __init__.py │ │ ├── flows │ │ │ ├── single-executor-stateless.yml │ │ │ ├── single-executor-stateful.yml │ │ │ └── gateway-and-executors.yml │ │ ├── test_single_executor_stateless.py │ │ ├── test_single_executor_stateful.py │ │ └── test_multiple_executors.py │ ├── jobs │ │ ├── __init__.py │ │ └── test_jobs.py │ ├── secrets │ │ ├── __init__.py │ │ └── test_secrets.py │ ├── stateful │ │ ├── __init__.py │ │ ├── flows │ │ │ ├── grpc-stateful.yml │ │ │ ├── http-stateful.yml │ │ │ └── websocket-stateful.yml │ │ ├── test_stateful_flow_grpc.py │ │ ├── test_stateful_flow_http.py │ │ └── test_stateful_flow_websocket.py │ ├── update │ │ ├── __init__.py │ │ ├── flows │ │ │ ├── base_flow.yml │ │ │ ├── update_image.yml │ │ │ ├── scale_out.yml │ │ │ ├── custom_name_executor.yml │ │ │ ├── expose_executor.yml │ │ │ ├── modify_env.yml │ │ │ ├── modify_delete_labels.yml │ │ │ ├── custom_name_exposed_executor.yml │ │ │ ├── add_args.yml │ │ │ ├── add_labels.yml │ │ │ ├── update_resources.yml │ │ │ └── add_env.yml │ │ ├── test_update_executor_image.py │ │ ├── test_update_executor_args.py │ │ ├── test_update_executor_resources.py │ │ ├── test_update_env.py │ │ ├── test_update_labels.py │ │ ├── test_update_expose_executor.py │ │ └── test_rename_executor.py │ ├── validate │ │ ├── __init__.py │ │ ├── flows │ │ │ ├── valid-flow.yml │ │ │ └── invalid-flow.yml │ │ └── test_validate.py │ ├── autoscale │ │ ├── __init__.py │ │ ├── flows │ │ │ └── executors-autoscaled.yml │ │ └── test_executors_autoscaled.py │ ├── custom_labels │ │ ├── __init__.py │ │ ├── flows │ │ │ └── valid-labels.yml │ │ └── test_custom_labels.py │ ├── custom_name │ │ ├── __init__.py │ │ ├── flows │ │ │ ├── valid-name.yml │ │ │ └── invalid-name.yml │ │ └── test_custom_name.py │ ├── executor_uses │ │ ├── __init__.py │ │ ├── flow.yml │ │ └── test_executor_uses.py │ ├── jina_version │ │ ├── __init__.py │ │ ├── flows │ │ │ └── custom-jina-version.yml │ │ └── test_custom_jina_version.py │ ├── custom_actions │ │ ├── __init__.py │ │ ├── flows │ │ │ └── base_flow.yml │ │ ├── test_recreate.py │ │ ├── test_scale.py │ │ ├── test_pause_resume.py │ │ └── test_restart.py │ ├── custom_resources │ │ ├── __init__.py │ │ ├── flows │ │ │ ├── gateway-resources.yml │ │ │ └── executor-resources.yml │ │ ├── test_gateway_resources.py │ │ └── test_executor_resources.py │ ├── remove_by_phase │ │ └── __init__.py │ ├── remove_multiple │ │ ├── __init__.py │ │ ├── flows │ │ │ ├── flow1.yml │ │ │ └── flow2.yml │ │ └── test_multi_flows_removal.py │ ├── test_expose_version_arg.py │ ├── __init__.py │ ├── projects │ │ ├── simple │ │ │ ├── executor1 │ │ │ │ ├── requirements.txt │ │ │ │ ├── config.yml │ │ │ │ └── executor.py │ │ │ ├── flow.yml │ │ │ └── app.py │ │ ├── multi_executors │ │ │ ├── executor1 │ │ │ │ ├── requirements.txt │ │ │ │ ├── config.yml │ │ │ │ └── executor.py │ │ │ ├── executor2 │ │ │ │ ├── requirements.txt │ │ │ │ ├── config.yml │ │ │ │ └── executor.py │ │ │ └── flow.yml │ │ ├── envvars_context_syntax │ │ │ ├── executor1 │ │ │ │ ├── requirements.txt │ │ │ │ ├── config.yml │ │ │ │ └── executor.py │ │ │ ├── .env │ │ │ └── flow.yml │ │ ├── envvars_default_file │ │ │ ├── executor1 │ │ │ │ ├── requirements.txt │ │ │ │ ├── config.yml │ │ │ │ └── executor.py │ │ │ ├── .env │ │ │ └── flow.yml │ │ └── executors_with_shards │ │ │ ├── executor1 │ │ │ ├── requirements.txt │ │ │ ├── config.yml │ │ │ └── executor.py │ │ │ └── flow.yml │ ├── executor_with_no_uses │ │ ├── flow │ │ │ ├── executor1 │ │ │ │ ├── config.yml │ │ │ │ └── executor.py │ │ │ ├── executor2 │ │ │ │ ├── config.yml │ │ │ │ └── executor.py │ │ │ └── flow.yml │ │ └── test_executor_with_no_uses.py │ ├── normalized_flow_env_vars │ │ ├── flow.yml │ │ └── test_normalized_flow_env_vars.py │ ├── test_simple.py │ ├── test_multi_executors.py │ ├── test_executors_with_shards.py │ └── test_jina_new.py │ └── deployment │ ├── basic │ ├── __init__.py │ ├── deployments │ │ ├── grpc-deployment.yml │ │ ├── http-deployment.yml │ │ └── multi-protocol-deployment.yml │ ├── test_basic_grpc.py │ └── test_basic_http.py │ ├── envvars │ ├── __init__.py │ ├── deployments │ │ └── envvars.yml │ └── test_yaml_env_file.py │ ├── update │ ├── __init__.py │ ├── deployments │ │ ├── update_image.yml │ │ ├── base_deployment.yml │ │ ├── scale_out.yml │ │ ├── rename_executor.yml │ │ ├── modify_env.yml │ │ ├── update_resources.yml │ │ ├── add_secret.yml │ │ ├── modify_delete_labels.yml │ │ ├── add_args.yml │ │ ├── add_labels.yml │ │ └── add_env.yml │ ├── test_rename_executor.py │ ├── test_update_executor_args.py │ ├── test_update_executor_image.py │ ├── test_update_executor_resources.py │ ├── test_update_env.py │ └── test_update_labels.py │ ├── autoscale │ ├── __init__.py │ ├── deployments │ │ └── autoscale-http.yml │ └── test_autoscale.py │ ├── custom_name │ ├── __init__.py │ ├── deployments │ │ ├── valid-name-deployment.yml │ │ └── invalid-name-deployment.yml │ └── test_custom_name.py │ ├── jina_version │ ├── __init__.py │ ├── deployments │ │ └── deployment-3.21.0.yml │ └── test_custom_jina_version.py │ ├── custom_action │ ├── __init__.py │ ├── deployments │ │ └── base_deployment.yml │ ├── test_restart.py │ ├── test_scale.py │ ├── test_recreate.py │ └── test_pause_resume.py │ ├── custom_labels │ ├── __init__.py │ ├── deployments │ │ └── deployment-with-labels.yml │ └── test_custom_labels.py │ ├── custom_resources │ ├── __init__.py │ ├── deployments │ │ └── deployment-with-custom-resources.yml │ └── test_custom_resources.py │ └── __init__.py ├── jcloud ├── resources │ └── project-template │ │ ├── .env │ │ ├── executor1 │ │ ├── requirements.txt │ │ ├── config.yml │ │ └── executor.py │ │ └── flow.yml ├── __init__.py ├── parsers │ ├── get.py │ ├── deploy.py │ ├── status.py │ ├── base.py │ ├── normalize.py │ ├── logs.py │ ├── create.py │ ├── list.py │ ├── remove.py │ ├── update.py │ └── __init__.py ├── __main__.py └── constants.py ├── MANIFEST.in ├── pytest.ini ├── pyproject.toml ├── .github ├── pull_request_template.md ├── labeler.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug-report--flow-deployment-failed-.md ├── workflows │ ├── tag.yml │ ├── force-release.yml │ ├── force-docs-build.yml │ ├── cd.yml │ ├── label-pr.yml │ └── integration-tests.yml └── README-img │ └── logo.svg ├── scripts ├── get-last-release-note.py ├── black.sh ├── get-all-test-paths.sh ├── docstrings_lint.sh └── release.sh ├── .pre-commit-config.yaml ├── README.md ├── .gitignore └── setup.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jcloud/resources/project-template/.env: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/basic/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/envvars/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/expose/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/jobs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/secrets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/stateful/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/update/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/validate/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jcloud/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.3.1' 2 | -------------------------------------------------------------------------------- /tests/integration/deployment/basic/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/deployment/envvars/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/autoscale/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_labels/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_name/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/executor_uses/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/jina_version/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/deployment/autoscale/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_name/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/deployment/jina_version/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_actions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_resources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/remove_by_phase/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/remove_multiple/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/test_expose_version_arg.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_action/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_labels/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_resources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/flows/normalized_flows/flow5.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow -------------------------------------------------------------------------------- /jcloud/resources/project-template/executor1/requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/__init__.py: -------------------------------------------------------------------------------- 1 | FlowAlive = 'FlowAlive' 2 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/simple/executor1/requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/multi_executors/executor1/requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/deployment/__init__.py: -------------------------------------------------------------------------------- 1 | DeploymentAlive = 'DeploymentAlive' 2 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/envvars_context_syntax/executor1/requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/envvars_default_file/executor1/requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/executors_with_shards/executor1/requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/multi_executors/executor2/requirements.txt: -------------------------------------------------------------------------------- 1 | requests -------------------------------------------------------------------------------- /tests/integration/flow/projects/envvars_context_syntax/.env: -------------------------------------------------------------------------------- 1 | VALUE_A=56 2 | VALUE_B=abcd 3 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/envvars_default_file/.env: -------------------------------------------------------------------------------- 1 | VALUE_A=56 2 | VALUE_B=abcd 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | prune tests/ 3 | prune **/tests/ 4 | recursive-include jcloud/resources * -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | asyncio_mode = auto 3 | log_cli_level = DEBUG 4 | env = 5 | JCLOUD_LOGLEVEL=DEBUG -------------------------------------------------------------------------------- /tests/unit/flows/failed_flows/failed_flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flows 2 | version: "1" 3 | with: 4 | port: 51000 5 | -------------------------------------------------------------------------------- /tests/unit/flows/not/flow4.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: E1 4 | uses: ${{ E1_USES }} 5 | -------------------------------------------------------------------------------- /tests/unit/flows/not/flow1.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: ABC 4 | uses: ABC/config.yml 5 | -------------------------------------------------------------------------------- /tests/unit/flows/not/flow3.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: ABC 4 | uses: something_random 5 | -------------------------------------------------------------------------------- /tests/unit/flows/normalized_flows/flow3.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: ABC 4 | uses: docker://ABC -------------------------------------------------------------------------------- /tests/unit/flows/normalized_flows/flow4.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: ABC 4 | uses: docker://ABC -------------------------------------------------------------------------------- /jcloud/resources/project-template/executor1/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor 2 | metas: 3 | py_modules: 4 | - executor.py -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=18.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /tests/unit/flows/local_flow/executor1/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor 2 | metas: 3 | py_modules: 4 | - executor.py 5 | -------------------------------------------------------------------------------- /tests/unit/flows/local_flow/executor2/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor 2 | metas: 3 | py_modules: 4 | - executor.py 5 | -------------------------------------------------------------------------------- /tests/unit/flows/mixed_flow/executor1/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor 2 | metas: 3 | py_modules: 4 | - executor.py 5 | -------------------------------------------------------------------------------- /tests/unit/flows/normalized_flows/flow1.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: ABC 4 | uses: jinahub+docker://ABC -------------------------------------------------------------------------------- /tests/unit/flows/normalized_flows/flow8.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: E1 4 | uses: ${{ E1_USES }} 5 | -------------------------------------------------------------------------------- /jcloud/resources/project-template/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: executor1 4 | uses: executor1/config.yml -------------------------------------------------------------------------------- /tests/integration/flow/projects/simple/executor1/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor 2 | metas: 3 | py_modules: 4 | - executor.py -------------------------------------------------------------------------------- /tests/unit/flows/normalized_flows/flow7.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: E1 4 | uses: ${{ ENV.E1_USES }} 5 | -------------------------------------------------------------------------------- /tests/unit/flows/normalized_flows/flow9.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: E1 4 | uses: ${{ CONTEXT.E1_USES }} 5 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/simple/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: executor1 4 | uses: executor1/config.yml -------------------------------------------------------------------------------- /tests/integration/flow/executor_uses/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: encoder 4 | uses: jinahub://Sentencizer 5 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/multi_executors/executor1/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor1 2 | metas: 3 | py_modules: 4 | - executor.py -------------------------------------------------------------------------------- /tests/integration/flow/projects/multi_executors/executor2/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor2 2 | metas: 3 | py_modules: 4 | - executor.py -------------------------------------------------------------------------------- /tests/integration/deployment/update/deployments/update_image.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinahub+docker://SimpleIndexer 4 | -------------------------------------------------------------------------------- /tests/integration/flow/executor_with_no_uses/flow/executor1/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor 2 | metas: 3 | py_modules: 4 | - executor.py 5 | -------------------------------------------------------------------------------- /tests/integration/flow/executor_with_no_uses/flow/executor2/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor 2 | metas: 3 | py_modules: 4 | - executor.py 5 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/envvars_context_syntax/executor1/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor 2 | metas: 3 | py_modules: 4 | - executor.py -------------------------------------------------------------------------------- /tests/integration/flow/projects/envvars_default_file/executor1/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor 2 | metas: 3 | py_modules: 4 | - executor.py -------------------------------------------------------------------------------- /tests/integration/flow/projects/executors_with_shards/executor1/config.yml: -------------------------------------------------------------------------------- 1 | jtype: MyExecutor 2 | metas: 3 | py_modules: 4 | - executor.py -------------------------------------------------------------------------------- /tests/unit/flows/not/flow2.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: ABC 4 | uses: ABC/config.yml 5 | - name: DEF 6 | uses: DEF/config.yml -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/base_flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - uses: jinahub+docker://Sentencizer 6 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_actions/flows/base_flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - uses: jinahub+docker://Sentencizer 6 | -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/update_image.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - uses: jinahub+docker://SimpleIndexer 6 | -------------------------------------------------------------------------------- /tests/unit/flows/flow2.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: abc 4 | uses: jinahub+docker://Sentencizer 5 | - name: def 6 | - needs: [abc, def] 7 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/executors_with_shards/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: executor1 4 | uses: executor1/config.yml 5 | shards: 2 -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/scale_out.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - uses: jinahub+docker://Sentencizer 6 | replicas: 2 7 | -------------------------------------------------------------------------------- /tests/integration/flow/basic/flows/grpc-flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: grpc 4 | executors: 5 | - name: sentencizer 6 | uses: jinahub+docker://Sentencizer 7 | -------------------------------------------------------------------------------- /tests/integration/flow/validate/flows/valid-flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - name: sentencizer 6 | uses: jinahub+docker://Sentencizer 7 | -------------------------------------------------------------------------------- /tests/unit/flows/flow1.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: abc 4 | uses: jinahub+docker://Sentencizer 5 | - name: def 6 | - name: joiner 7 | needs: [abc, def] 8 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/deployments/base_deployment.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 4 | -------------------------------------------------------------------------------- /tests/unit/flows/local_flow/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: executor1 4 | uses: executor1/config.yml 5 | - name: executor2 6 | uses: executor2/config.yml 7 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_name/flows/valid-name.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | jcloud: 3 | name: fashion-data 4 | executors: 5 | - name: sentencizer 6 | uses: jinahub+docker://Sentencizer 7 | -------------------------------------------------------------------------------- /tests/integration/flow/stateful/flows/grpc-stateful.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: grpc 4 | executors: 5 | - name: simpleindexer 6 | uses: jinahub+docker://SimpleIndexer 7 | -------------------------------------------------------------------------------- /tests/integration/flow/stateful/flows/http-stateful.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - name: simpleindexer 6 | uses: jinahub+docker://SimpleIndexer 7 | -------------------------------------------------------------------------------- /tests/unit/flows/flow-with-obj-label.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | jcloud: 3 | labels: 4 | test-label-invalid-obj: 5 | foo: bar 6 | executors: 7 | - uses: jinahub+docker://Sentencizer 8 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/deployments/scale_out.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 4 | replicas: 2 5 | -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/custom_name_executor.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - name: newsentencizer 6 | uses: jinahub+docker://Sentencizer 7 | -------------------------------------------------------------------------------- /tests/unit/flows/mixed_flow/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: executor1 4 | uses: executor1/config.yml 5 | - name: sentencizer 6 | uses: jinahub+docker://Sentencizer 7 | -------------------------------------------------------------------------------- /tests/integration/flow/stateful/flows/websocket-stateful.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: websocket 4 | executors: 5 | - name: simpleindexer 6 | uses: jinahub+docker://SimpleIndexer 7 | -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/expose_executor.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - uses: jinahub+docker://Sentencizer 6 | jcloud: 7 | expose: true 8 | -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/modify_env.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - uses: jinahub+docker://Sentencizer 6 | env: 7 | JINA_LOG_LEVEL : INFO 8 | -------------------------------------------------------------------------------- /tests/unit/flows/normalized_flows/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: executor1 4 | uses: jinahub+docker://Executor1 5 | - name: executor2 6 | uses: jinahub+docker://Executor2 7 | -------------------------------------------------------------------------------- /tests/integration/deployment/basic/deployments/grpc-deployment.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | protocol: grpc 4 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 5 | -------------------------------------------------------------------------------- /tests/integration/deployment/basic/deployments/http-deployment.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | protocol: http 4 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 5 | -------------------------------------------------------------------------------- /tests/integration/deployment/jina_version/deployments/deployment-3.21.0.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinahub+docker://Sentencizer 4 | jcloud: 5 | version: 3.21.0 6 | docarray: 0.20.0 7 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_name/flows/invalid-name.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | jcloud: 3 | name: abc_def#1/ # invalid name 4 | executors: 5 | - name: sentencizer 6 | uses: jinahub+docker://Sentencizer 7 | -------------------------------------------------------------------------------- /tests/integration/flow/jina_version/flows/custom-jina-version.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | jcloud: 5 | version: 3.9.3 6 | executors: 7 | - uses: jinahub+docker://Sentencizer 8 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_action/deployments/base_deployment.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | protocol: grpc 4 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 5 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/deployments/rename_executor.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | name: newsentencizer 4 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 5 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Goal** 2 | 3 | - [ ] Run [Integration tests GHA](https://github.com/jina-ai/jcloud/actions/workflows/integration-tests.yml) manually & comment the link. 4 | 5 | @jina-ai/team-wolf 6 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/deployments/modify_env.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 4 | env: 5 | JINA_LOG_LEVEL: INFO 6 | -------------------------------------------------------------------------------- /tests/integration/flow/normalized_flow_env_vars/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 4 | env: 5 | TEST: ${{ ENV.TEST }} 6 | -------------------------------------------------------------------------------- /tests/integration/flow/basic/flows/http-flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - name: sentencizer 6 | uses: jinahub+docker://Sentencizer 7 | env: 8 | JINA_LOG_LEVEL : DEBUG 9 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_labels/flows/valid-labels.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | jcloud: 3 | labels: 4 | label1: value1 5 | label2: value2 6 | executors: 7 | - name: sentencizer 8 | uses: jinahub+docker://Sentencizer 9 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/envvars_context_syntax/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: executor1 4 | uses: executor1/config.yml 5 | uses_with: 6 | var_a: ${{ VALUE_A }} 7 | var_b: ${{ VALUE_B }} -------------------------------------------------------------------------------- /tests/integration/deployment/update/deployments/update_resources.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 4 | jcloud: 5 | resources: 6 | instance: C2 7 | -------------------------------------------------------------------------------- /tests/integration/flow/basic/flows/websocket-flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: websocket 4 | executors: 5 | - name: sentencizer 6 | uses: jinahub+docker://Sentencizer 7 | env: 8 | JINA_LOG_LEVEL : DEBUG 9 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/envvars_default_file/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: executor1 4 | uses: executor1/config.yml 5 | uses_with: 6 | var_a: ${{ ENV.VALUE_A }} 7 | var_b: ${{ ENV.VALUE_B }} -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/modify_delete_labels.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | jcloud: 5 | labels: 6 | "jina.ai/application": "retail-search" 7 | executors: 8 | - uses: jinahub+docker://Sentencizer 9 | -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/custom_name_exposed_executor.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - name: newsentencizer 6 | uses: jinahub+docker://Sentencizer 7 | jcloud: 8 | expose: true 9 | -------------------------------------------------------------------------------- /tests/integration/flow/basic/flows/http-flow-new-syntax.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - name: sentencizer 6 | uses: jinaai+docker://jina-ai/Sentencizer:latest 7 | env: 8 | JINA_LOG_LEVEL: DEBUG 9 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/simple/app.py: -------------------------------------------------------------------------------- 1 | from docarray import Document 2 | from jina import Flow 3 | 4 | f = Flow().add(uses='executor1/config.yml') 5 | 6 | with f: 7 | da = f.post('/', [Document(), Document()]) 8 | print(da.texts) 9 | -------------------------------------------------------------------------------- /tests/unit/flows/normalized_flows/flow2.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: ABC 4 | uses: jinaai+docker://ABC/hello 5 | - name: DEF 6 | uses: jinaai+sandbox://DEF/hello 7 | - name: XYZ 8 | uses: jinaai+serverless://XYZ/hello 9 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/deployments/add_secret.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 4 | env_from_secret: 5 | env1: 6 | name: secret1 7 | key: key1 8 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/deployments/modify_delete_labels.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 4 | jcloud: 5 | labels: 6 | "jina.ai/application": "retail-search" 7 | -------------------------------------------------------------------------------- /tests/integration/flow/expose/flows/single-executor-stateless.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | jcloud: 3 | gateway: 4 | expose: false 5 | executors: 6 | - name: sentencizer 7 | uses: jinahub+docker://Sentencizer 8 | jcloud: 9 | expose: true 10 | -------------------------------------------------------------------------------- /tests/unit/flows/local_flow/executor1/executor.py: -------------------------------------------------------------------------------- 1 | from jina import Executor, requests, DocumentArray 2 | 3 | 4 | class MyExecutor(Executor): 5 | @requests 6 | def foo(self, docs: DocumentArray, **kwargs): 7 | docs[:, 'text'] = 'hello, world!' 8 | -------------------------------------------------------------------------------- /tests/unit/flows/local_flow/executor2/executor.py: -------------------------------------------------------------------------------- 1 | from jina import Executor, requests, DocumentArray 2 | 3 | 4 | class MyExecutor(Executor): 5 | @requests 6 | def foo(self, docs: DocumentArray, **kwargs): 7 | docs[:, 'text'] = 'hello, world!' 8 | -------------------------------------------------------------------------------- /tests/unit/flows/mixed_flow/executor1/executor.py: -------------------------------------------------------------------------------- 1 | from jina import Executor, requests, DocumentArray 2 | 3 | 4 | class MyExecutor(Executor): 5 | @requests 6 | def foo(self, docs: DocumentArray, **kwargs): 7 | docs[:, 'text'] = 'hello, world!' 8 | -------------------------------------------------------------------------------- /jcloud/resources/project-template/executor1/executor.py: -------------------------------------------------------------------------------- 1 | from jina import Executor, requests, DocumentArray 2 | 3 | 4 | class MyExecutor(Executor): 5 | @requests 6 | def foo(self, docs: DocumentArray, **kwargs): 7 | docs[:, 'text'] = 'hello, world!' 8 | -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/add_args.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - uses: jinahub+docker://Sentencizer 6 | env: 7 | PUNCT_CHARS: '(!,)' 8 | uses_with: 9 | punct_chars: ${{ ENV.PUNCT_CHARS }} 10 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/deployments/add_args.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 4 | env: 5 | PUNCT_CHARS: "(!,)" 6 | uses_with: 7 | punct_chars: ${{ ENV.PUNCT_CHARS }} 8 | -------------------------------------------------------------------------------- /tests/integration/flow/executor_with_no_uses/flow/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: executor1 4 | uses: executor1/config.yml 5 | - name: executor2 6 | uses: jinahub+docker://Sentencizer 7 | - name: joiner 8 | needs: [executor1, executor2] 9 | -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/add_labels.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | jcloud: 5 | labels: 6 | "jina.ai/username": "johndoe" 7 | "jina.ai/application": "fashion-search" 8 | executors: 9 | - uses: jinahub+docker://Sentencizer 10 | -------------------------------------------------------------------------------- /tests/integration/flow/validate/flows/invalid-flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | gateway: 3 | protocol: http 4 | executors: 5 | - name: sentencizer 6 | uses: jinahub+docker://Sentencizer 7 | jcloud: 8 | monitor: 9 | traces: 10 | host: 123 11 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_name/deployments/valid-name-deployment.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment # Tests only for API layer 2 | with: 3 | protocol: http 4 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 5 | jcloud: 6 | name: fashion-data 7 | -------------------------------------------------------------------------------- /tests/integration/flow/executor_with_no_uses/flow/executor1/executor.py: -------------------------------------------------------------------------------- 1 | from jina import Executor, requests, DocumentArray 2 | 3 | 4 | class MyExecutor(Executor): 5 | @requests 6 | def foo(self, docs: DocumentArray, **kwargs): 7 | docs[:, 'text'] = 'hello, world!' 8 | -------------------------------------------------------------------------------- /tests/integration/flow/executor_with_no_uses/flow/executor2/executor.py: -------------------------------------------------------------------------------- 1 | from jina import Executor, requests, DocumentArray 2 | 3 | 4 | class MyExecutor(Executor): 5 | @requests 6 | def foo(self, docs: DocumentArray, **kwargs): 7 | docs[:, 'text'] = 'hello, world!' 8 | -------------------------------------------------------------------------------- /tests/integration/flow/remove_multiple/flows/flow1.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | jcloud: 5 | name: remove-multiple-1 6 | executors: 7 | - name: sentencizer 8 | uses: jinahub+docker://Sentencizer 9 | env: 10 | JINA_LOG_LEVEL : DEBUG 11 | -------------------------------------------------------------------------------- /tests/integration/flow/remove_multiple/flows/flow2.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | jcloud: 5 | name: remove-multiple-2 6 | executors: 7 | - name: sentencizer 8 | uses: jinahub+docker://Sentencizer 9 | env: 10 | JINA_LOG_LEVEL : DEBUG 11 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_labels/deployments/deployment-with-labels.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | protocol: grpc 4 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 5 | jcloud: 6 | labels: 7 | label1: value1 8 | label2: value2 9 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/deployments/add_labels.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 4 | jcloud: 5 | labels: 6 | "jina.ai/username": "johndoe" 7 | "jina.ai/application": "fashion-search" 8 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_resources/flows/gateway-resources.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | jcloud: 3 | gateway: 4 | resources: 5 | requests: 6 | memory: 800M 7 | cpu: 0.4 8 | executors: 9 | - name: sentencizer 10 | uses: jinahub+docker://Sentencizer 11 | -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/update_resources.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - uses: jinahub+docker://Sentencizer 6 | jcloud: 7 | resources: 8 | capacity: on-demand 9 | cpu: 0.2 10 | memory: "200M" 11 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_name/deployments/invalid-name-deployment.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment # Tests only for API layer 2 | with: 3 | protocol: http 4 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 5 | jcloud: 6 | name: abc_def#1/ # invalid name 7 | -------------------------------------------------------------------------------- /tests/integration/flow/expose/flows/single-executor-stateful.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | jcloud: 5 | gateway: 6 | expose: false 7 | executors: 8 | - name: simpleindexer 9 | uses: jinahub+docker://SimpleIndexer 10 | jcloud: 11 | expose: true 12 | -------------------------------------------------------------------------------- /tests/integration/deployment/envvars/deployments/envvars.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 4 | env: 5 | JINA_LOG_LEVEL: DEBUG 6 | PUNCT_CHARS: "(!,)" 7 | uses_with: 8 | punct_chars: ${{ ENV.PUNCT_CHARS }} 9 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/deployments/add_env.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 4 | env: 5 | JINA_LOG_LEVEL: DEBUG 6 | PUNCT_CHARS: "(!,)" 7 | uses_with: 8 | punct_chars: ${{ ENV.PUNCT_CHARS }} 9 | -------------------------------------------------------------------------------- /tests/integration/flow/update/flows/add_env.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - uses: jinahub+docker://Sentencizer 6 | env: 7 | JINA_LOG_LEVEL : DEBUG 8 | PUNCT_CHARS: '(!,)' 9 | uses_with: 10 | punct_chars: ${{ ENV.PUNCT_CHARS }} 11 | -------------------------------------------------------------------------------- /tests/integration/deployment/autoscale/deployments/autoscale-http.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | protocol: http 4 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 5 | jcloud: 6 | autoscale: 7 | max: 3 8 | min: 1 9 | target: 40 10 | metric: rps 11 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/simple/executor1/executor.py: -------------------------------------------------------------------------------- 1 | from jina import DocumentArray, Executor, requests 2 | 3 | 4 | class MyExecutor(Executor): 5 | @requests 6 | def foo(self, docs: DocumentArray, **kwargs): 7 | docs[0].text = 'hello, world!' 8 | docs[1].text = 'goodbye, world!' 9 | -------------------------------------------------------------------------------- /tests/integration/flow/envvars/flows/envs-in-flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - name: sentencizer 6 | uses: jinahub+docker://Sentencizer 7 | env: 8 | JINA_LOG_LEVEL : DEBUG 9 | PUNCT_CHARS: '(!,)' 10 | uses_with: 11 | punct_chars: ${{ ENV.PUNCT_CHARS }} 12 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/executors_with_shards/executor1/executor.py: -------------------------------------------------------------------------------- 1 | from jina import Document, DocumentArray, Executor, requests 2 | 3 | 4 | class MyExecutor(Executor): 5 | @requests 6 | def get_shard_id(self, docs: DocumentArray, **kwargs): 7 | return DocumentArray([Document(text=str(self.runtime_args.shard_id))]) 8 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_resources/deployments/deployment-with-custom-resources.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | with: 3 | name: c2instance 4 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 5 | jcloud: 6 | nodeSelector: 7 | karpenter.sh/capacity-type: on-demand 8 | resources: 9 | instance: C2 10 | nodeGroup: ALL 11 | -------------------------------------------------------------------------------- /scripts/get-last-release-note.py: -------------------------------------------------------------------------------- 1 | ## under jina root dir 2 | # python scripts/get-last-release-note.py 3 | ## result in root/tmp.md 4 | 5 | with open('CHANGELOG.md') as fp: 6 | n = [] 7 | for v in fp: 8 | if v.startswith('## Release Note'): 9 | n.clear() 10 | n.append(v) 11 | 12 | with open('tmp.md', 'w') as fp: 13 | fp.writelines(n) 14 | -------------------------------------------------------------------------------- /tests/integration/flow/expose/flows/gateway-and-executors.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | jcloud: 5 | expose: true 6 | executors: 7 | - name: sentencizer 8 | uses: jinahub+docker://Sentencizer 9 | jcloud: 10 | expose: true 11 | - name: simpleindexer 12 | uses: jinahub+docker://SimpleIndexer 13 | jcloud: 14 | expose: true 15 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/multi_executors/flow.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | executors: 5 | - name: executor1 6 | uses: executor1/config.yml 7 | uses_with: 8 | init_var: init_var_ex1 9 | - name: executor2 10 | uses: executor2/config.yml 11 | install_requirements: true 12 | uses_with: 13 | init_var: init_var_ex2 14 | -------------------------------------------------------------------------------- /tests/unit/flows/flow-with-labels.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | jcloud: 3 | labels: 4 | test-label-one: 1 5 | test-label-two: 231 6 | executors: 7 | - uses: jinahub+docker://Sentencizer 8 | jcloud: 9 | labels: 10 | test-executor-label-one: 352 11 | test-executor-label-two: "hello" 12 | test-label-bool: true 13 | test-label-complex: 1j 14 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/ambv/black 3 | rev: 22.3.0 4 | hooks: 5 | - id: black 6 | types: [python] 7 | exclude: ^(docarray/proto/docarray_pb2.py|docs/|docarray/resources/) 8 | args: 9 | - -S 10 | - repo: https://github.com/asottile/blacken-docs 11 | rev: v1.12.1 12 | hooks: 13 | - id: blacken-docs 14 | args: 15 | - -S -------------------------------------------------------------------------------- /tests/unit/flows/flow1-test.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: abc 4 | uses: jinahub+docker://Sentencizer 5 | - name: def 6 | - name: joiner 7 | needs: 8 | - abc 9 | - def 10 | jcloud: 11 | docarray: 0.21.1 12 | version: 3.20.3 13 | with: 14 | env_from_secret: 15 | env1: 16 | name: test 17 | key: env1 18 | env2: 19 | name: test 20 | key: env2 21 | -------------------------------------------------------------------------------- /tests/integration/flow/autoscale/flows/executors-autoscaled.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | protocol: http 4 | jcloud: 5 | gateway: 6 | ingress: kong 7 | executors: 8 | - name: auto1 9 | uses: jinahub+docker://Sentencizer 10 | jcloud: 11 | autoscale: 12 | min: 1 13 | max: 2 14 | metric: rps 15 | target: 2 16 | - name: auto2 17 | uses: jinahub+serverless://Sentencizer 18 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/multi_executors/executor1/executor.py: -------------------------------------------------------------------------------- 1 | from jina import DocumentArray, Executor, requests 2 | 3 | 4 | class MyExecutor1(Executor): 5 | def __init__(self, init_var, **kwargs): 6 | super().__init__(**kwargs) 7 | self.init_var = init_var 8 | 9 | @requests 10 | def foo(self, docs: DocumentArray, **kwargs): 11 | for d in docs: 12 | d.tags.update({'MyExecutor1': self.init_var}) 13 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/multi_executors/executor2/executor.py: -------------------------------------------------------------------------------- 1 | from jina import DocumentArray, Executor, requests 2 | 3 | 4 | class MyExecutor2(Executor): 5 | def __init__(self, init_var, **kwargs): 6 | super().__init__(**kwargs) 7 | self.init_var = init_var 8 | 9 | @requests 10 | def foo(self, docs: DocumentArray, **kwargs): 11 | for d in docs: 12 | d.tags.update({'MyExecutor2': self.init_var}) 13 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/envvars_context_syntax/executor1/executor.py: -------------------------------------------------------------------------------- 1 | from jina import DocumentArray, Executor, requests 2 | 3 | 4 | class MyExecutor(Executor): 5 | def __init__(self, var_a, var_b, *args, **kwargs): 6 | super().__init__(*args, **kwargs) 7 | self.var_a = var_a 8 | self.var_b = var_b 9 | 10 | @requests 11 | def foo(self, docs: DocumentArray, **kwargs): 12 | docs[:, 'tags'] = {'var_a': self.var_a, 'var_b': self.var_b} 13 | -------------------------------------------------------------------------------- /tests/integration/flow/projects/envvars_default_file/executor1/executor.py: -------------------------------------------------------------------------------- 1 | from jina import DocumentArray, Executor, requests 2 | 3 | 4 | class MyExecutor(Executor): 5 | def __init__(self, var_a, var_b, *args, **kwargs): 6 | super().__init__(*args, **kwargs) 7 | self.var_a = var_a 8 | self.var_b = var_b 9 | 10 | @requests 11 | def foo(self, docs: DocumentArray, **kwargs): 12 | docs[:, 'tags'] = {'var_a': self.var_a, 'var_b': self.var_b} 13 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_resources/flows/executor-resources.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | executors: 3 | - name: sentencizer 4 | uses: jinahub+docker://Sentencizer 5 | jcloud: 6 | capacity: spot 7 | resources: 8 | memory: 800M 9 | cpu: 1 10 | - name: simpleindexer 11 | uses: jinahub+docker://SimpleIndexer 12 | jcloud: 13 | capacity: on-demand 14 | resources: 15 | memory: 200M 16 | storage: 17 | kind: ebs 18 | size: 1Gi 19 | -------------------------------------------------------------------------------- /tests/integration/deployment/basic/deployments/multi-protocol-deployment.yml: -------------------------------------------------------------------------------- 1 | jtype: Deployment 2 | gateway: 3 | uses_with: 4 | http_port: "8000" 5 | grpc_port: "9000" 6 | port: 7 | - 8000 8 | - 9000 9 | protocol: 10 | - http 11 | - grpc 12 | jcloud: 13 | custom_dns_http: 14 | - operator-test.docsqa.jina.ai 15 | custom_dns_grpc: 16 | - operator-test.wolf.jina.ai 17 | with: 18 | uses: jinaai+docker://auth0-unified-64c3e19b1d2f398f/JCloudCISentencizer:latest 19 | -------------------------------------------------------------------------------- /scripts/black.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pip install black==22.3.0 3 | arrVar=() 4 | echo we ignore non-*.py files and files generated from protobuf 5 | excluded_files=( 6 | docarray/proto/docarray_pb2.py 7 | docs/conf.py 8 | ) 9 | for changed_file in $CHANGED_FILES; do 10 | if [[ ${changed_file} == *.py ]] && ! [[ " ${excluded_files[@]} " =~ " ${changed_file} " ]]; then 11 | echo checking ${changed_file} 12 | arrVar+=(${changed_file}) 13 | fi 14 | done 15 | if [ ${#arrVar[@]} -ne 0 ]; then 16 | black -S --check "${arrVar[@]}" 17 | fi 18 | -------------------------------------------------------------------------------- /jcloud/parsers/get.py: -------------------------------------------------------------------------------- 1 | from .helper import _chf 2 | 3 | 4 | def set_get_resource_parser(subparser, resource): 5 | get_parser = subparser.add_parser( 6 | 'get', 7 | help=f'Get the details of a {resource.title()}.', 8 | formatter_class=_chf, 9 | ) 10 | 11 | get_parser.add_argument( 12 | 'name', 13 | type=str, 14 | help=f'The name of the {resource.title()}.', 15 | ) 16 | 17 | get_parser.add_argument( 18 | 'flow', 19 | type=str, 20 | help='The string ID of the Flow.', 21 | ) 22 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Add 'label1' to any changes within 'example' folder or any subfolders 2 | area/docs: 3 | - docs/**/* 4 | - ./*.md 5 | 6 | area/testing: 7 | - tests/**/* 8 | 9 | area/setup: 10 | - setup.py 11 | - extra-requirements.txt 12 | - requirements.txt 13 | - MANIFEST.in 14 | 15 | area/core: 16 | - jcloud/**/* 17 | 18 | area/entrypoint: 19 | - jcloud/__init__.py 20 | 21 | area/housekeeping: 22 | - .github/**/* 23 | - ./.gitignore 24 | - ./*.yaml 25 | - ./*.yml 26 | 27 | area/cicd: 28 | - .github/workflows/**/* 29 | 30 | area/docker: 31 | - Dockerfiles/**/* 32 | - ./.dockerignore 33 | 34 | area/script: 35 | - script/**/* 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/integration/flow/test_simple.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, DocumentArray 5 | 6 | from jcloud.flow import CloudFlow 7 | 8 | cur_dir = os.path.dirname(os.path.abspath(__file__)) 9 | 10 | 11 | def test_project_simple(): 12 | with CloudFlow(path=os.path.join(cur_dir, 'projects', 'simple')) as flow: 13 | assert flow.endpoints != {} 14 | assert 'gateway' in flow.endpoints 15 | gateway = flow.endpoints['gateway'] 16 | 17 | da = Client(host=gateway).post( 18 | on='/', 19 | inputs=DocumentArray.empty(2), 20 | ) 21 | assert da.texts == ['hello, world!', 'goodbye, world!'] 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /tests/integration/flow/jina_version/test_custom_jina_version.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Client, Document 4 | 5 | from jcloud.flow import CloudFlow 6 | 7 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 8 | flow_file = 'custom-jina-version.yml' 9 | 10 | 11 | def test_expose_version_arg(): 12 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 13 | assert flow.endpoints != {} 14 | assert 'gateway' in flow.endpoints 15 | gateway = flow.endpoints['gateway'] 16 | da = Client(host=gateway).post(on="/", inputs=Document(text="Hello. World.")) 17 | assert da[0].chunks[0].text == "Hello." and da[0].chunks[1].text == "World." 18 | -------------------------------------------------------------------------------- /tests/integration/flow/autoscale/test_executors_autoscaled.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, Document 5 | 6 | from jcloud.flow import CloudFlow 7 | 8 | cur_dir = os.path.dirname(os.path.abspath(__file__)) 9 | 10 | 11 | @pytest.mark.skip('unskip once autoscaling is implemented') 12 | def test_customized_resources(): 13 | FLOW_FILE_PATH = os.path.join(cur_dir, "flows", "executors-autoscaled.yml") 14 | with CloudFlow(path=FLOW_FILE_PATH, name="executors-autoscaled") as flow: 15 | da = Client(host=flow.gateway).post( 16 | on="/", inputs=Document(text="Hello. World.") 17 | ) 18 | assert da[0].chunks[0].text == "Hello." and da[0].chunks[1].text == "World." 19 | -------------------------------------------------------------------------------- /scripts/get-all-test-paths.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | 5 | TEST_SUITE=$1 6 | DEFAULT_BATCH_SIZE=5 7 | BATCH_SIZE="${2:-$DEFAULT_BATCH_SIZE}" 8 | 9 | declare -a unit_tests=($(find tests/unit -name "test_*.py")) 10 | declare -a integration_tests=($(find tests/integration -name "test_*.py")) 11 | declare -a all_tests=("${unit_tests[@]}" "${integration_tests[@]}") 12 | 13 | if [ "$TEST_SUITE" == "unit" ]; then 14 | dest="$(echo "${unit_tests[@]}" | xargs -n$BATCH_SIZE)" 15 | elif [[ "$TEST_SUITE" == "integration" ]]; then 16 | dest="$(echo "${integration_tests[@]}" | xargs -n$BATCH_SIZE)" 17 | else 18 | dest="$(echo "${all_tests[@]}" | xargs -n$BATCH_SIZE)" 19 | fi 20 | 21 | printf '%s\n' "${dest[@]}" | jq -R . | jq -cs . 22 | -------------------------------------------------------------------------------- /tests/integration/flow/executor_uses/test_executor_uses.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, Document 5 | 6 | from jcloud.flow import CloudFlow 7 | 8 | cur_dir = os.path.dirname(os.path.abspath(__file__)) 9 | flow_file = 'flow.yml' 10 | 11 | 12 | def test_legacy_executor_syntax(): 13 | with CloudFlow(path=os.path.join(cur_dir, flow_file)) as flow: 14 | assert flow.endpoints != {} 15 | assert 'gateway' in flow.endpoints 16 | gateway = flow.endpoints['gateway'] 17 | 18 | da = Client(host=gateway).post( 19 | on='/', 20 | inputs=Document(text='Hello. World.'), 21 | ) 22 | assert da[0].chunks[0].text == 'Hello.' and da[0].chunks[1].text == 'World.' 23 | -------------------------------------------------------------------------------- /tests/integration/flow/executor_with_no_uses/test_executor_with_no_uses.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, Document, DocumentArray 5 | 6 | from jcloud.flow import CloudFlow 7 | 8 | cur_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flow') 9 | flow_file = 'flow.yml' 10 | 11 | 12 | def test_executor_with_no_uses(): 13 | with CloudFlow(path=os.path.join(cur_dir, flow_file)) as flow: 14 | assert flow.endpoints != {} 15 | assert 'gateway' in flow.endpoints 16 | gateway = flow.endpoints['gateway'] 17 | 18 | da = Client(host=gateway).post( 19 | on='/', 20 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 21 | ) 22 | assert len(da.texts) == 50 23 | -------------------------------------------------------------------------------- /tests/integration/flow/validate/test_validate.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Client, Document, DocumentArray 4 | 5 | from jcloud.flow import CloudFlow 6 | 7 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 8 | valid_flow_file = 'valid-flow.yml' 9 | invalid_flow_file = 'invalid-flow.yml' 10 | 11 | 12 | async def test_valid_flow(): 13 | validate_response = await CloudFlow( 14 | path=os.path.join(flows_dir, valid_flow_file) 15 | ).validate() 16 | 17 | assert len(validate_response['errors']) == 0 18 | 19 | 20 | async def test_invalid_flow(): 21 | validate_response = await CloudFlow( 22 | path=os.path.join(flows_dir, invalid_flow_file) 23 | ).validate() 24 | assert len(validate_response['errors']) == 2 25 | -------------------------------------------------------------------------------- /tests/integration/flow/test_multi_executors.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, DocumentArray 5 | 6 | from jcloud.flow import CloudFlow 7 | 8 | cur_dir = os.path.dirname(os.path.abspath(__file__)) 9 | 10 | 11 | def test_project_multi_executors(): 12 | with CloudFlow(path=os.path.join(cur_dir, 'projects', 'multi_executors')) as flow: 13 | assert flow.endpoints != {} 14 | assert 'gateway' in flow.endpoints 15 | gateway = flow.endpoints['gateway'] 16 | 17 | da: DocumentArray = Client(host=gateway).post( 18 | on='/', 19 | inputs=DocumentArray.empty(2), 20 | ) 21 | for d in da: 22 | assert d.tags['MyExecutor1'] == 'init_var_ex1' 23 | assert d.tags['MyExecutor2'] == 'init_var_ex2' 24 | -------------------------------------------------------------------------------- /tests/integration/flow/basic/test_basic_grpc.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Client, Document, DocumentArray 4 | 5 | from jcloud.flow import CloudFlow 6 | 7 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 8 | flow_file = 'grpc-flow.yml' 9 | protocol = 'grpc' 10 | 11 | 12 | def test_basic_grpc_flow(): 13 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 14 | assert flow.endpoints != {} 15 | assert 'gateway' in flow.endpoints 16 | gateway = flow.endpoints['gateway'] 17 | assert gateway.startswith(f'{protocol}s://') 18 | 19 | da = Client(host=gateway).post( 20 | on='/', 21 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 22 | ) 23 | assert len(da.texts) == 50 24 | -------------------------------------------------------------------------------- /tests/integration/flow/basic/test_basic_http.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Client, Document, DocumentArray 4 | 5 | from jcloud.flow import CloudFlow 6 | 7 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 8 | flow_file = 'http-flow.yml' 9 | protocol = 'http' 10 | 11 | 12 | def test_basic_http_flow(): 13 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 14 | assert flow.endpoints != {} 15 | assert 'gateway' in flow.endpoints 16 | gateway = flow.endpoints['gateway'] 17 | assert gateway.startswith(f'{protocol}s://') 18 | 19 | da = Client(host=gateway).post( 20 | on='/', 21 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 22 | ) 23 | assert len(da.texts) == 50 24 | -------------------------------------------------------------------------------- /tests/integration/flow/envvars/test_yaml_env_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, Document, DocumentArray 5 | 6 | from jcloud.flow import CloudFlow 7 | 8 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 9 | flow_file = 'envs-in-flow.yml' 10 | 11 | 12 | def test_yaml_env_file(): 13 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 14 | assert flow.endpoints != {} 15 | assert 'gateway' in flow.endpoints 16 | gateway = flow.endpoints['gateway'] 17 | 18 | da = Client(host=gateway).post( 19 | on='/', 20 | inputs=DocumentArray(Document(text='hello! There? abc')), 21 | ) 22 | assert da[0].chunks[0].text == 'hello!' 23 | assert da[0].chunks[1].text == 'There? abc' 24 | -------------------------------------------------------------------------------- /tests/integration/flow/basic/test_basic_websocket.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Client, Document, DocumentArray 4 | 5 | from jcloud.flow import CloudFlow 6 | 7 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 8 | flow_file = 'websocket-flow.yml' 9 | protocol = 'ws' 10 | 11 | 12 | def test_basic_websocket_flow(): 13 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 14 | assert flow.endpoints != {} 15 | assert 'gateway' in flow.endpoints 16 | gateway = flow.endpoints['gateway'] 17 | assert gateway.startswith(f'{protocol}s://') 18 | 19 | da = Client(host=gateway).post( 20 | on='/', 21 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 22 | ) 23 | assert len(da.texts) == 50 24 | -------------------------------------------------------------------------------- /jcloud/__main__.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | import logging 3 | import os 4 | 5 | from .parsers import get_main_parser 6 | 7 | args = get_main_parser().parse_args() 8 | if args.loglevel: 9 | os.environ['JCLOUD_LOGLEVEL'] = args.loglevel 10 | 11 | logging.getLogger('asyncio').setLevel(logging.WARNING) 12 | 13 | try: 14 | if 'NO_VERSION_CHECK' not in os.environ: 15 | from .helper import is_latest_version 16 | 17 | is_latest_version() 18 | from jcloud import api 19 | 20 | if hasattr(args, 'subcommand'): 21 | getattr(api, args.subcommand.replace('-', '_'))(args) 22 | else: 23 | getattr(api, args.jc_cli.replace('-', '_'))(args) 24 | except KeyboardInterrupt: 25 | pass 26 | 27 | 28 | if __name__ == '__main__': 29 | main() 30 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_resources/test_gateway_resources.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Client, Document, DocumentArray 4 | 5 | from jcloud.flow import CloudFlow 6 | 7 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 8 | protocol = 'grpc' 9 | flow_file = 'gateway-resources.yml' 10 | 11 | 12 | def test_gateway_resources(): 13 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 14 | assert flow.endpoints != {} 15 | assert 'gateway' in flow.endpoints 16 | gateway = flow.endpoints['gateway'] 17 | assert gateway.startswith(f'{protocol}s://') 18 | 19 | da = Client(host=gateway).post( 20 | on='/', 21 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 22 | ) 23 | assert len(da.texts) == 50 24 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_resources/test_executor_resources.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, Document, DocumentArray 5 | 6 | from jcloud.flow import CloudFlow 7 | 8 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 9 | protocol = 'grpc' 10 | flow_file = 'executor-resources.yml' 11 | 12 | 13 | def test_executor_resources(): 14 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 15 | assert flow.endpoints != {} 16 | assert 'gateway' in flow.endpoints 17 | gateway = flow.endpoints['gateway'] 18 | assert gateway.startswith(f'{protocol}s://') 19 | 20 | da = Client(host=gateway).post( 21 | on='/', 22 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 23 | ) 24 | assert len(da.texts) == 50 25 | -------------------------------------------------------------------------------- /tests/integration/deployment/jina_version/test_custom_jina_version.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Client, Document 4 | 5 | from jcloud.deployment import CloudDeployment 6 | 7 | deployments_dir = os.path.join( 8 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 9 | ) 10 | deployment_file = 'deployment-3.21.0.yml' 11 | executor_name = 'executor' 12 | 13 | 14 | def test_expose_version_arg(): 15 | with CloudDeployment( 16 | path=os.path.join(deployments_dir, deployment_file) 17 | ) as deployment: 18 | assert deployment.endpoints != {} 19 | assert executor_name in deployment.endpoints 20 | endpoint = deployment.endpoints[executor_name] 21 | da = Client(host=endpoint).post(on="/", inputs=Document(text="Hello. World.")) 22 | assert da[0].chunks[0].text == "Hello." and da[0].chunks[1].text == "World." 23 | -------------------------------------------------------------------------------- /tests/unit/flows/normalized_flows/flow6.yml: -------------------------------------------------------------------------------- 1 | jtype: Flow 2 | with: 3 | cors: true 4 | protocol: http 5 | port_expose: 12345 6 | expose_endpoints: 7 | /abc: 8 | methods: ['POST'] 9 | executors: 10 | - name: E1 11 | uses: jinahub+docker://E1 12 | 13 | - name: E2 14 | uses: jinahub+docker://E1 15 | shards: 2 16 | 17 | - name: E3 18 | needs: E1 19 | when: 20 | tags__answered: 21 | $exists: False 22 | uses: jinahub+docker://E3 23 | uses_with: 24 | limit: 5 25 | replicas: 3 26 | 27 | - name: E4 28 | needs: E2 29 | when: 30 | tags__answered: 31 | $exists: False 32 | uses: jinahub+docker://E4 33 | install_requirements: true 34 | uses_with: 35 | abc: def 36 | timeout_ready: -1 37 | 38 | - name: E5 39 | needs: [E3, E4] 40 | uses: jinahub+docker://E5 41 | -------------------------------------------------------------------------------- /tests/integration/deployment/basic/test_basic_grpc.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.deployment import CloudDeployment 4 | from jcloud.constants import Phase 5 | 6 | from tests.utils import utils 7 | 8 | deployments_dir = os.path.join( 9 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 10 | ) 11 | deployment_file = 'grpc-deployment.yml' 12 | protocol = 'grpc' 13 | executor_name = 'executor' 14 | 15 | 16 | def test_basic_grpc_deployment(): 17 | with CloudDeployment( 18 | path=os.path.join(deployments_dir, deployment_file) 19 | ) as deployment: 20 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 21 | assert deployment.endpoints != {} 22 | assert executor_name in deployment.endpoints 23 | endpoint = deployment.endpoints[executor_name] 24 | assert endpoint.startswith(f'{protocol}s://') 25 | 26 | assert utils.eventually_serve_requests(endpoint) 27 | -------------------------------------------------------------------------------- /tests/integration/deployment/basic/test_basic_http.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.deployment import CloudDeployment 4 | from jcloud.constants import Phase 5 | 6 | from tests.utils import utils 7 | 8 | deployments_dir = os.path.join( 9 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 10 | ) 11 | deployment_file = 'http-deployment.yml' 12 | protocol = 'http' 13 | executor_name = 'executor' 14 | 15 | 16 | def test_basic_grpc_deployment(): 17 | with CloudDeployment( 18 | path=os.path.join(deployments_dir, deployment_file) 19 | ) as deployment: 20 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 21 | assert deployment.endpoints != {} 22 | assert executor_name in deployment.endpoints 23 | endpoint = deployment.endpoints[executor_name] 24 | assert endpoint.startswith(f'{protocol}s://') 25 | 26 | assert utils.eventually_serve_requests(endpoint) 27 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_labels/test_custom_labels.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, Document, DocumentArray 5 | 6 | from jcloud.flow import CloudFlow 7 | 8 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 9 | protocol = 'grpc' 10 | 11 | 12 | def test_custom_labels(): 13 | labels_file = 'valid-labels.yml' 14 | with CloudFlow(path=os.path.join(flows_dir, labels_file)) as flow: 15 | assert flow.endpoints != {} 16 | assert 'gateway' in flow.endpoints 17 | gateway = flow.endpoints['gateway'] 18 | assert gateway.startswith(f'{protocol}s://') 19 | 20 | da = Client(host=gateway).post( 21 | on='/', 22 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 23 | ) 24 | assert len(da.texts) == 50 25 | 26 | # TODO: check that the labels are actually set by searching for them 27 | -------------------------------------------------------------------------------- /tests/integration/deployment/autoscale/test_autoscale.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.deployment import CloudDeployment 4 | from jcloud.constants import Phase 5 | 6 | from tests.utils import utils 7 | 8 | cur_dir = os.path.dirname(os.path.abspath(__file__)) 9 | 10 | protocol = 'http' 11 | executor_name = 'executor' 12 | 13 | 14 | # @pytest.mark.skip('unskip once autoscaling is implemented') 15 | def test_autoscale_deployment(): 16 | deployment_file_path = os.path.join(cur_dir, "deployments", "autoscale-http.yml") 17 | with CloudDeployment(path=deployment_file_path) as deployment: 18 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 19 | assert deployment.endpoints != {} 20 | assert executor_name in deployment.endpoints 21 | endpoint = deployment.endpoints[executor_name] 22 | assert endpoint.startswith(f'{protocol}s://') 23 | assert utils.eventually_serve_requests(endpoint) 24 | -------------------------------------------------------------------------------- /tests/integration/deployment/envvars/test_yaml_env_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, Document, DocumentArray 5 | 6 | from jcloud.deployment import CloudDeployment 7 | 8 | deployments_dir = os.path.join( 9 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 10 | ) 11 | deployment_file = 'envvars.yml' 12 | executor_name = 'executor' 13 | 14 | 15 | def test_yaml_env_file(): 16 | with CloudDeployment( 17 | path=os.path.join(deployments_dir, deployment_file) 18 | ) as deployment: 19 | assert deployment.endpoints != {} 20 | assert executor_name in deployment.endpoints 21 | endpoint = deployment.endpoints[executor_name] 22 | 23 | da = Client(host=endpoint).post( 24 | on='/', 25 | inputs=DocumentArray(Document(text='hello! There? abc')), 26 | ) 27 | assert da[0].chunks[0].text == 'hello!' 28 | assert da[0].chunks[1].text == 'There? abc' 29 | -------------------------------------------------------------------------------- /tests/integration/flow/envvars/test_envvars_context_syntax.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Dict 3 | 4 | import pytest 5 | from jina import Client, DocumentArray 6 | 7 | from jcloud.flow import CloudFlow 8 | 9 | projects_dir = os.path.join( 10 | os.path.dirname(os.path.abspath(__file__)), '..', 'projects' 11 | ) 12 | protocol = 'grpc' 13 | 14 | 15 | def sorted_dict(d: Dict): 16 | return dict(sorted(d.items())) 17 | 18 | 19 | def test_envvars_context_syntax(): 20 | with CloudFlow( 21 | path=os.path.join(projects_dir, 'envvars_context_syntax'), 22 | ) as flow: 23 | assert flow.endpoints != {} 24 | assert 'gateway' in flow.endpoints 25 | gateway = flow.endpoints['gateway'] 26 | assert gateway.startswith(f'{protocol}s://') 27 | 28 | da = Client(host=gateway).post(on='/', inputs=DocumentArray.empty(2)) 29 | for d in da: 30 | assert sorted_dict(d.tags) == sorted_dict({'var_a': 56.0, 'var_b': 'abcd'}) 31 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_labels/test_custom_labels.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.deployment import CloudDeployment 4 | from jcloud.constants import Phase 5 | 6 | from tests.utils import utils 7 | 8 | deployments_dir = os.path.join( 9 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 10 | ) 11 | protocol = 'grpc' 12 | executor_name = 'executor' 13 | 14 | 15 | def test_custom_labels(): 16 | labels_file = 'deployment-with-labels.yml' 17 | with CloudDeployment(path=os.path.join(deployments_dir, labels_file)) as deployment: 18 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 19 | 20 | assert deployment.endpoints != {} 21 | assert executor_name in deployment.endpoints 22 | endpoint = deployment.endpoints[executor_name] 23 | assert endpoint.startswith(f'{protocol}s://') 24 | 25 | assert utils.eventually_serve_requests(endpoint) 26 | 27 | # TODO: check that the labels are actually set by searching for them 28 | -------------------------------------------------------------------------------- /tests/integration/flow/basic/test_basic_http_new_syntax.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Client, Document, DocumentArray 4 | 5 | from jcloud.flow import CloudFlow 6 | 7 | from tests.utils import utils 8 | from .. import FlowAlive 9 | 10 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 11 | flow_file = 'http-flow-new-syntax.yml' 12 | protocol = 'http' 13 | 14 | 15 | def test_basic_http_flow_with_new_syntax(): 16 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 17 | assert flow.endpoints != {} 18 | assert 'gateway' in flow.endpoints 19 | gateway = flow.endpoints['gateway'] 20 | assert gateway.startswith(f'{protocol}s://') 21 | 22 | ltt = utils.get_last_transition_time(flow, FlowAlive) 23 | assert ltt 24 | 25 | da = Client(host=gateway).post( 26 | on='/', 27 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 28 | ) 29 | assert len(da.texts) == 50 30 | -------------------------------------------------------------------------------- /tests/integration/flow/expose/test_single_executor_stateless.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Document, DocumentArray, Flow 4 | 5 | from jcloud.flow import CloudFlow 6 | from jcloud.helper import remove_prefix 7 | 8 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 9 | flow_file = 'single-executor-stateless.yml' 10 | 11 | 12 | def test_single_executor_stateless(): 13 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 14 | assert flow.endpoints != {} 15 | assert 'sentencizer' in flow.endpoints 16 | sentencizer_host = flow.endpoints['sentencizer'] 17 | with Flow().add( 18 | host=remove_prefix(sentencizer_host, 'grpcs://'), 19 | external=True, 20 | port=443, 21 | tls=True, 22 | ) as f: 23 | da = f.post( 24 | on='/', 25 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 26 | ) 27 | assert len(da.texts) == 50 28 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_resources/test_custom_resources.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, Document, DocumentArray 5 | 6 | from jcloud.deployment import CloudDeployment 7 | 8 | deployments_dir = os.path.join( 9 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 10 | ) 11 | protocol = 'grpc' 12 | deployment_file = 'deployment-with-custom-resources.yml' 13 | executor_name = 'c2instance' 14 | 15 | 16 | def test_executor_resources(): 17 | with CloudDeployment( 18 | path=os.path.join(deployments_dir, deployment_file) 19 | ) as deployment: 20 | assert deployment.endpoints != {} 21 | assert executor_name in deployment.endpoints 22 | endpoint = deployment.endpoints[executor_name] 23 | assert endpoint.startswith(f'{protocol}s://') 24 | 25 | da = Client(host=endpoint).post( 26 | on='/', 27 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 28 | ) 29 | assert len(da.texts) == 50 30 | -------------------------------------------------------------------------------- /jcloud/parsers/deploy.py: -------------------------------------------------------------------------------- 1 | from .helper import _chf 2 | from ..constants import Resources 3 | 4 | 5 | def set_deploy_parser(subparser, parser_prog): 6 | if Resources.Flow in parser_prog: 7 | set_flow_deploy_parser(subparser) 8 | elif Resources.Deployment in parser_prog: 9 | set_deployment_deploy_parser(subparser) 10 | 11 | 12 | def set_flow_deploy_parser(subparser): 13 | deploy_parser = subparser.add_parser( 14 | 'deploy', 15 | help='Deploy a Flow.', 16 | formatter_class=_chf, 17 | ) 18 | 19 | deploy_parser.add_argument( 20 | 'path', 21 | type=str, 22 | help='The local path to a Jina flow project.', 23 | ) 24 | 25 | 26 | def set_deployment_deploy_parser(subparser): 27 | deploy_parser = subparser.add_parser( 28 | 'deploy', 29 | help='Deploy a Deployment.', 30 | formatter_class=_chf, 31 | ) 32 | 33 | deploy_parser.add_argument( 34 | 'path', 35 | type=str, 36 | help='The local path to a Jina deployment project.', 37 | ) 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report--flow-deployment-failed-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report (Flow deployment failed) 3 | about: Create a report to help us improve 4 | title: 'Flow deployment failed (Request ID: ...)' 5 | labels: bug 6 | assignees: zac-li, deepankarm 7 | 8 | --- 9 | 10 | ## Flow deployment failed 11 | 12 | #### Command 13 | ```bash 14 | # Please paste here the command that failed 15 | ``` 16 | 17 | #### Flow yaml 18 | ```yaml 19 | # Paste your Flow yaml here 20 | ``` 21 | 22 | #### Flow ID / Request ID 23 | ```text 24 | Flow ID: N/A 25 | Request ID: N/A 26 | ``` 27 | 28 | 29 | --- 30 | 31 | #### `.env` file 32 | 35 | N/A 36 | 37 | 38 | #### `jina` version 39 | ```bash 40 | jina -vf 41 | ... 42 | ``` 43 | 44 | #### Logs 45 | 48 | N/A 49 | 50 | --- 51 | 52 | #### Additional context 53 | Add any other context about the problem here. 54 | -------------------------------------------------------------------------------- /tests/integration/flow/test_executors_with_shards.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Client 4 | 5 | from jcloud.flow import CloudFlow 6 | 7 | cur_dir = os.path.dirname(os.path.abspath(__file__)) 8 | 9 | 10 | def test_project_with_shards(): 11 | with CloudFlow( 12 | path=os.path.join(cur_dir, 'projects', 'executors_with_shards') 13 | ) as flow: 14 | 15 | assert flow.endpoints != {} 16 | assert 'gateway' in flow.endpoints 17 | gateway = flow.endpoints['gateway'] 18 | 19 | shard_0_counter = shard_1_counter = 0 20 | for _ in range(5): 21 | da = Client(host=gateway).post(on='/', inputs=[]) 22 | shard_id = da[0].text 23 | 24 | if shard_id == "0": 25 | shard_0_counter += 1 26 | elif shard_id == "1": 27 | shard_1_counter += 1 28 | else: 29 | assert False, "Unexpected shard encountered." 30 | 31 | # Both shard-0 and shard-1 should be used at least once. 32 | assert shard_0_counter > 0 and shard_0_counter > 0 33 | -------------------------------------------------------------------------------- /tests/integration/flow/secrets/test_secrets.py: -------------------------------------------------------------------------------- 1 | import os 2 | import asyncio 3 | 4 | from jcloud.flow import CloudFlow 5 | from jcloud.constants import Resources 6 | 7 | flows_dir = os.path.join( 8 | os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'basic', 'flows' 9 | ) 10 | flow_file = 'http-flow.yml' 11 | 12 | 13 | def test_create_secret(): 14 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 15 | 16 | secret_response = asyncio.run(flow.create_secret('mysecret', {'env1': 'value'})) 17 | assert 'name' in secret_response 18 | 19 | secret = asyncio.run(flow.get_resource(Resources.Secret, 'mysecret')) 20 | 21 | assert secret['name'] == 'mysecret' 22 | assert secret['data'] == {'env1': 'value'} 23 | 24 | asyncio.run(flow.update_secret('mysecret', {'env1': 'value2'})) 25 | 26 | secret = asyncio.run(flow.get_resource(Resources.Secret, 'mysecret')) 27 | 28 | assert secret['name'] == 'mysecret' 29 | assert secret['data'] == {'env1': 'value2'} 30 | 31 | asyncio.run(flow.delete_resource(Resources.Secret, 'mysecret')) 32 | -------------------------------------------------------------------------------- /tests/integration/flow/normalized_flow_env_vars/test_normalized_flow_env_vars.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Client, Document, DocumentArray 4 | 5 | from jcloud.flow import CloudFlow 6 | 7 | from jcloud.helper import get_dict_list_key_path 8 | 9 | cur_dir = os.path.dirname(os.path.abspath(__file__)) 10 | flow_file = 'flow.yml' 11 | protocol = 'grpc' 12 | 13 | 14 | def test_update_executor_args(): 15 | os.environ["TEST"] = "test" 16 | with CloudFlow(path=os.path.join(cur_dir, flow_file)) as flow: 17 | 18 | assert flow.endpoints != {} 19 | assert 'gateway' in flow.endpoints 20 | gateway = flow.endpoints['gateway'] 21 | assert gateway.startswith(f'{protocol}s://') 22 | 23 | status = flow._loop.run_until_complete(flow.status) 24 | assert ( 25 | get_dict_list_key_path(status, ['spec', 'executors', 0, 'env', 'TEST']) 26 | == 'test' 27 | ) 28 | 29 | da = Client(host=gateway).post( 30 | on='/', 31 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 32 | ) 33 | assert len(da.texts) == 50 34 | -------------------------------------------------------------------------------- /tests/integration/flow/envvars/test_envvars_default_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, DocumentArray 5 | 6 | from jcloud.flow import CloudFlow 7 | from jcloud.env_helper import EnvironmentVariables 8 | from jcloud.helper import load_envs 9 | 10 | projects_dir = os.path.join( 11 | os.path.dirname(os.path.abspath(__file__)), '..', 'projects' 12 | ) 13 | protocol = 'grpc' 14 | 15 | 16 | def sorted_dict(d): 17 | return dict(sorted(d.items())) 18 | 19 | 20 | def test_envvars_default_file(): 21 | envs = load_envs(os.path.join(projects_dir, 'envvars_default_file', '.env')) 22 | with EnvironmentVariables(envs) as _: 23 | with CloudFlow(path=os.path.join(projects_dir, 'envvars_default_file')) as flow: 24 | assert flow.endpoints != {} 25 | assert 'gateway' in flow.endpoints 26 | gateway = flow.endpoints['gateway'] 27 | assert gateway.startswith(f'{protocol}s://') 28 | 29 | da = Client(host=gateway).post(on='/', inputs=DocumentArray.empty(2)) 30 | for d in da: 31 | assert sorted_dict(d.tags) == sorted_dict( 32 | {'var_a': 56.0, 'var_b': 'abcd'} 33 | ) 34 | -------------------------------------------------------------------------------- /tests/integration/flow/jobs/test_jobs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import asyncio 3 | 4 | from jcloud.flow import CloudFlow 5 | from jcloud.constants import Resources 6 | 7 | flows_dir = os.path.join( 8 | os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'basic', 'flows' 9 | ) 10 | flow_file = 'http-flow.yml' 11 | 12 | 13 | def test_jobs(): 14 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 15 | 16 | job_response = asyncio.run( 17 | flow.create_job( 18 | 'test-job', 19 | 'docker://jinaai/jina:3.18-standard', 20 | ['jina', '-v'], 21 | 600, 22 | 5, 23 | ) 24 | ) 25 | assert 'name' in job_response 26 | assert 'status' in job_response 27 | 28 | job_logs = '' 29 | while len(job_logs) == 0: 30 | job_logs = asyncio.run(flow.job_logs('test-job')) 31 | assert '3.18.0' in job_logs 32 | 33 | job = asyncio.run(flow.get_resource(Resources.Job, 'test-job')) 34 | assert job['name'] == 'test-job' 35 | 36 | jobs = asyncio.run(flow.list_resources(Resources.Job)) 37 | assert len(jobs) == 1 38 | assert jobs[0]['name'] == 'test-job' 39 | 40 | asyncio.run(flow.delete_resource(Resources.Job, 'test-job')) 41 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_name/test_custom_name.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, Document, DocumentArray 5 | 6 | from jcloud.flow import CloudFlow 7 | 8 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 9 | protocol = 'grpc' 10 | 11 | 12 | def test_valid_custom_name(): 13 | valid_flow_file = 'valid-name.yml' 14 | valid_custom_name = 'fashion-data' 15 | 16 | with CloudFlow(path=os.path.join(flows_dir, valid_flow_file)) as flow: 17 | assert valid_custom_name in flow.flow_id 18 | assert flow.endpoints != {} 19 | assert 'gateway' in flow.endpoints 20 | gateway = flow.endpoints['gateway'] 21 | assert gateway.startswith(f'{protocol}s://') 22 | assert valid_custom_name in gateway 23 | 24 | da = Client(host=gateway).post( 25 | on='/', 26 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 27 | ) 28 | assert len(da.texts) == 50 29 | 30 | 31 | def test_invalid_custom_name(capsys): 32 | invalid_flow_file = 'invalid-name.yml' 33 | invalid_custom_name = 'abc_def#1/' 34 | 35 | with pytest.raises(SystemExit): 36 | with CloudFlow(path=os.path.join(flows_dir, invalid_flow_file)): 37 | pass 38 | 39 | captured = capsys.readouterr() 40 | assert f'invalid name {invalid_custom_name}' in captured.out 41 | -------------------------------------------------------------------------------- /jcloud/parsers/status.py: -------------------------------------------------------------------------------- 1 | from .helper import _chf 2 | from ..constants import Resources 3 | 4 | 5 | def set_status_parser(subparser, parser_prog): 6 | if Resources.Flow in parser_prog: 7 | set_flow_status_parser(subparser) 8 | elif Resources.Deployment in parser_prog: 9 | set_deployment_status_parser(subparser) 10 | 11 | 12 | def set_flow_status_parser(subparser): 13 | status_parser = subparser.add_parser( 14 | 'status', 15 | help='Get the status of a Flow.', 16 | formatter_class=_chf, 17 | ) 18 | 19 | status_parser.add_argument( 20 | 'flow', 21 | type=str, 22 | help='The string ID of a flow.', 23 | ) 24 | 25 | status_parser.add_argument( 26 | '--verbose', 27 | action='store_true', 28 | default=False, 29 | help='Pass if you want to see the full details of the Flow.', 30 | ) 31 | 32 | 33 | def set_deployment_status_parser(subparser): 34 | status_parser = subparser.add_parser( 35 | 'status', 36 | help='Get the status of a Deployment.', 37 | formatter_class=_chf, 38 | ) 39 | 40 | status_parser.add_argument( 41 | 'deployment', 42 | type=str, 43 | help='The string ID of a deployment.', 44 | ) 45 | 46 | status_parser.add_argument( 47 | '--verbose', 48 | action='store_true', 49 | default=False, 50 | help='Pass if you want to see the full details of the Deployment.', 51 | ) 52 | -------------------------------------------------------------------------------- /tests/integration/flow/expose/test_single_executor_stateful.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | from jina import Document, Flow 5 | 6 | from jcloud.flow import CloudFlow 7 | from jcloud.helper import remove_prefix 8 | 9 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 10 | flow_file = 'single-executor-stateful.yml' 11 | 12 | 13 | def test_single_executor_stateful(): 14 | index_docs = [ 15 | Document(text=f'text-{i}', embedding=np.array([i, i + 1, i + 2])) 16 | for i in range(5) 17 | ] 18 | query_doc = index_docs[0] 19 | 20 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 21 | assert flow.endpoints != {} 22 | assert 'simpleindexer' in flow.endpoints 23 | simpleindexer_host = flow.endpoints['simpleindexer'] 24 | with Flow().add( 25 | host=remove_prefix(simpleindexer_host, 'grpcs://'), 26 | external=True, 27 | port=443, 28 | tls=True, 29 | ) as f1: 30 | da_index = f1.index(inputs=index_docs) 31 | assert da_index.texts == [f'text-{i}' for i in range(5)] 32 | for limit in [3, 5]: 33 | da_search = f1.search( 34 | inputs=query_doc, 35 | parameters={'limit': limit}, 36 | ) 37 | assert len(da_search[0].matches.texts) == limit 38 | assert da_search[0].matches.texts == [f'text-{i}' for i in range(limit)] 39 | -------------------------------------------------------------------------------- /.github/workflows/tag.yml: -------------------------------------------------------------------------------- 1 | name: Release CD 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" # push to version tags trigger the build 7 | 8 | #on: 9 | # push: 10 | # branches-ignore: 11 | # - '**' # temporally disable this action 12 | 13 | jobs: 14 | update-doc: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: benc-uk/workflow-dispatch@v1 18 | with: 19 | workflow: Manual Docs Build 20 | token: ${{ secrets.JINA_DEV_BOT }} 21 | inputs: '{ "release_token": "${{ env.release_token }}", "triggered_by": "TAG"}' 22 | env: 23 | release_token: ${{ secrets.JCLOUD_RELEASE_TOKEN }} 24 | 25 | create-release: 26 | needs: update-doc 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v2 31 | with: 32 | ref: 'main' 33 | - uses: actions/setup-python@v2 34 | with: 35 | python-version: 3.7 36 | - run: | 37 | python scripts/get-last-release-note.py 38 | - name: Create Release 39 | id: create_release 40 | uses: actions/create-release@v1 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 43 | with: 44 | tag_name: ${{ github.ref }} 45 | release_name: 💫 Patch ${{ github.ref }} 46 | body_path: 'tmp.md' 47 | draft: false 48 | prerelease: false 49 | -------------------------------------------------------------------------------- /jcloud/parsers/base.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | 4 | def set_base_parser(parser=None): 5 | import os 6 | 7 | from .. import __version__ 8 | from .helper import _chf, colored 9 | 10 | if not parser: 11 | parser = argparse.ArgumentParser( 12 | description=f'JCloud (v{colored(__version__, "green")}) deploys your Jina Flow to the cloud.', 13 | formatter_class=_chf, 14 | ) 15 | parser.add_argument( 16 | '-v', 17 | '--version', 18 | action='version', 19 | version=__version__, 20 | help='Show version.', 21 | ) 22 | parser.add_argument( 23 | '--loglevel', 24 | type=str, 25 | choices=['DEBUG', 'INFO', 'CRITICAL', 'NOTSET'], 26 | default=os.environ.get('JCLOUD_LOGLEVEL', 'INFO'), 27 | help='Set the loglevel of the logger', 28 | ) 29 | return parser 30 | 31 | 32 | def set_simple_parser(parser=None): 33 | if not parser: 34 | parser = set_base_parser() 35 | 36 | parser.add_argument( 37 | 'flow', 38 | type=str, 39 | help='The string ID of a flow.', 40 | ) 41 | return parser 42 | 43 | 44 | def set_new_project_parser(parser=None): 45 | if not parser: 46 | parser = set_base_parser() 47 | 48 | parser.add_argument( 49 | 'path', 50 | type=str, 51 | nargs='?', 52 | help='The new project will be created at this path.', 53 | default='helloworld', 54 | ) 55 | return parser 56 | -------------------------------------------------------------------------------- /scripts/docstrings_lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # required in order to get the status of all the files at once 3 | pip install darglint==1.6.0 4 | pip install pydocstyle==5.1.1 5 | echo ==================================================================================== 6 | echo DOCSTRINGS LINT: checking $CHANGED_FILES 7 | echo ------------------------------------------------------------------------------------ 8 | echo 'removing files under /tests...' 9 | arrVar=() 10 | # we ignore tests files 11 | for changed_file in $CHANGED_FILES; do 12 | case ${changed_file} in 13 | tests/* | \ 14 | .github/* | \ 15 | scripts/* | \ 16 | docarray/resources/* | \ 17 | docs/* | \ 18 | setup.py | \ 19 | fastentrypoints.py) 20 | ;;*) 21 | echo keeping ${changed_file} 22 | arrVar+=(${changed_file}) 23 | ;; 24 | esac 25 | done 26 | 27 | # if array is empty 28 | if [ ${#arrVar[@]} -eq 0 ]; then 29 | echo 'nothing to check' 30 | exit 0 31 | fi 32 | 33 | DARGLINT_OUTPUT=$(darglint -v 2 -s sphinx "${arrVar[@]}"); PYDOCSTYLE_OUTPUT=$(pydocstyle --select=D101,D102,D103 "${arrVar[@]}") 34 | # status captured here 35 | if [[ -z "$PYDOCSTYLE_OUTPUT" ]] && [[ -z "$DARGLINT_OUTPUT" ]]; then 36 | echo 'OK' 37 | exit 0 38 | else 39 | echo 'failure. make sure to check the guide for docstrings: https://docarray.jina.ai/chapters/docstring.html' 40 | echo $DARGLINT_OUTPUT 41 | echo $PYDOCSTYLE_OUTPUT 42 | exit 1 43 | fi 44 | echo ==================================================================================== 45 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_actions/test_recreate.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.flow import CloudFlow 4 | from jcloud.constants import Phase 5 | 6 | from tests.utils import utils 7 | from .. import FlowAlive 8 | 9 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 10 | flow_file = 'base_flow.yml' 11 | protocol = 'http' 12 | 13 | 14 | def test_recreate_flow(): 15 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 16 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 17 | 18 | assert flow.endpoints != {} 19 | assert 'gateway' in flow.endpoints 20 | gateway = flow.endpoints['gateway'] 21 | assert gateway.startswith(f'{protocol}s://') 22 | 23 | ltt = utils.get_last_transition_time(flow, FlowAlive) 24 | assert ltt 25 | 26 | assert utils.eventually_serve_requests(gateway) 27 | 28 | # terminate the flow 29 | flow._loop.run_until_complete(flow._terminate()) 30 | assert utils.eventually_reaches_phase(flow, Phase.Deleted) 31 | 32 | # recreate the flow 33 | flow._loop.run_until_complete(flow.recreate()) 34 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 35 | assert utils.eventually_condition_gets_updated(flow, FlowAlive, ltt) 36 | 37 | assert flow.endpoints != {} 38 | assert 'gateway' in flow.endpoints 39 | gateway = flow.endpoints['gateway'] 40 | assert gateway.startswith(f'{protocol}s://') 41 | 42 | assert utils.eventually_serve_requests(gateway) 43 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_actions/test_scale.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.flow import CloudFlow 4 | from jcloud.constants import Phase 5 | 6 | from tests.utils import utils 7 | from .. import FlowAlive 8 | 9 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 10 | flow_file = 'base_flow.yml' 11 | protocol = 'http' 12 | 13 | 14 | def test_scale_flow(): 15 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 16 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 17 | 18 | assert flow.endpoints != {} 19 | assert 'gateway' in flow.endpoints 20 | gateway = flow.endpoints['gateway'] 21 | assert gateway.startswith(f'{protocol}s://') 22 | 23 | ltt = utils.get_last_transition_time(flow, FlowAlive) 24 | assert ltt 25 | 26 | assert utils.eventually_serve_requests(gateway) 27 | 28 | # scale executor to 2 replicas 29 | flow._loop.run_until_complete(flow.scale(executor='executor0', replicas=2)) 30 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 31 | assert utils.eventually_condition_gets_updated(flow, FlowAlive, ltt) 32 | 33 | assert flow.endpoints != {} 34 | assert 'gateway' in flow.endpoints 35 | gateway = flow.endpoints['gateway'] 36 | assert gateway.startswith(f'{protocol}s://') 37 | 38 | assert utils.eventually_serve_requests(gateway) 39 | 40 | status = flow._loop.run_until_complete(flow.status) 41 | assert status['spec']['executors'][0]['replicas'] == 2 42 | -------------------------------------------------------------------------------- /tests/integration/flow/test_jina_new.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | import subprocess 5 | import tempfile 6 | 7 | import pytest 8 | 9 | from pathlib import Path 10 | from venv import create 11 | from jina import Client, DocumentArray 12 | from jcloud.flow import CloudFlow 13 | 14 | cur_dir = os.path.dirname(os.path.abspath(__file__)) 15 | 16 | 17 | def setup_venv(): 18 | v_dir = Path(tempfile.mkdtemp()) 19 | create(v_dir, with_pip=True) 20 | 21 | _pip_path = v_dir / 'bin' / 'pip' 22 | subprocess.run([_pip_path, 'install', '-U', 'pip', '-q']) 23 | subprocess.run([_pip_path, 'install', 'jina[standard]==3.18.0', '-q']) 24 | return v_dir 25 | 26 | 27 | def test_jina_new_project(): 28 | v_dir = setup_venv() 29 | subprocess.run( 30 | [v_dir / 'bin' / 'jina', 'new', os.path.join(cur_dir, 'hello-world')], 31 | ) 32 | assert os.path.exists(os.path.join(cur_dir, 'hello-world')) 33 | assert os.path.isdir(os.path.join(cur_dir, 'hello-world')) 34 | 35 | with CloudFlow(path=os.path.join(cur_dir, 'hello-world')) as flow: 36 | assert flow.endpoints != {} 37 | assert 'gateway (grpc)' in flow.endpoints 38 | assert 'gateway (http)' in flow.endpoints 39 | assert 'gateway (websocket)' in flow.endpoints 40 | gateway = flow.endpoints['gateway (grpc)'] 41 | 42 | da = Client(host=gateway).post(on='/', inputs=DocumentArray.empty(2)) 43 | assert da.texts == ['hello, world!', 'goodbye, world!'] 44 | 45 | shutil.rmtree(os.path.join(cur_dir, 'hello-world')) 46 | 47 | assert not os.path.exists(os.path.join(cur_dir, 'hello-world')) 48 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_name/test_custom_name.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jina import Client, Document, DocumentArray 5 | 6 | from jcloud.deployment import CloudDeployment 7 | 8 | deployments_dir = os.path.join( 9 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 10 | ) 11 | protocol = 'http' 12 | executor_name = 'executor' 13 | 14 | 15 | def test_valid_custom_name(): 16 | valid_deployment_file = 'valid-name-deployment.yml' 17 | valid_custom_name = 'fashion-data' 18 | 19 | with CloudDeployment( 20 | path=os.path.join(deployments_dir, valid_deployment_file) 21 | ) as deployment: 22 | assert valid_custom_name in deployment.deployment_id 23 | assert deployment.endpoints != {} 24 | assert executor_name in deployment.endpoints 25 | endpoint = deployment.endpoints[executor_name] 26 | assert endpoint.startswith(f'{protocol}s://') 27 | assert valid_custom_name in endpoint 28 | 29 | da = Client(host=endpoint).post( 30 | on='/', 31 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 32 | ) 33 | assert len(da.texts) == 50 34 | 35 | 36 | def test_invalid_custom_name(capsys): 37 | invalid_deployment_file = 'invalid-name-deployment.yml' 38 | invalid_custom_name = 'abc_def#1/' 39 | 40 | with pytest.raises(SystemExit): 41 | with CloudDeployment( 42 | path=os.path.join(deployments_dir, invalid_deployment_file) 43 | ): 44 | pass 45 | 46 | captured = capsys.readouterr() 47 | assert f'invalid name {invalid_custom_name}' in captured.out 48 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_action/test_restart.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.deployment import CloudDeployment 4 | from jcloud.constants import Phase 5 | 6 | from tests.utils import utils 7 | from .. import DeploymentAlive 8 | 9 | 10 | deployments_dir = os.path.join( 11 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 12 | ) 13 | deployment_file = 'base_deployment.yml' 14 | protocol = 'grpc' 15 | executor_name = 'executor' 16 | 17 | 18 | def test_restart_deployment(): 19 | with CloudDeployment( 20 | path=os.path.join(deployments_dir, deployment_file) 21 | ) as deployment: 22 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 23 | 24 | assert deployment.endpoints != {} 25 | assert executor_name in deployment.endpoints 26 | endpoint = deployment.endpoints[executor_name] 27 | assert endpoint.startswith(f'{protocol}s://') 28 | 29 | ltt = utils.get_last_transition_time(deployment, DeploymentAlive) 30 | assert ltt 31 | 32 | assert utils.eventually_serve_requests(endpoint) 33 | 34 | # restart the deployment 35 | deployment._loop.run_until_complete(deployment.restart()) 36 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 37 | assert utils.eventually_condition_gets_updated(deployment, DeploymentAlive, ltt) 38 | 39 | assert deployment.endpoints != {} 40 | assert executor_name in deployment.endpoints 41 | endpoint = deployment.endpoints[executor_name] 42 | assert endpoint.startswith(f'{protocol}s://') 43 | 44 | assert utils.eventually_serve_requests(endpoint) 45 | -------------------------------------------------------------------------------- /tests/integration/flow/expose/test_multiple_executors.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jina import Client, Document, DocumentArray, Flow 4 | 5 | from jcloud.flow import CloudFlow 6 | from jcloud.helper import remove_prefix 7 | 8 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 9 | flow_file = 'gateway-and-executors.yml' 10 | 11 | 12 | def test_multiple_executors(): 13 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 14 | assert flow.endpoints != {} 15 | 16 | # Send data to the gateway 17 | assert 'gateway' in flow.endpoints 18 | gateway = flow.endpoints['gateway'] 19 | da = Client(host=gateway).post( 20 | on='/', 21 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 22 | ) 23 | assert len(da.texts) == 50 24 | 25 | assert 'sentencizer' in flow.endpoints 26 | sentencizer_host = flow.endpoints['sentencizer'] 27 | assert 'simpleindexer' in flow.endpoints 28 | simpleindexer_host = flow.endpoints['simpleindexer'] 29 | 30 | # Test local gateway with remote executors 31 | with Flow().add( 32 | host=remove_prefix(sentencizer_host, 'grpcs://'), 33 | external=True, 34 | port=443, 35 | tls=True, 36 | ).add( 37 | host=remove_prefix(simpleindexer_host, 'grpcs://'), 38 | external=True, 39 | port=443, 40 | tls=True, 41 | ) as f: 42 | da = f.post( 43 | on='/', 44 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 45 | ) 46 | assert len(da.texts) == 50 47 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_action/test_scale.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.deployment import CloudDeployment 4 | from jcloud.constants import Phase 5 | 6 | from tests.utils import utils 7 | from .. import DeploymentAlive 8 | 9 | deployments_dir = os.path.join( 10 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 11 | ) 12 | deployment_file = 'base_deployment.yml' 13 | protocol = 'grpc' 14 | executor_name = 'executor' 15 | 16 | 17 | def test_scale_deployment(): 18 | with CloudDeployment( 19 | path=os.path.join(deployments_dir, deployment_file) 20 | ) as deployment: 21 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 22 | 23 | assert deployment.endpoints != {} 24 | assert executor_name in deployment.endpoints 25 | gateway = deployment.endpoints[executor_name] 26 | assert gateway.startswith(f'{protocol}s://') 27 | 28 | ltt = utils.get_last_transition_time(deployment, DeploymentAlive) 29 | assert ltt 30 | 31 | assert utils.eventually_serve_requests(gateway) 32 | 33 | # scale executor to 2 replicas 34 | deployment._loop.run_until_complete(deployment.scale(replicas=2)) 35 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 36 | assert utils.eventually_condition_gets_updated(deployment, DeploymentAlive, ltt) 37 | 38 | assert deployment.endpoints != {} 39 | assert executor_name in deployment.endpoints 40 | gateway = deployment.endpoints[executor_name] 41 | assert gateway.startswith(f'{protocol}s://') 42 | 43 | assert utils.eventually_serve_requests(gateway) 44 | 45 | status = deployment._loop.run_until_complete(deployment.status) 46 | assert status['spec']['with']['replicas'] == 2 47 | -------------------------------------------------------------------------------- /tests/integration/flow/update/test_update_executor_image.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.flow import CloudFlow 4 | from jcloud.constants import Phase 5 | 6 | from jcloud.helper import get_dict_list_key_path 7 | 8 | from tests.utils import utils 9 | from .. import FlowAlive 10 | 11 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 12 | flow_file = 'base_flow.yml' 13 | new_exc_image_flow_file = 'update_image.yml' 14 | protocol = 'http' 15 | 16 | 17 | def test_update_executor_image(): 18 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 19 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 20 | 21 | assert flow.endpoints != {} 22 | assert 'gateway' in flow.endpoints 23 | gateway = flow.endpoints['gateway'] 24 | assert gateway.startswith(f'{protocol}s://') 25 | 26 | ltt = utils.get_last_transition_time(flow, FlowAlive) 27 | assert ltt 28 | 29 | assert utils.eventually_serve_requests(gateway) 30 | 31 | flow.path = os.path.join(flows_dir, new_exc_image_flow_file) 32 | flow._loop.run_until_complete(flow.update()) 33 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 34 | assert utils.eventually_condition_gets_updated(flow, FlowAlive, ltt) 35 | 36 | assert flow.endpoints != {} 37 | assert 'gateway' in flow.endpoints 38 | gateway = flow.endpoints['gateway'] 39 | assert gateway.startswith(f'{protocol}s://') 40 | 41 | status = flow._loop.run_until_complete(flow.status) 42 | 43 | assert ( 44 | get_dict_list_key_path(status, ['spec', 'executors', 0, 'uses']) 45 | == 'jinahub+docker://SimpleIndexer' 46 | ) 47 | 48 | assert utils.eventually_serve_requests(gateway) 49 | -------------------------------------------------------------------------------- /tests/integration/flow/update/test_update_executor_args.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.flow import CloudFlow 4 | from jcloud.constants import Phase 5 | 6 | from jcloud.helper import get_dict_list_key_path 7 | 8 | from tests.utils import utils 9 | from .. import FlowAlive 10 | 11 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 12 | flow_file = 'base_flow.yml' 13 | new_exc_args_flow_file = 'add_args.yml' 14 | protocol = 'http' 15 | 16 | 17 | def test_update_executor_args(): 18 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 19 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 20 | 21 | assert flow.endpoints != {} 22 | assert 'gateway' in flow.endpoints 23 | gateway = flow.endpoints['gateway'] 24 | assert gateway.startswith(f'{protocol}s://') 25 | 26 | ltt = utils.get_last_transition_time(flow, FlowAlive) 27 | assert ltt 28 | 29 | assert utils.eventually_serve_requests(gateway) 30 | 31 | flow.path = os.path.join(flows_dir, new_exc_args_flow_file) 32 | flow._loop.run_until_complete(flow.update()) 33 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 34 | assert utils.eventually_condition_gets_updated(flow, FlowAlive, ltt) 35 | 36 | assert flow.endpoints != {} 37 | assert 'gateway' in flow.endpoints 38 | gateway = flow.endpoints['gateway'] 39 | assert gateway.startswith(f'{protocol}s://') 40 | 41 | status = flow._loop.run_until_complete(flow.status) 42 | assert ( 43 | get_dict_list_key_path( 44 | status, ['spec', 'executors', 0, 'uses_with', 'punct_chars'] 45 | ) 46 | == '$PUNCT_CHARS' 47 | ) 48 | 49 | assert utils.eventually_serve_requests(gateway) 50 | -------------------------------------------------------------------------------- /tests/integration/deployment/custom_action/test_recreate.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | from jcloud.deployment import CloudDeployment 5 | from jcloud.constants import Phase 6 | 7 | from tests.utils import utils 8 | from .. import DeploymentAlive 9 | 10 | deployments_dir = os.path.join( 11 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 12 | ) 13 | deployment_file = 'base_deployment.yml' 14 | protocol = 'grpc' 15 | executor_name = 'executor' 16 | 17 | 18 | def test_recreate_deployment(): 19 | with CloudDeployment( 20 | path=os.path.join(deployments_dir, deployment_file) 21 | ) as deployment: 22 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 23 | 24 | assert deployment.endpoints != {} 25 | assert executor_name in deployment.endpoints 26 | endpoint = deployment.endpoints[executor_name] 27 | assert endpoint.startswith(f'{protocol}s://') 28 | 29 | ltt = utils.get_last_transition_time(deployment, DeploymentAlive) 30 | assert ltt 31 | 32 | assert utils.eventually_serve_requests(endpoint) 33 | 34 | # terminate the deployment 35 | deployment._loop.run_until_complete(deployment._terminate()) 36 | assert utils.eventually_reaches_phase(deployment, Phase.Deleted) 37 | 38 | # recreate the deployment 39 | deployment._loop.run_until_complete(deployment.recreate()) 40 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 41 | assert utils.eventually_condition_gets_updated(deployment, DeploymentAlive, ltt) 42 | 43 | assert deployment.endpoints != {} 44 | assert executor_name in deployment.endpoints 45 | endpoint = deployment.endpoints[executor_name] 46 | assert endpoint.startswith(f'{protocol}s://') 47 | 48 | assert utils.eventually_serve_requests(endpoint) 49 | -------------------------------------------------------------------------------- /jcloud/constants.py: -------------------------------------------------------------------------------- 1 | import os 2 | from enum import Enum 3 | from pathlib import Path 4 | from typing import Dict, Optional 5 | 6 | JCLOUD_API = os.getenv('JCLOUD_API', 'https://api-v2.wolf.jina.ai/') 7 | FLOWS_API = os.path.join(JCLOUD_API, 'flows') 8 | DEPLOYMENTS_API = os.path.join(JCLOUD_API, 'deployments') 9 | JOBS_API = os.path.join(JCLOUD_API, 'jobs') 10 | SECRETS_API = os.path.join(JCLOUD_API, 'secrets') 11 | DASHBOARD_FLOW_URL_MARKDOWN = "[https://cloud.jina.ai/](https://cloud.jina.ai/user/flows?action=detail&id={flow_id}&tab=logs)" 12 | DASHBOARD_FLOW_URL_LINK = "[link=https://cloud.jina.ai/user/flows?action=detail&id={flow_id}&tab=logs]https://cloud.jina.ai/[/link]" 13 | DASHBOARD_DEPLOYMENT_URL_MARKDOWN = "[https://cloud.jina.ai/](https://cloud.jina.ai/user/deployments?action=detail&id={deployment_id}&tab=logs)" 14 | DASHBOARD_DEPLOYMENT_URL_LINK = "[link=https://cloud.jina.ai/user/deployments?action=detail&id={deployment_id}&tab=logs]https://cloud.jina.ai/[/link]" 15 | 16 | 17 | class Phase(str, Enum): 18 | Empty = '' 19 | Pending = 'Pending' 20 | Starting = 'Starting' 21 | Serving = 'Serving' 22 | Failed = 'Failed' 23 | Updating = 'Updating' 24 | Deleted = 'Deleted' 25 | Paused = 'Paused' 26 | 27 | 28 | class CONSTANTS: 29 | DEFAULT_FLOW_FILENAME = 'flow.yml' 30 | DEFAULT_ENV_FILENAME = '.env' 31 | NORMED_FLOWS_DIR = Path('/tmp/flows') 32 | 33 | 34 | class CustomAction(str, Enum): 35 | NoAction = '' 36 | Restart = 'restart' 37 | Pause = 'pause' 38 | Resume = 'resume' 39 | Scale = 'scale' 40 | Recreate = 'recreate' 41 | 42 | 43 | class Resources(str, Enum): 44 | Flow = 'flow' 45 | Job = 'job' 46 | Secret = 'secret' 47 | Deployment = 'deployment' 48 | 49 | 50 | def get_phase_from_response(response: Dict) -> Optional[Phase]: 51 | return Phase(response.get('status', {}).get('phase')) 52 | -------------------------------------------------------------------------------- /.github/workflows/force-release.yml: -------------------------------------------------------------------------------- 1 | name: Manual Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_token: 7 | description: 'Your release token' 8 | required: true 9 | release_reason: 10 | description: 'Short reason for this manual release' 11 | required: true 12 | 13 | jobs: 14 | token-check: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/github-script@v3 18 | with: 19 | script: | 20 | core.setFailed('token are not equivalent!') 21 | if: github.event.inputs.release_token != env.release_token 22 | env: 23 | release_token: ${{ secrets.JCLOUD_RELEASE_TOKEN }} 24 | 25 | regular-release: 26 | needs: token-check 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v2 30 | with: 31 | token: ${{ secrets.JINA_DEV_BOT }} 32 | fetch-depth: 100 # means max contribute history is limited to 100 lines 33 | # submodules: true 34 | - uses: actions/setup-python@v2 35 | with: 36 | python-version: 3.7 37 | - run: | 38 | git fetch --depth=1 origin +refs/tags/*:refs/tags/* 39 | npm install git-release-notes 40 | pip install twine wheel 41 | ./scripts/release.sh final "${{ github.event.inputs.release_reason }}" "${{github.actor}}" 42 | env: 43 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 44 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 45 | JINA_SLACK_WEBHOOK: ${{ secrets.JINA_SLACK_WEBHOOK }} 46 | - if: failure() 47 | run: echo "nothing to release" 48 | - name: bumping master version 49 | uses: ad-m/github-push-action@v0.6.0 50 | with: 51 | github_token: ${{ secrets.JINA_DEV_BOT }} 52 | tags: true 53 | branch: main 54 | -------------------------------------------------------------------------------- /tests/integration/flow/update/test_update_executor_resources.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.flow import CloudFlow 4 | from jcloud.constants import Phase 5 | 6 | from jcloud.helper import get_dict_list_key_path 7 | 8 | from tests.utils import utils 9 | from .. import FlowAlive 10 | 11 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 12 | flow_file = 'base_flow.yml' 13 | resources_flow_file = 'update_resources.yml' 14 | protocol = 'http' 15 | 16 | 17 | def test_update_executor_resources(): 18 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 19 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 20 | 21 | assert flow.endpoints != {} 22 | assert 'gateway' in flow.endpoints 23 | gateway = flow.endpoints['gateway'] 24 | assert gateway.startswith(f'{protocol}s://') 25 | 26 | ltt = utils.get_last_transition_time(flow, FlowAlive) 27 | assert ltt 28 | 29 | assert utils.eventually_serve_requests(gateway) 30 | 31 | flow.path = os.path.join(flows_dir, resources_flow_file) 32 | flow._loop.run_until_complete(flow.update()) 33 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 34 | assert utils.eventually_condition_gets_updated(flow, FlowAlive, ltt) 35 | 36 | assert flow.endpoints != {} 37 | assert 'gateway' in flow.endpoints 38 | gateway = flow.endpoints['gateway'] 39 | assert gateway.startswith(f'{protocol}s://') 40 | 41 | status = flow._loop.run_until_complete(flow.status) 42 | 43 | res = get_dict_list_key_path( 44 | status, ['spec', 'executors', 0, 'jcloud', 'resources'] 45 | ) 46 | assert res is not None 47 | assert 'cpu' in res and res['cpu'] == '0.2' 48 | assert 'memory' in res and res['memory'] == '200M' 49 | 50 | assert utils.eventually_serve_requests(gateway) 51 | -------------------------------------------------------------------------------- /jcloud/parsers/normalize.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from .helper import _chf 3 | from ..constants import Resources 4 | 5 | 6 | def set_normalize_parser(subparser, parser_prog): 7 | if Resources.Flow in parser_prog: 8 | set_flow_normalize_parser(subparser) 9 | # elif Resources.Deployment in parser_prog: 10 | # set_deployment_normalize_parser(subparser) 11 | 12 | 13 | def set_flow_normalize_parser(subparser): 14 | normalize_parser = subparser.add_parser( 15 | 'normalize', 16 | help='Normalize a Flow.', 17 | formatter_class=_chf, 18 | ) 19 | normalize_parser.add_argument( 20 | 'path', 21 | type=Path, 22 | help='The local path to a Jina Flow project directory or yml file.', 23 | ) 24 | normalize_parser.add_argument( 25 | '-o', 26 | '--output', 27 | type=Path, 28 | help='The output path to the normalized Jina Flow yml file.', 29 | ) 30 | normalize_parser.add_argument( 31 | '-v', 32 | '--verbose', 33 | action='store_true', 34 | help='Increase verbosity.', 35 | ) 36 | normalize_parser.set_defaults(verbose=False) 37 | 38 | 39 | def set_deployment_normalize_parser(subparser): 40 | normalize_parser = subparser.add_parser( 41 | 'normalize', 42 | help='Normalize a Deployment.', 43 | formatter_class=_chf, 44 | ) 45 | normalize_parser.add_argument( 46 | 'path', 47 | type=Path, 48 | help='The local path to a Jina Deployment project directory or yml file.', 49 | ) 50 | normalize_parser.add_argument( 51 | '-o', 52 | '--output', 53 | type=Path, 54 | help='The output path to the normalized Jina Deployment yml file.', 55 | ) 56 | normalize_parser.add_argument( 57 | '-v', 58 | '--verbose', 59 | action='store_true', 60 | help='Increase verbosity.', 61 | ) 62 | normalize_parser.set_defaults(verbose=False) 63 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/test_rename_executor.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.deployment import CloudDeployment 4 | 5 | from jcloud.helper import get_dict_list_key_path 6 | from jcloud.constants import Phase 7 | 8 | from tests.utils import utils 9 | from .. import DeploymentAlive 10 | 11 | deployments_dir = os.path.join( 12 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 13 | ) 14 | deployment_file = 'base_deployment.yml' 15 | rename_executor_deployment_file = 'rename_executor.yml' 16 | protocol = 'grpc' 17 | executor_name = 'executor' 18 | 19 | 20 | def test_rename_executor(): 21 | with CloudDeployment( 22 | path=os.path.join(deployments_dir, deployment_file) 23 | ) as deployment: 24 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 25 | 26 | assert deployment.endpoints != {} 27 | assert executor_name in deployment.endpoints 28 | endpoint = deployment.endpoints[executor_name] 29 | assert endpoint.startswith(f'{protocol}s://') 30 | 31 | ltt = utils.get_last_transition_time(deployment, DeploymentAlive) 32 | assert ltt 33 | assert utils.eventually_serve_requests(endpoint) 34 | 35 | deployment.path = os.path.join(deployments_dir, rename_executor_deployment_file) 36 | deployment._loop.run_until_complete(deployment.update()) 37 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 38 | assert utils.eventually_condition_gets_updated(deployment, DeploymentAlive, ltt) 39 | 40 | new_name = 'newsentencizer' 41 | assert deployment.endpoints != {} 42 | assert new_name in deployment.endpoints 43 | endpoint = deployment.endpoints[new_name] 44 | assert endpoint.startswith(f'{protocol}s://') 45 | 46 | status = deployment._loop.run_until_complete(deployment.status) 47 | 48 | assert get_dict_list_key_path(status, ['spec', 'with', 'name']) == new_name 49 | 50 | assert utils.eventually_serve_requests(endpoint) 51 | -------------------------------------------------------------------------------- /jcloud/parsers/logs.py: -------------------------------------------------------------------------------- 1 | from .helper import _chf 2 | from ..constants import Resources 3 | 4 | 5 | def set_logs_resource_parser(subparser, parser_prog): 6 | 7 | if Resources.Flow in parser_prog: 8 | logs_parser = subparser.add_parser( 9 | 'logs', 10 | help='Get logs of a Flow gateway or executor.', 11 | formatter_class=_chf, 12 | ) 13 | _set_logs_flow_parser(logs_parser) 14 | elif Resources.Deployment in parser_prog: 15 | logs_parser = subparser.add_parser( 16 | 'logs', 17 | help='Get logs of a Deployment.', 18 | formatter_class=_chf, 19 | ) 20 | _set_logs_deployment_parser(logs_parser) 21 | else: 22 | logs_parser = subparser.add_parser( 23 | 'logs', 24 | help='Get logs of a Job.', 25 | formatter_class=_chf, 26 | ) 27 | _set_logs_job_parser(logs_parser) 28 | 29 | 30 | def _set_logs_flow_parser(logs_parser): 31 | logs_parser.add_argument( 32 | 'flow', 33 | type=str, 34 | help='The string ID of a Flow.', 35 | ) 36 | 37 | group = logs_parser.add_mutually_exclusive_group(required=True) 38 | 39 | group.add_argument( 40 | '--gateway', 41 | action='store_true', 42 | required=False, 43 | help='Get logs for gateway.', 44 | ) 45 | group.add_argument( 46 | '--executor', 47 | type=str, 48 | required=False, 49 | help='Get logs for executor.', 50 | ) 51 | 52 | 53 | def _set_logs_job_parser(logs_parser): 54 | logs_parser.add_argument( 55 | 'name', 56 | type=str, 57 | help='The name of the Job.', 58 | ) 59 | 60 | logs_parser.add_argument( 61 | 'flow', 62 | type=str, 63 | help='The string ID of a Flow.', 64 | ) 65 | 66 | 67 | def _set_logs_deployment_parser(logs_parser): 68 | logs_parser.add_argument( 69 | 'deployment', 70 | type=str, 71 | help='The string ID of a Deployment.', 72 | ) 73 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/test_update_executor_args.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.deployment import CloudDeployment 4 | from jcloud.constants import Phase 5 | 6 | from jcloud.helper import get_dict_list_key_path 7 | 8 | from tests.utils import utils 9 | from .. import DeploymentAlive 10 | 11 | deployments_dir = os.path.join( 12 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 13 | ) 14 | deployment_file = 'base_deployment.yml' 15 | new_exc_args_deployment_file = 'add_args.yml' 16 | protocol = 'grpc' 17 | executor_name = 'executor' 18 | 19 | 20 | def test_update_executor_args(): 21 | with CloudDeployment( 22 | path=os.path.join(deployments_dir, deployment_file) 23 | ) as deployment: 24 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 25 | 26 | assert deployment.endpoints != {} 27 | assert executor_name in deployment.endpoints 28 | endpoint = deployment.endpoints[executor_name] 29 | assert endpoint.startswith(f'{protocol}s://') 30 | 31 | ltt = utils.get_last_transition_time(deployment, DeploymentAlive) 32 | assert ltt 33 | 34 | assert utils.eventually_serve_requests(endpoint) 35 | 36 | deployment.path = os.path.join(deployments_dir, new_exc_args_deployment_file) 37 | deployment._loop.run_until_complete(deployment.update()) 38 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 39 | assert utils.eventually_condition_gets_updated(deployment, DeploymentAlive, ltt) 40 | 41 | assert deployment.endpoints != {} 42 | assert executor_name in deployment.endpoints 43 | endpoint = deployment.endpoints[executor_name] 44 | assert endpoint.startswith(f'{protocol}s://') 45 | 46 | status = deployment._loop.run_until_complete(deployment.status) 47 | assert ( 48 | get_dict_list_key_path(status, ['spec', 'with', 'uses_with', 'punct_chars']) 49 | == '$PUNCT_CHARS' 50 | ) 51 | 52 | assert utils.eventually_serve_requests(endpoint) 53 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/test_update_executor_image.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.deployment import CloudDeployment 4 | from jcloud.constants import Phase 5 | 6 | from jcloud.helper import get_dict_list_key_path 7 | 8 | from tests.utils import utils 9 | from .. import DeploymentAlive 10 | 11 | 12 | deployments_dir = os.path.join( 13 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 14 | ) 15 | deployment_file = 'base_deployment.yml' 16 | new_exc_image_deployment_file = 'update_image.yml' 17 | protocol = 'grpc' 18 | executor_name = 'executor' 19 | 20 | 21 | def test_update_executor_image(): 22 | with CloudDeployment( 23 | path=os.path.join(deployments_dir, deployment_file) 24 | ) as deployment: 25 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 26 | 27 | assert deployment.endpoints != {} 28 | assert executor_name in deployment.endpoints 29 | endpoint = deployment.endpoints[executor_name] 30 | assert endpoint.startswith(f'{protocol}s://') 31 | 32 | ltt = utils.get_last_transition_time(deployment, DeploymentAlive) 33 | assert ltt 34 | 35 | assert utils.eventually_serve_requests(endpoint) 36 | 37 | deployment.path = os.path.join(deployments_dir, new_exc_image_deployment_file) 38 | deployment._loop.run_until_complete(deployment.update()) 39 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 40 | assert utils.eventually_condition_gets_updated(deployment, DeploymentAlive, ltt) 41 | 42 | assert deployment.endpoints != {} 43 | assert executor_name in deployment.endpoints 44 | endpoint = deployment.endpoints[executor_name] 45 | assert endpoint.startswith(f'{protocol}s://') 46 | 47 | status = deployment._loop.run_until_complete(deployment.status) 48 | 49 | assert ( 50 | get_dict_list_key_path(status, ['spec', 'with', 'uses']) 51 | == 'jinahub+docker://SimpleIndexer' 52 | ) 53 | 54 | assert utils.eventually_serve_requests(endpoint) 55 | -------------------------------------------------------------------------------- /tests/integration/deployment/update/test_update_executor_resources.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jcloud.deployment import CloudDeployment 4 | from jcloud.constants import Phase 5 | 6 | from jcloud.helper import get_dict_list_key_path 7 | 8 | from tests.utils import utils 9 | from .. import DeploymentAlive 10 | 11 | deployments_dir = os.path.join( 12 | os.path.dirname(os.path.abspath(__file__)), 'deployments' 13 | ) 14 | deployment_file = 'base_deployment.yml' 15 | resources_deployment_file = 'update_resources.yml' 16 | protocol = 'grpc' 17 | executor_name = 'executor' 18 | 19 | 20 | def test_update_executor_resources(): 21 | with CloudDeployment( 22 | path=os.path.join(deployments_dir, deployment_file) 23 | ) as deployment: 24 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 25 | 26 | assert deployment.endpoints != {} 27 | assert executor_name in deployment.endpoints 28 | endpoint = deployment.endpoints[executor_name] 29 | assert endpoint.startswith(f'{protocol}s://') 30 | 31 | ltt = utils.get_last_transition_time(deployment, DeploymentAlive) 32 | assert ltt 33 | 34 | assert utils.eventually_serve_requests(endpoint) 35 | 36 | deployment.path = os.path.join(deployments_dir, resources_deployment_file) 37 | deployment._loop.run_until_complete(deployment.update()) 38 | assert utils.eventually_reaches_phase(deployment, Phase.Serving) 39 | assert utils.eventually_condition_gets_updated(deployment, DeploymentAlive, ltt) 40 | 41 | assert deployment.endpoints != {} 42 | assert executor_name in deployment.endpoints 43 | endpoint = deployment.endpoints[executor_name] 44 | assert endpoint.startswith(f'{protocol}s://') 45 | 46 | status = deployment._loop.run_until_complete(deployment.status) 47 | 48 | res = get_dict_list_key_path(status, ['spec', 'jcloud', 'resources']) 49 | assert res is not None 50 | assert 'cpu' in res and res['cpu'] == '0.5' 51 | assert 'memory' in res and res['memory'] == '1G' 52 | 53 | assert utils.eventually_serve_requests(endpoint) 54 | -------------------------------------------------------------------------------- /.github/workflows/force-docs-build.yml: -------------------------------------------------------------------------------- 1 | name: Manual Docs Build 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_token: 7 | description: 'Your release token' 8 | required: true 9 | triggered_by: 10 | description: 'CD | TAG | MANUAL' 11 | required: false 12 | default: MANUAL 13 | 14 | jobs: 15 | token-check: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/github-script@v3 19 | with: 20 | script: | 21 | core.setFailed('token are not equivalent!') 22 | if: github.event.inputs.release_token != env.release_token 23 | env: 24 | release_token: ${{ secrets.JCLOUD_RELEASE_TOKEN }} 25 | 26 | release-docs: 27 | needs: token-check 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v2 31 | with: 32 | fetch-depth: 0 33 | - uses: actions/setup-python@v2 34 | with: 35 | python-version: 3.7 36 | - name: Build doc and push to gh-pages 37 | run: | 38 | git config --local user.email "dev-bot@jina.ai" 39 | git config --local user.name "Jina Dev Bot" 40 | pip install . 41 | mkdir gen-html 42 | cd docs 43 | pip install -r requirements.txt 44 | pip install --pre -U furo 45 | bash makedoc.sh 46 | cd ./_build/dirhtml/ 47 | cp -r ./ ../../../gen-html 48 | cd - # back to ./docs 49 | cd .. 50 | git checkout -f gh-pages 51 | git rm -rf ./docs 52 | mkdir -p docs 53 | cd gen-html 54 | cp -r ./ ../docs 55 | cd ../docs 56 | ls -la 57 | touch .nojekyll 58 | cp 404/index.html 404.html 59 | sed -i 's/href="\.\./href="/' 404.html # fix asset urls that needs to be updated in 404.html 60 | echo jcloud.jina.ai > CNAME 61 | cd .. 62 | git add docs 63 | git status 64 | git commit -m "chore(docs): update docs due to ${{github.event_name}} on ${{github.repository}}" 65 | git push --force origin gh-pages -------------------------------------------------------------------------------- /tests/integration/flow/stateful/test_stateful_flow_grpc.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | import pytest 5 | from jina import Client, Document 6 | 7 | from jcloud.flow import CloudFlow 8 | 9 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'flows') 10 | 11 | 12 | @pytest.mark.skip('unskip once sharing workspace is implemented') 13 | def test_crud_stateful_flow_grpc(): 14 | # This tests 15 | # Index Flow stores data on disk -> terminated 16 | # Query Flow accesses same data using Index Flows workspace to `/search` 17 | protocol = 'grpc' 18 | INDEX_FLOW_NAME = f'simpleindexer-{protocol}-index' 19 | SEARCH_FLOW_NAME = F'simpleindexer-{protocol}-search' 20 | FLOW_FILE_PATH = os.path.join(flows_dir, f'{protocol}-stateful.yml') 21 | 22 | index_docs = [ 23 | Document(text=f'text-{i}', embedding=np.array([i, i + 1, i + 2])) 24 | for i in range(5) 25 | ] 26 | query_doc = index_docs[0] 27 | 28 | with CloudFlow(path=FLOW_FILE_PATH, name=INDEX_FLOW_NAME) as index_flow: 29 | da_index = Client(host=index_flow.gateway).index(inputs=index_docs) 30 | assert da_index.texts == [f'text-{i}' for i in range(5)] 31 | for limit in [3, 5]: 32 | da_search = Client(host=index_flow.gateway).search( 33 | inputs=query_doc, parameters={'limit': limit} 34 | ) 35 | assert len(da_search[0].matches.texts) == limit 36 | assert da_search[0].matches.texts == [f'text-{i}' for i in range(limit)] 37 | 38 | with CloudFlow( 39 | path=FLOW_FILE_PATH, name=SEARCH_FLOW_NAME, workspace_id=index_flow.workspace_id 40 | ) as search_flow: 41 | da_search = Client(host=search_flow.gateway).search(inputs=query_doc) 42 | assert da_search[0].matches.texts == [f'text-{i}' for i in range(5)] 43 | for limit in [3, 5]: 44 | da_search = Client(host=search_flow.gateway).search( 45 | inputs=query_doc, parameters={'limit': limit} 46 | ) 47 | assert len(da_search[0].matches.texts) == limit 48 | assert da_search[0].matches.texts == [f'text-{i}' for i in range(limit)] 49 | -------------------------------------------------------------------------------- /tests/integration/flow/stateful/test_stateful_flow_http.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | import pytest 5 | from jina import Client, Document 6 | 7 | from jcloud.flow import CloudFlow 8 | 9 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'flows') 10 | 11 | 12 | @pytest.mark.skip('unskip once sharing workspace is implemented') 13 | def test_crud_stateful_flow_http(): 14 | # This tests 15 | # Index Flow stores data on disk -> terminated 16 | # Query Flow accesses same data using Index Flows workspace to `/search` 17 | protocol = 'http' 18 | INDEX_FLOW_NAME = f'simpleindexer-{protocol}-index' 19 | SEARCH_FLOW_NAME = F'simpleindexer-{protocol}-search' 20 | FLOW_FILE_PATH = os.path.join(flows_dir, f'{protocol}-stateful.yml') 21 | 22 | index_docs = [ 23 | Document(text=f'text-{i}', embedding=np.array([i, i + 1, i + 2])) 24 | for i in range(5) 25 | ] 26 | query_doc = index_docs[0] 27 | 28 | with CloudFlow(path=FLOW_FILE_PATH, name=INDEX_FLOW_NAME) as index_flow: 29 | da_index = Client(host=index_flow.gateway).index(inputs=index_docs) 30 | assert da_index.texts == [f'text-{i}' for i in range(5)] 31 | for limit in [3, 5]: 32 | da_search = Client(host=index_flow.gateway).search( 33 | inputs=query_doc, parameters={'limit': limit} 34 | ) 35 | assert len(da_search[0].matches.texts) == limit 36 | assert da_search[0].matches.texts == [f'text-{i}' for i in range(limit)] 37 | 38 | with CloudFlow( 39 | path=FLOW_FILE_PATH, name=SEARCH_FLOW_NAME, workspace_id=index_flow.workspace_id 40 | ) as search_flow: 41 | da_search = Client(host=search_flow.gateway).search(inputs=query_doc) 42 | assert da_search[0].matches.texts == [f'text-{i}' for i in range(5)] 43 | for limit in [3, 5]: 44 | da_search = Client(host=search_flow.gateway).search( 45 | inputs=query_doc, parameters={'limit': limit} 46 | ) 47 | assert len(da_search[0].matches.texts) == limit 48 | assert da_search[0].matches.texts == [f'text-{i}' for i in range(limit)] 49 | -------------------------------------------------------------------------------- /tests/integration/flow/stateful/test_stateful_flow_websocket.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | import pytest 5 | from jina import Client, Document 6 | 7 | from jcloud.flow import CloudFlow 8 | 9 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'flows') 10 | 11 | 12 | @pytest.mark.skip('unskip once sharing workspace is implemented') 13 | def test_crud_stateful_flow_websocket(): 14 | # This tests 15 | # Index Flow stores data on disk -> terminated 16 | # Query Flow accesses same data using Index Flows workspace to `/search` 17 | protocol = 'websocket' 18 | INDEX_FLOW_NAME = f'simpleindexer-{protocol}-index' 19 | SEARCH_FLOW_NAME = F'simpleindexer-{protocol}-search' 20 | FLOW_FILE_PATH = os.path.join(flows_dir, f'{protocol}-stateful.yml') 21 | 22 | index_docs = [ 23 | Document(text=f'text-{i}', embedding=np.array([i, i + 1, i + 2])) 24 | for i in range(5) 25 | ] 26 | query_doc = index_docs[0] 27 | 28 | with CloudFlow(path=FLOW_FILE_PATH, name=INDEX_FLOW_NAME) as index_flow: 29 | da_index = Client(host=index_flow.gateway).index(inputs=index_docs) 30 | assert da_index.texts == [f'text-{i}' for i in range(5)] 31 | for limit in [3, 5]: 32 | da_search = Client(host=index_flow.gateway).search( 33 | inputs=query_doc, parameters={'limit': limit} 34 | ) 35 | assert len(da_search[0].matches.texts) == limit 36 | assert da_search[0].matches.texts == [f'text-{i}' for i in range(limit)] 37 | 38 | with CloudFlow( 39 | path=FLOW_FILE_PATH, name=SEARCH_FLOW_NAME, workspace_id=index_flow.workspace_id 40 | ) as search_flow: 41 | da_search = Client(host=search_flow.gateway).search(inputs=query_doc) 42 | assert da_search[0].matches.texts == [f'text-{i}' for i in range(5)] 43 | for limit in [3, 5]: 44 | da_search = Client(host=search_flow.gateway).search( 45 | inputs=query_doc, parameters={'limit': limit} 46 | ) 47 | assert len(da_search[0].matches.texts) == limit 48 | assert da_search[0].matches.texts == [f'text-{i}' for i in range(limit)] 49 | -------------------------------------------------------------------------------- /tests/integration/flow/custom_actions/test_pause_resume.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from jina import Client, Document, DocumentArray 6 | 7 | from jcloud.flow import CloudFlow 8 | from jcloud.constants import Phase 9 | 10 | from tests.utils import utils 11 | from .. import FlowAlive 12 | 13 | flows_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'flows') 14 | flow_file = 'base_flow.yml' 15 | protocol = 'http' 16 | 17 | 18 | def test_pause_resume_flow(): 19 | with CloudFlow(path=os.path.join(flows_dir, flow_file)) as flow: 20 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 21 | 22 | assert flow.endpoints != {} 23 | assert 'gateway' in flow.endpoints 24 | gateway = flow.endpoints['gateway'] 25 | assert gateway.startswith(f'{protocol}s://') 26 | 27 | ltt = utils.get_last_transition_time(flow, FlowAlive) 28 | assert ltt 29 | 30 | assert utils.eventually_serve_requests(gateway) 31 | 32 | # pause the flow 33 | flow._loop.run_until_complete(flow.pause()) 34 | assert utils.eventually_reaches_phase(flow, Phase.Paused) 35 | assert utils.eventually_condition_gets_updated(flow, FlowAlive, ltt) 36 | 37 | assert flow.endpoints != {} 38 | assert 'gateway' in flow.endpoints 39 | gateway = flow.endpoints['gateway'] 40 | assert gateway.startswith(f'{protocol}s://') 41 | 42 | with pytest.raises(ValueError): 43 | da = Client(host=gateway).post( 44 | on='/', 45 | inputs=DocumentArray(Document(text=f'text-{i}') for i in range(50)), 46 | ) 47 | 48 | # resume the flow 49 | ltt = utils.get_last_transition_time(flow, FlowAlive) 50 | assert ltt 51 | 52 | flow._loop.run_until_complete(flow.resume()) 53 | assert utils.eventually_reaches_phase(flow, Phase.Serving) 54 | assert utils.eventually_condition_gets_updated(flow, FlowAlive, ltt) 55 | 56 | assert flow.endpoints != {} 57 | assert 'gateway' in flow.endpoints 58 | gateway = flow.endpoints['gateway'] 59 | assert gateway.startswith(f'{protocol}s://') 60 | 61 | assert utils.eventually_serve_requests(gateway) 62 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | prep-testbed: 10 | if: | 11 | !startsWith(github.event.head_commit.message, 'chore') && 12 | !startsWith(github.event.head_commit.message, 'build: hotfix') && 13 | !endsWith(github.event.head_commit.message, 'reformatted by jina-dev-bot') 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - id: set-matrix 18 | run: | 19 | sudo apt-get install jq 20 | echo "::set-output name=matrix::$(bash scripts/get-all-test-paths.sh unit)" 21 | outputs: 22 | matrix: ${{ steps.set-matrix.outputs.matrix }} 23 | 24 | core-test: 25 | needs: prep-testbed 26 | runs-on: ubuntu-latest 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | python-version: [3.7] 31 | test-path: ${{fromJson(needs.prep-testbed.outputs.matrix)}} 32 | steps: 33 | - uses: actions/checkout@v2 34 | - name: Set up Python ${{ matrix.python-version }} 35 | uses: actions/setup-python@v2 36 | with: 37 | python-version: ${{ matrix.python-version }} 38 | - name: Prepare environment 39 | run: | 40 | python -m pip install --upgrade pip 41 | python -m pip install wheel 42 | pip install --no-cache-dir ".[full,test]" 43 | sudo apt-get install libsndfile1 44 | - name: Test 45 | id: test 46 | env: 47 | JINA_AUTH_TOKEN: ${{ secrets.CI_TOKEN }} 48 | run: | 49 | pytest --suppress-no-test-exit-code --cov=jcloud --cov-report=xml \ 50 | -v -s --log-cli-level=DEBUG -m "not gpu" ${{ matrix.test-path }} 51 | echo "::set-output name=codecov_flag::jcloud" 52 | timeout-minutes: 30 53 | 54 | prerelease: 55 | needs: core-test 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/checkout@v2 59 | with: 60 | fetch-depth: 100 61 | - name: Pre-release (.devN) 62 | run: | 63 | git fetch --depth=1 origin +refs/tags/*:refs/tags/* 64 | pip install twine wheel 65 | ./scripts/release.sh 66 | env: 67 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 68 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 69 | JINA_SLACK_WEBHOOK: ${{ secrets.JINA_SLACK_WEBHOOK }} 70 | -------------------------------------------------------------------------------- /jcloud/parsers/create.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | from pathlib import Path 4 | 5 | from .helper import _chf 6 | from ..constants import Resources 7 | 8 | 9 | def set_create_resource_parser(subparser, resource): 10 | 11 | create_parser = subparser.add_parser( 12 | 'create', 13 | help=f'Create a {resource.title()}.', 14 | formatter_class=_chf, 15 | ) 16 | 17 | create_parser.add_argument( 18 | 'name', 19 | type=str, 20 | help=f'The name of the {resource.title()}.', 21 | ) 22 | 23 | create_parser.add_argument( 24 | 'flow', 25 | type=str, 26 | help='The string ID of the Flow.', 27 | ) 28 | if resource == Resources.Job: 29 | _set_job_create_parser(create_parser) 30 | else: 31 | _set_secret_create_parser(create_parser) 32 | 33 | 34 | def _set_job_create_parser(create_parser): 35 | create_parser.add_argument( 36 | 'image', 37 | type=str, 38 | help='The image the Job will use.', 39 | ) 40 | 41 | create_parser.add_argument( 42 | 'entrypoint', 43 | type=ast.literal_eval, 44 | help='The command to be added to the image\'s entrypoint.', 45 | ) 46 | 47 | create_parser.add_argument( 48 | '--timeout', 49 | type=int, 50 | default=600, 51 | help='Duration the Job will be active before termination in seconds.', 52 | ) 53 | 54 | create_parser.add_argument( 55 | '--backofflimit', 56 | type=int, 57 | help='Number of retries before Job is marked as failed.', 58 | ) 59 | 60 | create_parser.add_argument( 61 | '--secrets', 62 | type=ast.literal_eval, 63 | help='Literal value following the format "{\'ENV_VAR\': {\'name\': \'secret-name\', \'key\': \'secret-key\'}}"', 64 | ) 65 | 66 | 67 | def _set_secret_create_parser(create_parser): 68 | create_parser.add_argument( 69 | '--from-literal', 70 | type=ast.literal_eval, 71 | help='Literal Secret value. Should follow the format "{\'env1\':\'value\'},\'env2\':\'value2\'}".', 72 | ) 73 | 74 | create_parser.add_argument( 75 | '--update', 76 | action='store_true', 77 | help='Whether to update the flow spec after create the Secret', 78 | ) 79 | 80 | create_parser.add_argument( 81 | '--path', 82 | type=Path, 83 | help='The path of flow yaml spec file', 84 | ) 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Simplify deploying and hosting Jina projects on Jina Cloud
10 |