graph-agents-cli 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- graph_agents_cli/__init__.py +26 -0
- graph_agents_cli/_api_policy.py +2145 -0
- graph_agents_cli/_approvals.py +400 -0
- graph_agents_cli/_build.py +186 -0
- graph_agents_cli/_build_info.json +7 -0
- graph_agents_cli/_chat_client.py +462 -0
- graph_agents_cli/_click.py +157 -0
- graph_agents_cli/_defaults.py +139 -0
- graph_agents_cli/_experiments.py +64 -0
- graph_agents_cli/_http.py +192 -0
- graph_agents_cli/_output.py +83 -0
- graph_agents_cli/_project.py +462 -0
- graph_agents_cli/_remote.py +220 -0
- graph_agents_cli/_response_schema.py +264 -0
- graph_agents_cli/_runner.py +319 -0
- graph_agents_cli/_skills_check.py +274 -0
- graph_agents_cli/_tools.py +189 -0
- graph_agents_cli/_trust.py +66 -0
- graph_agents_cli/api/__init__.py +15 -0
- graph_agents_cli/api/_changes.py +506 -0
- graph_agents_cli/api/_files.py +658 -0
- graph_agents_cli/api/cmd_api.py +2480 -0
- graph_agents_cli/deploy/__init__.py +15 -0
- graph_agents_cli/deploy/_config.py +171 -0
- graph_agents_cli/deploy/_image.py +128 -0
- graph_agents_cli/deploy/_kube.py +286 -0
- graph_agents_cli/deploy/_modes.py +234 -0
- graph_agents_cli/deploy/_preflight.py +370 -0
- graph_agents_cli/deploy/_values.py +168 -0
- graph_agents_cli/deploy/cmd_deploy.py +1866 -0
- graph_agents_cli/deploy/gitops.py +562 -0
- graph_agents_cli/deploy/local_load.py +273 -0
- graph_agents_cli/dev/__init__.py +13 -0
- graph_agents_cli/dev/cmd_build.py +131 -0
- graph_agents_cli/dev/cmd_install.py +78 -0
- graph_agents_cli/dev/cmd_lint.py +119 -0
- graph_agents_cli/dev/cmd_playground.py +297 -0
- graph_agents_cli/dev/policy_check.py +1287 -0
- graph_agents_cli/eval/__init__.py +22 -0
- graph_agents_cli/eval/_client.py +670 -0
- graph_agents_cli/eval/_common.py +177 -0
- graph_agents_cli/eval/_judge.py +168 -0
- graph_agents_cli/eval/_judge_runner.py +238 -0
- graph_agents_cli/eval/_paths.py +212 -0
- graph_agents_cli/eval/checks.py +581 -0
- graph_agents_cli/eval/cmd_analyze.py +278 -0
- graph_agents_cli/eval/cmd_compare.py +284 -0
- graph_agents_cli/eval/cmd_eval_group.py +80 -0
- graph_agents_cli/eval/cmd_generate.py +558 -0
- graph_agents_cli/eval/cmd_grade.py +466 -0
- graph_agents_cli/eval/cmd_metric.py +156 -0
- graph_agents_cli/eval/cmd_run.py +370 -0
- graph_agents_cli/eval/cmd_submit.py +400 -0
- graph_agents_cli/eval/config.py +435 -0
- graph_agents_cli/eval/dataset.py +350 -0
- graph_agents_cli/eval/gate.py +420 -0
- graph_agents_cli/eval/transcript.py +192 -0
- graph_agents_cli/extension/__init__.py +13 -0
- graph_agents_cli/extension/_compat.py +86 -0
- graph_agents_cli/extension/_loader.py +293 -0
- graph_agents_cli/extension/_manifest.py +135 -0
- graph_agents_cli/extension/_overrides.py +195 -0
- graph_agents_cli/extension/_paths.py +91 -0
- graph_agents_cli/extension/_refs.py +193 -0
- graph_agents_cli/extension/_resolver.py +453 -0
- graph_agents_cli/extension/_schema.py +106 -0
- graph_agents_cli/extension/_spec.py +253 -0
- graph_agents_cli/extension/_sync.py +102 -0
- graph_agents_cli/extension/_trust.py +58 -0
- graph_agents_cli/extension/cmd_extension_add.py +259 -0
- graph_agents_cli/extension/cmd_extension_group.py +57 -0
- graph_agents_cli/extension/cmd_extension_list.py +56 -0
- graph_agents_cli/extension/cmd_extension_remove.py +61 -0
- graph_agents_cli/extension/cmd_extension_update.py +195 -0
- graph_agents_cli/info/__init__.py +13 -0
- graph_agents_cli/info/cmd_info.py +222 -0
- graph_agents_cli/infra/__init__.py +15 -0
- graph_agents_cli/infra/checks.py +1169 -0
- graph_agents_cli/infra/cmd_infra.py +103 -0
- graph_agents_cli/main.py +591 -0
- graph_agents_cli/peer/__init__.py +15 -0
- graph_agents_cli/peer/_generate.py +254 -0
- graph_agents_cli/peer/cmd_peer.py +1151 -0
- graph_agents_cli/run/__init__.py +13 -0
- graph_agents_cli/run/_local_server.py +1157 -0
- graph_agents_cli/run/_signals.py +141 -0
- graph_agents_cli/run/cmd_approvals.py +530 -0
- graph_agents_cli/run/cmd_run.py +1421 -0
- graph_agents_cli/scaffold/__init__.py +19 -0
- graph_agents_cli/scaffold/agents/README.md +24 -0
- graph_agents_cli/scaffold/agents/empty_py/.template/templateconfig.yaml +22 -0
- graph_agents_cli/scaffold/agents/langgraph/.env.example +292 -0
- graph_agents_cli/scaffold/agents/langgraph/.template/templateconfig.yaml +28 -0
- graph_agents_cli/scaffold/agents/langgraph/Dockerfile +59 -0
- graph_agents_cli/scaffold/agents/langgraph/Dockerfile.langgraph-server +59 -0
- graph_agents_cli/scaffold/agents/langgraph/README.md +571 -0
- graph_agents_cli/scaffold/agents/langgraph/api-policy.yaml +60 -0
- graph_agents_cli/scaffold/agents/langgraph/app/__init__.py +20 -0
- graph_agents_cli/scaffold/agents/langgraph/app/agent.py +174 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/__init__.py +15 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a.py +2162 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a_client.py +1167 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/api_client.py +4220 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/approvals.py +1349 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/auth.py +1986 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/chat.py +2962 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/checkpointer.py +432 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/content.py +569 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/db.py +580 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/limits.py +203 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/metrics.py +231 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/middleware.py +361 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/model.py +611 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/playground.py +230 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/run_locks.py +459 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/structured.py +755 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/telemetry.py +681 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/threads.py +493 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/token_exchange.py +959 -0
- graph_agents_cli/scaffold/agents/langgraph/app/fast_api_app.py +770 -0
- graph_agents_cli/scaffold/agents/langgraph/app/policies/__init__.py +55 -0
- graph_agents_cli/scaffold/agents/langgraph/app/policies/custom.py +97 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/__init__.py +46 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/example_api.py +92 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/weather.py +33 -0
- graph_agents_cli/scaffold/agents/langgraph/langgraph.json +14 -0
- graph_agents_cli/scaffold/agents/langgraph/pyproject.toml +78 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/conftest.py +376 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/eval/datasets/basic-dataset.json +53 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/eval/eval_config.yaml +32 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/approval_graph.py +137 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_issuer.py +216 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_openai.py +357 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_outcomes.py +569 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_relay.py +479 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_api_surface.py +812 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals.py +1367 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals_server.py +794 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor.py +497 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor_server.py +247 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_history_repair.py +278 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_model_apis.py +242 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_postgres.py +637 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_resilience_postgres.py +770 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_runtime_guardrails.py +854 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_e2e.py +340 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_runtime.py +989 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_answers.py +584 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_server.py +222 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_token_exchange_issuer.py +650 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/.results/.placeholder +0 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/README.md +22 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/conftest.py +21 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/load_test.py +81 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_client.py +824 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_scoping.py +724 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client.py +1214 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client_hardening.py +716 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_policy_rpc.py +767 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_approval_ledger.py +1536 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_fake_model.py +115 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_jwt_policy.py +991 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_limits.py +310 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging.py +148 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging_hardening.py +271 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_policy.py +378 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_resilience.py +610 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_server_auth.py +702 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_structured.py +673 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_telemetry.py +404 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_thread_listing.py +255 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_threads.py +268 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_token_exchange.py +1320 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_untrusted_content.py +393 -0
- graph_agents_cli/scaffold/agents/langgraph/uv-fastapi.lock +2084 -0
- graph_agents_cli/scaffold/agents/langgraph/uv-langgraph-server.lock +2106 -0
- graph_agents_cli/scaffold/agents/langgraph/{{cookiecutter.agent_guidance_filename}} +129 -0
- graph_agents_cli/scaffold/base_templates/_shared/graph-agents-cli-manifest.yaml +36 -0
- graph_agents_cli/scaffold/base_templates/python/.dockerignore +32 -0
- graph_agents_cli/scaffold/base_templates/python/.github/CODEOWNERS +30 -0
- graph_agents_cli/scaffold/base_templates/python/.github/agent.env +7 -0
- graph_agents_cli/scaffold/base_templates/python/.github/workflows/pr_checks.yaml +214 -0
- graph_agents_cli/scaffold/base_templates/python/.gitignore +209 -0
- graph_agents_cli/scaffold/base_templates/python/tests/unit/test_dummy.py +23 -0
- graph_agents_cli/scaffold/base_templates/python/{{cookiecutter.agent_guidance_filename}} +35 -0
- graph_agents_cli/scaffold/cmd_scaffold_group.py +49 -0
- graph_agents_cli/scaffold/commands/__init__.py +13 -0
- graph_agents_cli/scaffold/commands/create.py +1424 -0
- graph_agents_cli/scaffold/commands/enhance.py +1652 -0
- graph_agents_cli/scaffold/commands/upgrade.py +570 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/agent.env +12 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/promote-to-prod.yaml +371 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/staging.yaml +450 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-dev.yaml +43 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-prod.yaml +41 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-staging.yaml +43 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/.helmignore +14 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/Chart.yaml +21 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/examples/networkpolicy.yaml +103 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/NOTES.txt +48 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/_helpers.tpl +189 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/certificate.yaml +15 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/configmap.yaml +10 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/deployment.yaml +199 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/hpa.yaml +22 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/httproute.yaml +30 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/ingress.yaml +39 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/networkpolicy.yaml +48 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/pdb.yaml +13 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/postgresql-secret.yaml +37 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/service.yaml +15 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/serviceaccount.yaml +13 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/servicemonitor.yaml +42 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-dev.yaml +22 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-prod.yaml +45 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-staging.yaml +29 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values.yaml +396 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/tests/integration/test_chart.py +269 -0
- graph_agents_cli/scaffold/deployment_targets/none/README.md +5 -0
- graph_agents_cli/scaffold/deployment_targets/none/python/README.md +6 -0
- graph_agents_cli/scaffold/utils/__init__.py +13 -0
- graph_agents_cli/scaffold/utils/backup.py +212 -0
- graph_agents_cli/scaffold/utils/build_record.py +257 -0
- graph_agents_cli/scaffold/utils/cli_options.py +184 -0
- graph_agents_cli/scaffold/utils/fs.py +83 -0
- graph_agents_cli/scaffold/utils/generate_locks.py +214 -0
- graph_agents_cli/scaffold/utils/generation_metadata.py +88 -0
- graph_agents_cli/scaffold/utils/keyedit.py +768 -0
- graph_agents_cli/scaffold/utils/keymerge.py +537 -0
- graph_agents_cli/scaffold/utils/language.py +138 -0
- graph_agents_cli/scaffold/utils/lock_utils.py +94 -0
- graph_agents_cli/scaffold/utils/logging.py +77 -0
- graph_agents_cli/scaffold/utils/manifest.py +292 -0
- graph_agents_cli/scaffold/utils/merge.py +970 -0
- graph_agents_cli/scaffold/utils/merge3.py +216 -0
- graph_agents_cli/scaffold/utils/openapi_seed.py +199 -0
- graph_agents_cli/scaffold/utils/remote_template.py +376 -0
- graph_agents_cli/scaffold/utils/template.py +1352 -0
- graph_agents_cli/scaffold/utils/upgrade.py +894 -0
- graph_agents_cli/scaffold/utils/version.py +438 -0
- graph_agents_cli/secrets/__init__.py +15 -0
- graph_agents_cli/secrets/_apply.py +954 -0
- graph_agents_cli/secrets/_required.py +188 -0
- graph_agents_cli/secrets/cmd_secrets.py +211 -0
- graph_agents_cli/setup/__init__.py +13 -0
- graph_agents_cli/setup/_antigravity.py +221 -0
- graph_agents_cli/setup/cmd_auth.py +1030 -0
- graph_agents_cli/setup/cmd_dev_token.py +513 -0
- graph_agents_cli/setup/cmd_setup.py +428 -0
- graph_agents_cli/setup/cmd_update.py +140 -0
- graph_agents_cli/skills/__init__.py +13 -0
- graph_agents_cli/skills/_bundle.py +65 -0
- graph_agents_cli/skills/data/README.md +19 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/SKILL.md +357 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/github-settings.md +113 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/gitops.md +137 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/kubernetes.md +315 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/secrets.md +160 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/SKILL.md +303 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/references/dataset_schema.md +282 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/references/metrics-guide.md +143 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/SKILL.md +659 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langchain-models.md +124 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langgraph.md +235 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/template-contract.md +477 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/SKILL.md +231 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/references/langsmith.md +46 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/references/otel.md +59 -0
- graph_agents_cli/skills/data/graph-agents-cli-scaffold/SKILL.md +414 -0
- graph_agents_cli/skills/data/graph-agents-cli-scaffold/references/flags.md +134 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/SKILL.md +478 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/brainstorming.md +118 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/commands.md +419 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/extension.md +156 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/internals.md +67 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/spec-template.md +56 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/terminology.md +119 -0
- graph_agents_cli/system/__init__.py +15 -0
- graph_agents_cli/system/_apply.py +519 -0
- graph_agents_cli/system/_checks.py +1023 -0
- graph_agents_cli/system/_deploy.py +215 -0
- graph_agents_cli/system/_model.py +363 -0
- graph_agents_cli/system/_system.py +664 -0
- graph_agents_cli/system/_views.py +208 -0
- graph_agents_cli/system/cmd_system.py +423 -0
- graph_agents_cli-0.3.1.dist-info/METADATA +162 -0
- graph_agents_cli-0.3.1.dist-info/RECORD +291 -0
- graph_agents_cli-0.3.1.dist-info/WHEEL +4 -0
- graph_agents_cli-0.3.1.dist-info/entry_points.txt +2 -0
- graph_agents_cli-0.3.1.dist-info/licenses/LICENSE +201 -0
- graph_agents_cli-0.3.1.dist-info/licenses/NOTICE +19 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2026 graph-agents-cli contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""graph-agents-cli deploy commands."""
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Copyright 2026 graph-agents-cli contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Deployment settings read from the project manifest.
|
|
15
|
+
|
|
16
|
+
Adapts ``ProjectConfig`` (the manifest's field names) to the small set of
|
|
17
|
+
values deploy, secrets, and infra need. Attributes are read defensively so the
|
|
18
|
+
commands work while ``_project.py`` is being finalised; every fallback follows
|
|
19
|
+
the manifest schema and the CLI's defaults.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from graph_agents_cli import _project
|
|
30
|
+
from graph_agents_cli._defaults import (
|
|
31
|
+
ENVIRONMENTS,
|
|
32
|
+
auth_policy_implemented_default,
|
|
33
|
+
default_secret_keys,
|
|
34
|
+
normalize_auth_policy,
|
|
35
|
+
)
|
|
36
|
+
from graph_agents_cli.deploy._kube import ConfigError, Target
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class DeploySettings:
|
|
41
|
+
"""Everything deploy/secrets/infra need from the manifest."""
|
|
42
|
+
|
|
43
|
+
project_name: str
|
|
44
|
+
deployment_target: str = "kubernetes"
|
|
45
|
+
runtime: str = "fastapi"
|
|
46
|
+
model_provider: str = "openai"
|
|
47
|
+
registry: str = ""
|
|
48
|
+
cd: str = "skip"
|
|
49
|
+
auth_policy: str = "shared-bearer"
|
|
50
|
+
auth_policy_implemented: bool = True
|
|
51
|
+
environments: dict[str, dict[str, str]] = field(default_factory=dict)
|
|
52
|
+
secret_keys: list[str] = field(default_factory=list)
|
|
53
|
+
secrets_owner: str = ""
|
|
54
|
+
|
|
55
|
+
# -- derived -------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def release(self) -> str:
|
|
59
|
+
return self.project_name
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def secret_name(self) -> str:
|
|
63
|
+
return f"{self.project_name}-app"
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def metrics_secret_name(self) -> str:
|
|
67
|
+
"""The Secret holding only ``METRICS_TOKEN``, for the Prometheus ServiceMonitor."""
|
|
68
|
+
return f"{self.project_name}-metrics"
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def chart_dir(self) -> Path:
|
|
72
|
+
return Path("deployment") / "helm" / self.project_name
|
|
73
|
+
|
|
74
|
+
def values_file(self, env: str) -> Path:
|
|
75
|
+
return self.chart_dir / f"values-{env}.yaml"
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def image_repository(self) -> str:
|
|
79
|
+
if not self.registry:
|
|
80
|
+
raise ConfigError(
|
|
81
|
+
"No registry is configured (create_params.registry is empty).\n"
|
|
82
|
+
" Set it in graph-agents-cli-manifest.yaml, e.g. registry: ghcr.io/my-org"
|
|
83
|
+
)
|
|
84
|
+
return f"{self.registry.rstrip('/')}/{self.project_name}"
|
|
85
|
+
|
|
86
|
+
def environment(self, env: str) -> dict[str, str]:
|
|
87
|
+
if env not in self.environments:
|
|
88
|
+
known = ", ".join(sorted(self.environments)) or "none"
|
|
89
|
+
raise ConfigError(
|
|
90
|
+
f"Unknown environment {env!r}. Environments in the manifest: {known}."
|
|
91
|
+
)
|
|
92
|
+
return self.environments[env]
|
|
93
|
+
|
|
94
|
+
def target(self, env: str) -> Target:
|
|
95
|
+
spec = self.environment(env)
|
|
96
|
+
namespace = spec.get("namespace") or f"{self.project_name}-{env}"
|
|
97
|
+
return Target(context=spec.get("context") or None, namespace=namespace)
|
|
98
|
+
|
|
99
|
+
# -- construction --------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def from_project(cls, cfg: Any) -> DeploySettings:
|
|
103
|
+
cp = getattr(cfg, "create_params", None)
|
|
104
|
+
cp = dict(cp) if isinstance(cp, Mapping) else {}
|
|
105
|
+
|
|
106
|
+
def pick(attr: str, default: Any) -> Any:
|
|
107
|
+
value = getattr(cfg, attr, None)
|
|
108
|
+
if value is None:
|
|
109
|
+
value = cp.get(attr)
|
|
110
|
+
return default if value is None else value
|
|
111
|
+
|
|
112
|
+
name = getattr(cfg, "project_name", "") or ""
|
|
113
|
+
if not name:
|
|
114
|
+
raise ConfigError("The manifest has no project name.")
|
|
115
|
+
deployment_target = pick("deployment_target", "none")
|
|
116
|
+
if deployment_target != "kubernetes":
|
|
117
|
+
raise ConfigError(
|
|
118
|
+
f"This project has deployment_target: {deployment_target}; nothing to deploy.\n"
|
|
119
|
+
" Re-scaffold with --deployment-target kubernetes to add a Helm chart."
|
|
120
|
+
)
|
|
121
|
+
runtime = pick("runtime", "fastapi")
|
|
122
|
+
model_provider = pick("model_provider", "openai")
|
|
123
|
+
auth_policy = normalize_auth_policy(pick("auth_policy", "shared-bearer"), warn=False)
|
|
124
|
+
implemented = pick("auth_policy_implemented", None)
|
|
125
|
+
if implemented is None:
|
|
126
|
+
implemented = auth_policy_implemented_default(auth_policy)
|
|
127
|
+
|
|
128
|
+
environments: dict[str, dict[str, str]] = {}
|
|
129
|
+
raw_envs = getattr(cfg, "environments", None)
|
|
130
|
+
if isinstance(raw_envs, Mapping):
|
|
131
|
+
for env, spec in raw_envs.items():
|
|
132
|
+
spec = spec if isinstance(spec, Mapping) else {}
|
|
133
|
+
environments[str(env)] = {
|
|
134
|
+
"context": str(spec.get("context") or ""),
|
|
135
|
+
"namespace": str(spec.get("namespace") or f"{name}-{env}"),
|
|
136
|
+
}
|
|
137
|
+
if not environments:
|
|
138
|
+
environments = {
|
|
139
|
+
env: {"context": "", "namespace": f"{name}-{env}"} for env in ENVIRONMENTS
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
secret_keys = getattr(cfg, "secret_keys", None)
|
|
143
|
+
owner = ""
|
|
144
|
+
raw_secrets = getattr(cfg, "secrets", None)
|
|
145
|
+
if isinstance(raw_secrets, Mapping):
|
|
146
|
+
if secret_keys is None:
|
|
147
|
+
secret_keys = raw_secrets.get("keys")
|
|
148
|
+
owner = str(raw_secrets.get("owner") or "")
|
|
149
|
+
if secret_keys is None:
|
|
150
|
+
secret_keys = default_secret_keys(model_provider, runtime, auth_policy=auth_policy)
|
|
151
|
+
owner = getattr(cfg, "secrets_owner", None) or owner
|
|
152
|
+
|
|
153
|
+
return cls(
|
|
154
|
+
project_name=name,
|
|
155
|
+
deployment_target=deployment_target,
|
|
156
|
+
runtime=runtime,
|
|
157
|
+
model_provider=model_provider,
|
|
158
|
+
registry=str(pick("registry", "") or ""),
|
|
159
|
+
cd=str(pick("cd", "skip") or "skip"),
|
|
160
|
+
auth_policy=auth_policy,
|
|
161
|
+
auth_policy_implemented=bool(implemented),
|
|
162
|
+
environments=environments,
|
|
163
|
+
secret_keys=[str(k) for k in secret_keys],
|
|
164
|
+
secrets_owner=owner,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def load_settings() -> DeploySettings:
|
|
169
|
+
"""chdir to the project root and read the manifest into ``DeploySettings``."""
|
|
170
|
+
_project.chdir_project_root()
|
|
171
|
+
return DeploySettings.from_project(_project.read_project_config())
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Copyright 2026 graph-agents-cli contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Image reference checks run before ``docker build``, ``docker push`` or ``helm``.
|
|
15
|
+
|
|
16
|
+
``create`` writes the placeholder registry ``ghcr.io/CHANGE-ME`` when it finds
|
|
17
|
+
no git remote. Docker rejects it (a repository path must be lowercase) only
|
|
18
|
+
after the build starts, with a parser error that does not say which setting to
|
|
19
|
+
change. These helpers catch the placeholder and any other malformed reference
|
|
20
|
+
up front so ``build`` and ``deploy`` stop with a configuration error (exit 3)
|
|
21
|
+
that names the setting.
|
|
22
|
+
|
|
23
|
+
The grammar follows the distribution reference format Docker uses: an optional
|
|
24
|
+
registry host (with port), lowercase path components separated by ``/``, and a
|
|
25
|
+
tag of at most 128 word characters, dots and dashes.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import re
|
|
31
|
+
|
|
32
|
+
from graph_agents_cli._defaults import REGISTRY_FIX_COMMAND, REGISTRY_FIX_EFFECT
|
|
33
|
+
|
|
34
|
+
PLACEHOLDER = "CHANGE-ME"
|
|
35
|
+
|
|
36
|
+
_DOMAIN_COMPONENT = r"(?:[A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])"
|
|
37
|
+
_DOMAIN_RE = re.compile(
|
|
38
|
+
rf"^(?:{_DOMAIN_COMPONENT}(?:\.{_DOMAIN_COMPONENT})*|\[[0-9A-Fa-f:.]+\])(?::[0-9]+)?$"
|
|
39
|
+
)
|
|
40
|
+
_PATH_COMPONENT_RE = re.compile(r"^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$")
|
|
41
|
+
_TAG_RE = re.compile(r"^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$")
|
|
42
|
+
MAX_NAME_LENGTH = 255
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def has_placeholder(value: str | None) -> bool:
|
|
46
|
+
"""True when ``value`` still carries the scaffold's ``CHANGE-ME`` placeholder."""
|
|
47
|
+
return PLACEHOLDER.lower() in (value or "").lower()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _split_domain(name: str) -> tuple[str | None, str]:
|
|
51
|
+
"""``(domain, path)`` using Docker's rule for what counts as a registry host.
|
|
52
|
+
|
|
53
|
+
The first component is a host when it contains ``.`` or ``:``, is
|
|
54
|
+
``localhost``, or has an upper-case letter; otherwise the whole name is a
|
|
55
|
+
path on the default registry.
|
|
56
|
+
"""
|
|
57
|
+
first, sep, rest = name.partition("/")
|
|
58
|
+
if not sep:
|
|
59
|
+
return None, name
|
|
60
|
+
if "." in first or ":" in first or first == "localhost" or first.lower() != first:
|
|
61
|
+
return first, rest
|
|
62
|
+
return None, name
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def repository_problem(repository: str) -> str | None:
|
|
66
|
+
"""Why ``repository`` (``[host[:port]/]path``, no tag) is not a valid image name."""
|
|
67
|
+
if not repository:
|
|
68
|
+
return "the repository is empty"
|
|
69
|
+
if len(repository) > MAX_NAME_LENGTH:
|
|
70
|
+
return f"the repository name is longer than {MAX_NAME_LENGTH} characters"
|
|
71
|
+
domain, path = _split_domain(repository)
|
|
72
|
+
if domain is not None and not _DOMAIN_RE.match(domain):
|
|
73
|
+
return f"{domain!r} is not a valid registry host"
|
|
74
|
+
for component in path.split("/"):
|
|
75
|
+
if not component:
|
|
76
|
+
return "the repository path has an empty component (a doubled or trailing '/')"
|
|
77
|
+
if not _PATH_COMPONENT_RE.match(component):
|
|
78
|
+
if component.lower() != component:
|
|
79
|
+
return f"the repository path component {component!r} must be lowercase"
|
|
80
|
+
return (
|
|
81
|
+
f"the repository path component {component!r} may only hold lowercase letters, "
|
|
82
|
+
"digits and single '.', '_' or '-' separators"
|
|
83
|
+
)
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def tag_problem(tag: str) -> str | None:
|
|
88
|
+
"""Why ``tag`` is not a valid image tag (``None`` when it is)."""
|
|
89
|
+
if not _TAG_RE.match(tag or ""):
|
|
90
|
+
return (
|
|
91
|
+
f"the tag {tag!r} must start with a letter, digit or '_' and hold at most 128 "
|
|
92
|
+
"letters, digits, '_', '.' or '-'"
|
|
93
|
+
)
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def placeholder_message(registry: str) -> str:
|
|
98
|
+
return (
|
|
99
|
+
f"The image registry is still the placeholder {registry!r}.\n"
|
|
100
|
+
f" Run `{REGISTRY_FIX_COMMAND}` ({REGISTRY_FIX_EFFECT}), or set "
|
|
101
|
+
"create_params.registry in graph-agents-cli-manifest.yaml to your registry (for "
|
|
102
|
+
"example ghcr.io/<org>) by hand; `build` also takes --registry."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def reference_problem(
|
|
107
|
+
repository: str, tag: str | None, *, registry: str | None = None
|
|
108
|
+
) -> str | None:
|
|
109
|
+
"""A user-facing reason the image ``repository[:tag]`` cannot be built or pushed.
|
|
110
|
+
|
|
111
|
+
``registry`` is the configured registry the repository was derived from; a
|
|
112
|
+
placeholder there is reported as such (it names the setting to change)
|
|
113
|
+
rather than as a grammar error.
|
|
114
|
+
"""
|
|
115
|
+
if has_placeholder(registry):
|
|
116
|
+
return placeholder_message(registry or "")
|
|
117
|
+
if has_placeholder(repository):
|
|
118
|
+
return (
|
|
119
|
+
f"The image reference {repository!r} still holds the {PLACEHOLDER} placeholder; "
|
|
120
|
+
"pass the real image."
|
|
121
|
+
)
|
|
122
|
+
problem = repository_problem(repository)
|
|
123
|
+
if problem is None and tag is not None:
|
|
124
|
+
problem = tag_problem(tag)
|
|
125
|
+
if problem is None:
|
|
126
|
+
return None
|
|
127
|
+
ref = f"{repository}:{tag}" if tag is not None else repository
|
|
128
|
+
return f"{ref!r} is not a valid image reference: {problem}."
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# Copyright 2026 graph-agents-cli contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""kubectl and helm wrappers used by deploy, secrets, and infra check.
|
|
15
|
+
|
|
16
|
+
Every subprocess goes through ``graph_agents_cli._runner.run_resolved`` (looked
|
|
17
|
+
up at call time so tests can monkeypatch it). Nothing here imports a Kubernetes
|
|
18
|
+
client; the CLI shells out to ``kubectl`` and ``helm``.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import shlex
|
|
26
|
+
import subprocess
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
import click
|
|
31
|
+
|
|
32
|
+
from graph_agents_cli import _runner, _tools
|
|
33
|
+
from graph_agents_cli._output import Console
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DeployError(click.ClickException):
|
|
37
|
+
"""Base for deploy/secrets/infra failures; ``exit_code``: 1 refused, 2 tool failed, 3 config."""
|
|
38
|
+
|
|
39
|
+
exit_code = 1
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Refused(DeployError):
|
|
43
|
+
"""The operation was refused by policy or mode (exit 1)."""
|
|
44
|
+
|
|
45
|
+
exit_code = 1
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ToolFailed(DeployError):
|
|
49
|
+
"""helm/kubectl/docker/git/gh returned non-zero or is missing (exit 2)."""
|
|
50
|
+
|
|
51
|
+
exit_code = 2
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ConfigError(DeployError):
|
|
55
|
+
"""The project or environment is misconfigured (exit 3)."""
|
|
56
|
+
|
|
57
|
+
exit_code = 3
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class Target:
|
|
62
|
+
"""Where a command is aimed: a kube context (``None`` = current) and a namespace."""
|
|
63
|
+
|
|
64
|
+
context: str | None
|
|
65
|
+
namespace: str
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def format_cmd(cmd: list[str]) -> str:
|
|
69
|
+
return _runner.redact_cmd(list(cmd))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def echo_cmd(cmd: list[str], *, dry_run: bool = False, console: Console | None = None) -> None:
|
|
73
|
+
"""Print a command the way the rest of the CLI does (``▸ cmd``)."""
|
|
74
|
+
prefix = "[dry-run]" if dry_run else "▸"
|
|
75
|
+
(console or Console()).print(
|
|
76
|
+
f" {prefix} {format_cmd(cmd)}", style="cyan", highlight=False, markup=False
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def run_cmd(
|
|
81
|
+
cmd: list[str],
|
|
82
|
+
*,
|
|
83
|
+
capture: bool = True,
|
|
84
|
+
check: bool = True,
|
|
85
|
+
input_text: str | None = None,
|
|
86
|
+
env: dict[str, str] | None = None,
|
|
87
|
+
dry_run: bool = False,
|
|
88
|
+
quiet: bool = False,
|
|
89
|
+
console: Console | None = None,
|
|
90
|
+
) -> subprocess.CompletedProcess[str]:
|
|
91
|
+
"""Run ``cmd`` through ``run_resolved``.
|
|
92
|
+
|
|
93
|
+
``dry_run`` prints the command and returns an empty, successful result.
|
|
94
|
+
A missing tool or a non-zero exit (with ``check``) raises ``ToolFailed`` (exit 2).
|
|
95
|
+
"""
|
|
96
|
+
if not quiet:
|
|
97
|
+
echo_cmd(cmd, dry_run=dry_run, console=console)
|
|
98
|
+
if dry_run:
|
|
99
|
+
return subprocess.CompletedProcess(cmd, 0, "", "")
|
|
100
|
+
run_env = {**os.environ, **env} if env else None
|
|
101
|
+
try:
|
|
102
|
+
result = _runner.run_resolved(
|
|
103
|
+
list(cmd),
|
|
104
|
+
capture_output=capture,
|
|
105
|
+
text=True,
|
|
106
|
+
encoding="utf-8",
|
|
107
|
+
errors="replace",
|
|
108
|
+
input=input_text,
|
|
109
|
+
env=run_env,
|
|
110
|
+
)
|
|
111
|
+
except _tools.ToolNotFoundError as e:
|
|
112
|
+
raise ToolFailed(str(e)) from e
|
|
113
|
+
except OSError as e:
|
|
114
|
+
raise ToolFailed(f"Could not run {format_cmd(cmd)}: {e}") from e
|
|
115
|
+
if check and result.returncode != 0:
|
|
116
|
+
detail = "\n".join(
|
|
117
|
+
part.strip()
|
|
118
|
+
for part in (result.stdout, result.stderr)
|
|
119
|
+
if isinstance(part, str) and part.strip()
|
|
120
|
+
)
|
|
121
|
+
msg = f"Command failed (exit code {result.returncode}): {format_cmd(cmd)}"
|
|
122
|
+
if detail:
|
|
123
|
+
msg += f"\n{detail}"
|
|
124
|
+
raise ToolFailed(msg)
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def tool_available(name: str) -> bool:
|
|
129
|
+
"""True when ``name`` resolves on PATH (via ``require_tool``, monkeypatchable)."""
|
|
130
|
+
try:
|
|
131
|
+
_tools.require_tool(name)
|
|
132
|
+
except _tools.ToolNotFoundError:
|
|
133
|
+
return False
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def kubectl_args(args: list[str], target: Target | None, *, namespaced: bool = True) -> list[str]:
|
|
138
|
+
cmd = ["kubectl", *args]
|
|
139
|
+
if target is not None:
|
|
140
|
+
if namespaced and target.namespace:
|
|
141
|
+
cmd += ["-n", target.namespace]
|
|
142
|
+
if target.context:
|
|
143
|
+
cmd += ["--context", target.context]
|
|
144
|
+
return cmd
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def kubectl(
|
|
148
|
+
args: list[str],
|
|
149
|
+
target: Target | None = None,
|
|
150
|
+
*,
|
|
151
|
+
namespaced: bool = True,
|
|
152
|
+
**kwargs: Any,
|
|
153
|
+
) -> subprocess.CompletedProcess[str]:
|
|
154
|
+
return run_cmd(kubectl_args(args, target, namespaced=namespaced), **kwargs)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def kubectl_json(
|
|
158
|
+
args: list[str], target: Target | None = None, *, namespaced: bool = True, **kwargs: Any
|
|
159
|
+
) -> Any:
|
|
160
|
+
"""Run ``kubectl ... -o json`` and parse the result (``None`` on dry-run)."""
|
|
161
|
+
result = kubectl([*args, "-o", "json"], target, namespaced=namespaced, **kwargs)
|
|
162
|
+
if not result.stdout:
|
|
163
|
+
return None
|
|
164
|
+
try:
|
|
165
|
+
return json.loads(result.stdout)
|
|
166
|
+
except json.JSONDecodeError as e:
|
|
167
|
+
raise ToolFailed(f"kubectl returned invalid JSON: {e}") from e
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def helm_args(args: list[str], target: Target | None) -> list[str]:
|
|
171
|
+
cmd = ["helm", *args]
|
|
172
|
+
if target is not None:
|
|
173
|
+
if target.namespace:
|
|
174
|
+
cmd += ["-n", target.namespace]
|
|
175
|
+
if target.context:
|
|
176
|
+
cmd += ["--kube-context", target.context]
|
|
177
|
+
return cmd
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def helm(
|
|
181
|
+
args: list[str], target: Target | None = None, **kwargs: Any
|
|
182
|
+
) -> subprocess.CompletedProcess[str]:
|
|
183
|
+
return run_cmd(helm_args(args, target), **kwargs)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def current_context() -> str | None:
|
|
187
|
+
"""The kubeconfig's current context, or ``None`` when kubectl is missing or unset."""
|
|
188
|
+
try:
|
|
189
|
+
result = run_cmd(["kubectl", "config", "current-context"], check=False, quiet=True)
|
|
190
|
+
except ToolFailed:
|
|
191
|
+
return None
|
|
192
|
+
if result.returncode != 0:
|
|
193
|
+
return None
|
|
194
|
+
return (result.stdout or "").strip() or None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def context_names() -> set[str]:
|
|
198
|
+
"""Context names in the kubeconfig (empty when they cannot be listed; never raises)."""
|
|
199
|
+
try:
|
|
200
|
+
result = run_cmd(
|
|
201
|
+
["kubectl", "config", "get-contexts", "-o", "name"], check=False, quiet=True
|
|
202
|
+
)
|
|
203
|
+
except ToolFailed:
|
|
204
|
+
return set()
|
|
205
|
+
if result.returncode != 0:
|
|
206
|
+
return set()
|
|
207
|
+
return {line.strip() for line in (result.stdout or "").splitlines() if line.strip()}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def server_url(context: str | None) -> str | None:
|
|
211
|
+
"""The API server URL the kubeconfig records for ``context`` (read locally, never raises)."""
|
|
212
|
+
cmd = [
|
|
213
|
+
"kubectl",
|
|
214
|
+
"config",
|
|
215
|
+
"view",
|
|
216
|
+
"--minify",
|
|
217
|
+
"-o",
|
|
218
|
+
"jsonpath={.clusters[0].cluster.server}",
|
|
219
|
+
]
|
|
220
|
+
if context:
|
|
221
|
+
cmd += ["--context", context]
|
|
222
|
+
try:
|
|
223
|
+
result = run_cmd(cmd, check=False, quiet=True)
|
|
224
|
+
except ToolFailed:
|
|
225
|
+
return None
|
|
226
|
+
if result.returncode != 0:
|
|
227
|
+
return None
|
|
228
|
+
return (result.stdout or "").strip() or None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def in_ci() -> bool:
|
|
232
|
+
"""True inside a GitHub Actions job (the runner sets ``GITHUB_ACTIONS=true``)."""
|
|
233
|
+
return os.environ.get("GITHUB_ACTIONS", "").strip().lower() == "true"
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def ensure_namespace(
|
|
237
|
+
target: Target, *, dry_run: bool = False, console: Console | None = None
|
|
238
|
+
) -> None:
|
|
239
|
+
"""Create ``target.namespace`` when it does not exist (idempotent).
|
|
240
|
+
|
|
241
|
+
A fresh cluster has no ``<name>-<env>`` namespace yet, and the Secret is
|
|
242
|
+
applied before ``helm --create-namespace`` runs (in CD modes helm never runs
|
|
243
|
+
from here at all). A caller without permission to read namespaces
|
|
244
|
+
(namespace-scoped RBAC) is assumed to work in an existing one; any other
|
|
245
|
+
failure is a tool failure (exit 2).
|
|
246
|
+
"""
|
|
247
|
+
console = console or Console()
|
|
248
|
+
ns = target.namespace
|
|
249
|
+
create = kubectl_args(["create", "namespace", ns], target, namespaced=False)
|
|
250
|
+
if dry_run:
|
|
251
|
+
console.print(
|
|
252
|
+
f" [dry-run] {format_cmd(create)} (only when the namespace does not exist)",
|
|
253
|
+
style="cyan",
|
|
254
|
+
highlight=False,
|
|
255
|
+
markup=False,
|
|
256
|
+
)
|
|
257
|
+
return
|
|
258
|
+
probe = kubectl(
|
|
259
|
+
["get", "namespace", ns, "-o", "name"], target, namespaced=False, check=False, quiet=True
|
|
260
|
+
)
|
|
261
|
+
if probe.returncode == 0:
|
|
262
|
+
return
|
|
263
|
+
stderr = (probe.stderr or "").strip()
|
|
264
|
+
if "(Forbidden)" in stderr:
|
|
265
|
+
console.print(
|
|
266
|
+
f" Cannot read namespace {ns} (RBAC); assuming it exists.", style="dim", markup=False
|
|
267
|
+
)
|
|
268
|
+
return
|
|
269
|
+
if "(NotFound)" not in stderr:
|
|
270
|
+
raise ToolFailed(
|
|
271
|
+
f"Command failed (exit code {probe.returncode}): "
|
|
272
|
+
f"{format_cmd(kubectl_args(['get', 'namespace', ns], target, namespaced=False))}"
|
|
273
|
+
+ (f"\n{stderr}" if stderr else "")
|
|
274
|
+
)
|
|
275
|
+
result = run_cmd(create, check=False, console=console)
|
|
276
|
+
if result.returncode != 0 and "(AlreadyExists)" not in (result.stderr or ""):
|
|
277
|
+
detail = (result.stderr or result.stdout or "").strip()
|
|
278
|
+
raise ToolFailed(
|
|
279
|
+
f"Command failed (exit code {result.returncode}): {format_cmd(create)}"
|
|
280
|
+
+ (f"\n{detail}" if detail else "")
|
|
281
|
+
)
|
|
282
|
+
console.print(f" Created namespace {ns}.")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def pipe_description(first: list[str], second: list[str]) -> str:
|
|
286
|
+
return f"{shlex.join(first)} | {shlex.join(second)}"
|