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,315 @@
|
|
|
1
|
+
# Kubernetes and the Helm chart
|
|
2
|
+
|
|
3
|
+
`deployment/helm/<name>/`: `Chart.yaml`, `values.yaml`, `values-dev.yaml`, `values-staging.yaml`,
|
|
4
|
+
`values-prod.yaml`, `templates/` (`deployment.yaml`, `service.yaml`, `configmap.yaml`,
|
|
5
|
+
`httproute.yaml`, `ingress.yaml`, `certificate.yaml`, `hpa.yaml`, `pdb.yaml`,
|
|
6
|
+
`networkpolicy.yaml`, `servicemonitor.yaml`, `postgresql-secret.yaml`, `serviceaccount.yaml`,
|
|
7
|
+
`NOTES.txt`, `_helpers.tpl`), `examples/networkpolicy.yaml` (a worked staging/prod NetworkPolicy,
|
|
8
|
+
not packaged), `charts/` (the Postgres and Redis subcharts once fetched). There is
|
|
9
|
+
**no template for the app Secret**. Release name = project name; namespace =
|
|
10
|
+
`environments.<env>.namespace`.
|
|
11
|
+
|
|
12
|
+
`Chart.yaml` declares the Bitnami `postgresql` (`18.11.6`) and `redis` (`28.2.3`) charts from
|
|
13
|
+
`oci://registry-1.docker.io/bitnamicharts` as conditional dependencies (`postgresql.enabled`,
|
|
14
|
+
`redis.enabled`), pinned to exact versions; `values.yaml` pins their images by digest.
|
|
15
|
+
`helm dependency build deployment/helm/<name>` must run before `helm upgrade --install` or
|
|
16
|
+
`helm template`; `deploy` runs it when a subchart is missing from `charts/` (also under
|
|
17
|
+
`--dry-run`, since the render needs it; exit 2 when the registry is unreachable). Docker Hub
|
|
18
|
+
rate-limits anonymous pulls: in CI or offline, vendor the `.tgz` files under `charts/` (or point
|
|
19
|
+
`repository` at a mirror). To use another Postgres (a managed database, CloudNativePG, another
|
|
20
|
+
chart), keep the subcharts off and put the connection string in the Secret.
|
|
21
|
+
|
|
22
|
+
## Values
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
image: { repository: <registry>/<name>, tag: "", pullPolicy: IfNotPresent } # tag: a quoted string, set per deploy
|
|
26
|
+
imagePullSecrets: []
|
|
27
|
+
replicaCount: 1
|
|
28
|
+
runtime: fastapi # selects the subchart-derived variable names
|
|
29
|
+
env: { APP_ENV: prod, MODEL_PROVIDER: ..., MODEL_NAME: ..., CHECKPOINTER: postgres, AUTH_POLICY: ...,
|
|
30
|
+
AUTH_READ_ACROSS_ROLES: "", AUTH_ADMIN_ROLES: "", A2A_NAME: <agent dir>, PORT: "8000" }
|
|
31
|
+
existingSecret: "" # default "<release>-app"
|
|
32
|
+
secretOptional: false # true in values-dev.yaml only
|
|
33
|
+
service: { port: 80, targetPort: 8000 }
|
|
34
|
+
appUrl: "" # A2A card URL; empty derives it from the gateway/ingress hostname
|
|
35
|
+
gateway: { enabled: true, className: "", hostname: "", parentRef: { name: "", namespace: "" } }
|
|
36
|
+
ingress: { enabled: false, className: "", hostname: "", annotations: {} }
|
|
37
|
+
route:
|
|
38
|
+
publicPaths: [{path: /chat, type: Exact}, {path: /threads, type: PathPrefix}, {path: /a2a/<agent dir>, type: PathPrefix}]
|
|
39
|
+
devPaths: [{path: /playground, type: Exact}, {path: /docs, type: Exact}, {path: /openapi.json, type: Exact}]
|
|
40
|
+
metrics: { scrapeAnnotations: false, serviceMonitor: { enabled: false, interval: 30s, scrapeTimeout: 10s, labels: {}, bearerToken: { enabled: false, secretName: "", key: METRICS_TOKEN } } } # secretName "": <release>-metrics
|
|
41
|
+
tls: { existingSecret: "", certManager: { enabled: false, issuerRef: { name: "", kind: ClusterIssuer } } }
|
|
42
|
+
postgresql: { enabled: false, image: {..., digest: sha256:...}, auth: { database: agent, username: agent, existingSecret: <name>-postgresql-auth },
|
|
43
|
+
primary: { persistence: { size: 8Gi }, lifecycleHooks: { preStop: pg_ctl -m fast stop } } }
|
|
44
|
+
postgresqlSecret: { create: true } # the dev database password, created once and kept
|
|
45
|
+
redis: { enabled: false, architecture: standalone, image: {..., digest: sha256:...}, auth: { enabled: false } }
|
|
46
|
+
probes: { liveness: {path: /health, ...}, readiness: {path: /ready, ...}, startup: {path: /health, ...} }
|
|
47
|
+
resources: { requests: { cpu: 100m, memory: 256Mi }, limits: { memory: 1Gi } }
|
|
48
|
+
hpa: { enabled: false, minReplicas: 2, maxReplicas: 5, targetCPU: 70 }
|
|
49
|
+
pdb: { enabled: false, minAvailable: 1 }
|
|
50
|
+
topologySpread: { enabled: false, topologyKey: kubernetes.io/hostname }
|
|
51
|
+
shutdown: { preStopSleepSeconds: 5, drainSeconds: 20 }
|
|
52
|
+
terminationGracePeriodSeconds: 30 # must exceed preStopSleepSeconds + drainSeconds
|
|
53
|
+
serviceAccount: { create: true, name: "", annotations: {} }
|
|
54
|
+
tracing: { enabled: false, capture: metadata, otlpEndpoint: "", langsmith: { project: "" } }
|
|
55
|
+
podAnnotations: {}
|
|
56
|
+
podSecurityContext: { runAsNonRoot: true, runAsUser: 1000, runAsGroup: 1000, fsGroup: 1000, seccompProfile: RuntimeDefault }
|
|
57
|
+
securityContext: { allowPrivilegeEscalation: false, privileged: false, capabilities: { drop: [ALL] }, readOnlyRootFilesystem: true }
|
|
58
|
+
tmpVolume: { sizeLimit: 256Mi, medium: "" } # /tmp, the only writable path
|
|
59
|
+
extraVolumes: []
|
|
60
|
+
extraVolumeMounts: []
|
|
61
|
+
networkPolicy: { enabled: false, ingressFrom: [], restrictEgress: false, egressTo: [] }
|
|
62
|
+
nodeSelector: {}
|
|
63
|
+
tolerations: []
|
|
64
|
+
affinity: {}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Render-time refusals, each with a message naming the fix: an empty `image.tag` (the chart never
|
|
68
|
+
defaults to `latest`), an unquoted numeric tag (`tag: 0123456` would lose digits), a
|
|
69
|
+
`secretOptional` that is not a bool, an HPA without `resources.requests.cpu` or with
|
|
70
|
+
`minReplicas > maxReplicas`, an empty or malformed `route.publicPaths` while a Gateway or Ingress
|
|
71
|
+
is on (a Gateway API rule without matches publishes every path; at most 64 entries; absolute
|
|
72
|
+
paths only; `PathPrefix` or `Exact`), scraping while `env.METRICS_ENABLED` is off, and a
|
|
73
|
+
`terminationGracePeriodSeconds` not longer than `shutdown.preStopSleepSeconds +
|
|
74
|
+
shutdown.drainSeconds` (or a negative pause or drain).
|
|
75
|
+
|
|
76
|
+
`gateway.parentRef.name` is `required` by the HTTPRoute whenever `gateway.enabled` (the
|
|
77
|
+
staging/prod default): set it in `values-<env>.yaml`; `deploy` checks the merged values before
|
|
78
|
+
any tool runs and exits 3 naming the file when it is blank, and `infra check --env <env>` lists it
|
|
79
|
+
as the required check `gateway parentRef`. `appUrl` (or `env.APP_URL`) is the public base URL the
|
|
80
|
+
A2A agent card advertises; empty derives `https://<gateway.hostname>` (or the ingress hostname,
|
|
81
|
+
`http` without TLS) and with no hostname the pod falls back to its bind address and warns
|
|
82
|
+
(`NOTES.txt` warns too). Every `values-<env>.yaml` writes `image:` as a nested mapping (`image:`
|
|
83
|
+
newline ` tag: ...`), never an inline `{}` map, because `deploy` rewrites `image.tag` textually
|
|
84
|
+
and comments survive that way.
|
|
85
|
+
|
|
86
|
+
Precedence: the Deployment uses `envFrom: [secretRef: existingSecret]` plus `env:` from values.
|
|
87
|
+
Kubernetes gives `env` precedence over `envFrom`, so chart-set variables (`CHECKPOINTER`,
|
|
88
|
+
`TRACING_*`, and `POSTGRES_DSN` / `DATABASE_URI` / `REDIS_URI` **only when the corresponding
|
|
89
|
+
subchart is enabled**) win; when a subchart is disabled the Secret supplies the connection
|
|
90
|
+
string. Outside dev (`secretOptional: false`) the pods do not start until the Secret exists
|
|
91
|
+
(`CreateContainerConfigError`); `deploy` checks its required keys before it changes anything.
|
|
92
|
+
|
|
93
|
+
`values-dev.yaml`: `env.APP_ENV=dev`, `secretOptional: true`, `postgresql.enabled=true` (and
|
|
94
|
+
`redis.enabled=true` under `langgraph-server`), `gateway.enabled=false`, `image.tag: ""`.
|
|
95
|
+
`values-staging.yaml`: `secretOptional: false`, `postgresql.enabled=false`, gateway on,
|
|
96
|
+
hostnames blank. `values-prod.yaml`: the same plus `replicaCount: 2`, a PDB, `topologySpread`
|
|
97
|
+
on and larger requests (250m / 512Mi). Environment values files are config: `upgrade` never
|
|
98
|
+
overwrites them and they never contain secrets. `values.yaml` and `templates/**` are scaffolding
|
|
99
|
+
(3-way merged).
|
|
100
|
+
|
|
101
|
+
## Pod security
|
|
102
|
+
|
|
103
|
+
The pod runs as uid/gid 1000 with `runAsNonRoot`, seccomp `RuntimeDefault`, every capability
|
|
104
|
+
dropped, no privilege escalation, a read-only root filesystem (a `/tmp` emptyDir is the only
|
|
105
|
+
writable path; `HOME=/tmp`, `PYTHONDONTWRITEBYTECODE=1`) and no service-account token. The
|
|
106
|
+
rendered pod passes the `restricted` Pod Security Standard. Both images (`Dockerfile`,
|
|
107
|
+
`Dockerfile.langgraph-server`) run as 1000:1000 and are built for this.
|
|
108
|
+
|
|
109
|
+
## Probes
|
|
110
|
+
|
|
111
|
+
Liveness and startup ask `/health` (the process answers); readiness asks `/ready` (the database
|
|
112
|
+
and run store are set up and answer within 2 s), so a pod that loses its database leaves the
|
|
113
|
+
Service endpoints instead of being restarted, and a pod that starts before its database (a first
|
|
114
|
+
install, a node drain during an outage) waits unready instead of crash-looping. Tune `probes.<kind>.{path,periodSeconds,timeoutSeconds,failureThreshold}`.
|
|
115
|
+
|
|
116
|
+
## Graceful shutdown
|
|
117
|
+
|
|
118
|
+
On a rollout, `deploy --restart`, a scale-down or a node drain, a stopping pod first keeps
|
|
119
|
+
serving for `shutdown.preStopSleepSeconds` (default 5; a `preStop` hook running `sleep`) while
|
|
120
|
+
the Service endpoints, kube-proxy and the Gateway stop sending it new connections; without that
|
|
121
|
+
pause a rolling restart refuses connections. Then it gets SIGTERM and has
|
|
122
|
+
`shutdown.drainSeconds` (default 20) to finish in-flight requests and streams: the chart sets
|
|
123
|
+
`UVICORN_TIMEOUT_GRACEFUL_SHUTDOWN` (fastapi) or `BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS`
|
|
124
|
+
(langgraph-server, whose image fixes uvicorn's own timeout) unless `env` sets it. A `/chat`
|
|
125
|
+
stream still running after that is cut off. The kubelet kills the pod at
|
|
126
|
+
`terminationGracePeriodSeconds` (default 30), counted from the start of the pause, so the chart
|
|
127
|
+
refuses a grace period that is not longer than pause + drain. For long runs (`RUN_TIMEOUT_S`)
|
|
128
|
+
raise `drainSeconds` and `terminationGracePeriodSeconds` together; `0` turns the pause (or the
|
|
129
|
+
drain limit) off.
|
|
130
|
+
|
|
131
|
+
The bundled dev Postgres gets a `preStop` hook too (`pg_ctl -m fast stop`): Postgres answers
|
|
132
|
+
Kubernetes' SIGTERM with a smart shutdown that waits for every client, and the agent's pooled
|
|
133
|
+
sessions never leave, so without it a restart of the database pod ends in a SIGKILL after 30 s
|
|
134
|
+
and crash recovery. With it the pod stops in about a second and restarts cleanly.
|
|
135
|
+
|
|
136
|
+
## Traffic entry and TLS
|
|
137
|
+
|
|
138
|
+
- Default: Gateway API `HTTPRoute` (`gateway.networking.k8s.io/v1`; Kubernetes 1.28+). Set
|
|
139
|
+
`gateway.parentRef` to the operator's Gateway and `gateway.hostname`.
|
|
140
|
+
- Alternative: `ingress.enabled=true` with `ingress.className`.
|
|
141
|
+
- Only `route.publicPaths` are published (plus `route.devPaths` under `APP_ENV=dev`): `/chat`,
|
|
142
|
+
`/threads` and `/a2a/<agent dir>` by default. `/health`, `/ready` and `/metrics` stay inside
|
|
143
|
+
the cluster (the kubelet probes the pod directly). Keep the `/a2a` entry in step with
|
|
144
|
+
`env.A2A_NAME`. Under `langgraph-server`, `/threads` also publishes the server's native thread
|
|
145
|
+
routes (limited to the caller's own threads by the auth handler; a run started there skips
|
|
146
|
+
`/chat`'s guardrails); `/assistants`, `/runs`, `/store`, `/crons` are commented examples.
|
|
147
|
+
- No controller is assumed or installed. Choose a maintained implementation the platform supports
|
|
148
|
+
(Envoy Gateway, Cilium, Istio, Traefik, NGINX Gateway Fabric, Kong, or the platform's own
|
|
149
|
+
router). `infra check` lists the `GatewayClass` and `IngressClass` objects present.
|
|
150
|
+
- TLS: `tls.existingSecret` (operator-provided certificate) is the default expectation;
|
|
151
|
+
`tls.certManager.enabled=true` adds a `Certificate` and makes cert-manager a prerequisite.
|
|
152
|
+
- Authentication is enforced by the app (the auth policy), not by route annotations, so it is
|
|
153
|
+
identical on every controller and under local-load. Rate limiting is not built in: configure it
|
|
154
|
+
on the Gateway or ingress controller.
|
|
155
|
+
|
|
156
|
+
## Metrics and network policy
|
|
157
|
+
|
|
158
|
+
`/metrics` (Prometheus text) is served on the http port. `metrics.scrapeAnnotations: true` adds
|
|
159
|
+
`prometheus.io/scrape|path|port` pod annotations; `metrics.serviceMonitor.enabled: true` renders a
|
|
160
|
+
`ServiceMonitor` (Prometheus Operator CRDs needed; `labels` for its selector). With
|
|
161
|
+
`METRICS_TOKEN` in the Secret (add it to `secrets.keys`) the scraper must send
|
|
162
|
+
`Authorization: Bearer <token>`: `metrics.serviceMonitor.bearerToken.enabled: true` makes the
|
|
163
|
+
ServiceMonitor do so (its endpoint's `authorization`). The token is read from
|
|
164
|
+
`<release>-metrics`, a Secret holding only `METRICS_TOKEN` that `secrets apply` and a direct
|
|
165
|
+
`deploy` write alongside the app Secret, so Prometheus needs read access to that one Secret and
|
|
166
|
+
never to the app Secret (provider key, API tokens, DSN); `bearerToken.secretName` / `.key` name
|
|
167
|
+
a Secret you manage instead. `infra check` reports a missing token Secret. Pod annotations cannot
|
|
168
|
+
carry a token: an annotation-discovering Prometheus needs the token in its own scrape job
|
|
169
|
+
(`authorization.credentials_file`), or leave `METRICS_TOKEN` unset and rely on the
|
|
170
|
+
NetworkPolicy.
|
|
171
|
+
|
|
172
|
+
`networkPolicy.enabled: true` admits only the http port, from `networkPolicy.ingressFrom` when
|
|
173
|
+
listed (list the Gateway's namespace and, if you scrape, the Prometheus namespace);
|
|
174
|
+
`restrictEgress: true` also limits egress to DNS (the kube-dns pods) and
|
|
175
|
+
`networkPolicy.egressTo`. It is off by default (it needs a CNI that enforces NetworkPolicy and
|
|
176
|
+
addresses only you know). `examples/networkpolicy.yaml` is a worked staging/prod example: in
|
|
177
|
+
from the Gateway's and Prometheus's namespaces; out to DNS, the database (Redis too under
|
|
178
|
+
`langgraph-server`), an on-network model server (`openai-compatible`) and HTTPS on public
|
|
179
|
+
addresses only (every private range and `169.254.169.254` excluded), with a commented entry for
|
|
180
|
+
an on-network API. Copy its `networkPolicy` block into `values-<env>.yaml` and replace the
|
|
181
|
+
addresses marked `CHANGE` (NetworkPolicy matches addresses, not host names; add a JWKS URL or
|
|
182
|
+
an OTLP collector the agent must reach). The chart tests render and validate it; once deployed,
|
|
183
|
+
`/ready` answers 200 (DNS and the database reachable) and a pod in another namespace cannot
|
|
184
|
+
connect.
|
|
185
|
+
|
|
186
|
+
## Persistence toggles
|
|
187
|
+
|
|
188
|
+
| Runtime | Subchart on (`values-dev.yaml`) | Subchart off (staging/prod) |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| fastapi | chart sets `POSTGRES_DSN` from the chart-managed `<name>-postgresql-auth` Secret; `CHECKPOINTER=postgres` | `POSTGRES_DSN` from the Secret |
|
|
191
|
+
| langgraph-server | chart sets `DATABASE_URI` from the subchart and, with `redis.enabled` (on in `values-dev.yaml` for this runtime), `REDIS_URI`; the Deployment always sets `LANGGRAPH_SERVER=1` so `fast_api_app.py` detects the mounted runtime | both from the Secret |
|
|
192
|
+
|
|
193
|
+
The dev database password lives in `<name>-postgresql-auth`, created once by the chart and kept
|
|
194
|
+
across upgrades (`helm lookup`), uninstalls and Argo CD syncs (the Applications ignore its data).
|
|
195
|
+
The agent's database is agent-owned: its own credentials, migrations, backups, quotas. Never point
|
|
196
|
+
it at another application's operational database. Postgres `max_connections` must cover
|
|
197
|
+
replicas x (`DB_POOL_MAX_SIZE` + 1): the extra connection per replica renews the cross-replica
|
|
198
|
+
run leases (rows with a 30 s expiry, so a replica lost with its node frees its threads 30 s
|
|
199
|
+
later, and a database restart drops no lease). Connections get `connect_timeout=5` and TCP
|
|
200
|
+
keepalives unless the DSN sets them.
|
|
201
|
+
|
|
202
|
+
### External database: a least-privileged role over TLS
|
|
203
|
+
|
|
204
|
+
The agent needs no superuser. It creates its tables on first start (under an advisory lock) and
|
|
205
|
+
then reads and writes only them, so a role that owns its own database is enough:
|
|
206
|
+
|
|
207
|
+
```sql
|
|
208
|
+
CREATE ROLE agent LOGIN PASSWORD '...' NOSUPERUSER NOCREATEDB NOCREATEROLE;
|
|
209
|
+
CREATE DATABASE agent OWNER agent;
|
|
210
|
+
REVOKE ALL ON DATABASE agent FROM PUBLIC;
|
|
211
|
+
-- A shared database instead: CREATE SCHEMA agent AUTHORIZATION agent;
|
|
212
|
+
-- ALTER ROLE agent SET search_path = agent;
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The connection string goes to psycopg (libpq) unchanged, so every libpq parameter works. Require
|
|
216
|
+
TLS and check the server's certificate:
|
|
217
|
+
`postgresql://agent:<password>@db.example.com:5432/agent?sslmode=verify-full&sslrootcert=/etc/db-ca/ca.crt`,
|
|
218
|
+
with the CA mounted from a Secret or ConfigMap:
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
extraVolumes:
|
|
222
|
+
- name: db-ca
|
|
223
|
+
secret: { secretName: db-ca } # kubectl create secret generic db-ca --from-file=ca.crt
|
|
224
|
+
extraVolumeMounts:
|
|
225
|
+
- { name: db-ca, mountPath: /etc/db-ca, readOnly: true }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
A certificate from a public CA needs no file: `sslrootcert=system`. `PGSSLMODE` /
|
|
229
|
+
`PGSSLROOTCERT` in the chart env work too. `deploy` and `secrets apply` warn outside dev when the
|
|
230
|
+
DSN does not require TLS (`sslmode` `require`, `verify-ca` or `verify-full`), and `infra check`
|
|
231
|
+
reports it (`database tls`); the value is never printed. On the server, `hostssl` lines in
|
|
232
|
+
`pg_hba.conf` (and a `hostnossl ... reject` for the agent's role) refuse clear-text connections.
|
|
233
|
+
|
|
234
|
+
## Scaling and availability
|
|
235
|
+
|
|
236
|
+
`hpa.enabled` (needs metrics-server; `infra check` requires it only then; the chart refuses it
|
|
237
|
+
without `resources.requests.cpu`) and `pdb.enabled` (on in prod) ship disabled in `values.yaml`.
|
|
238
|
+
`replicaCount > 1` requires `postgres` (memory + kubernetes is refused at scaffold time). Requests
|
|
239
|
+
(100m / 256Mi) and a 1Gi memory limit are set by default, with no CPU limit; override them per
|
|
240
|
+
environment. `topologySpread.enabled` (prod) spreads replicas across nodes softly.
|
|
241
|
+
|
|
242
|
+
## Local-load dev clusters
|
|
243
|
+
|
|
244
|
+
`deploy` identifies a local cluster from the cluster itself (its nodes' `providerID`, names and
|
|
245
|
+
labels), confirmed with the tool's own listing; the context name decides only when the nodes
|
|
246
|
+
cannot be read (and a `kind-*` name is still confirmed with `kind get clusters`):
|
|
247
|
+
|
|
248
|
+
| Cluster | Load command |
|
|
249
|
+
|---|---|
|
|
250
|
+
| kind (confirmed with `kind get clusters`) | `kind load docker-image <image> --name <cluster>` |
|
|
251
|
+
| k3d (`k3d cluster list`) | `k3d image import <image> -c <cluster>` |
|
|
252
|
+
| k3s (one of its nodes is this machine) | `docker save -o .graph-agents-cli/image.tar` then `k3s ctr images import` (no `sudo`; needs root on most hosts, so run `deploy` as a user allowed to invoke `k3s ctr`) |
|
|
253
|
+
| minikube (`minikube profile list`) | `minikube image load <image> [-p <profile>]` |
|
|
254
|
+
| Docker Desktop, Rancher Desktop, OrbStack | none (shared daemon) |
|
|
255
|
+
|
|
256
|
+
Anything else gets the image through the registry. `deploy --env dev` then applies the Secret,
|
|
257
|
+
runs `helm dependency build` if needed, and runs `helm upgrade --install <name>
|
|
258
|
+
deployment/helm/<name> -n <name>-dev --create-namespace -f values.yaml -f values-dev.yaml --set
|
|
259
|
+
image.repository=...,image.tag=<tag>,existingSecret=<name>-app --wait --timeout 5m
|
|
260
|
+
--kube-context <context>` (`<tag>` is the short sha, `<sha>-dirty-<time>` for a tree with
|
|
261
|
+
uncommitted changes, a UTC timestamp outside git, or `--tag`). Access: `kubectl -n <name>-dev
|
|
262
|
+
port-forward svc/<name> 8000:80`, then `GRAPH_AGENTS_CLI_API_KEY=<API_KEY> graph-agents-cli run
|
|
263
|
+
--url http://localhost:8000 "hello"` (under `jwt`, a user's token; the variable keeps the
|
|
264
|
+
credential out of argv).
|
|
265
|
+
|
|
266
|
+
## Multi-node self-hosted clusters
|
|
267
|
+
|
|
268
|
+
kubeadm, RKE2, OpenShift and similar: images go through the registry (`--registry`, default
|
|
269
|
+
`ghcr.io/<org>`; Harbor or `registry:2` in-cluster work the same; the placeholder
|
|
270
|
+
`ghcr.io/CHANGE-ME` is refused with exit 3). Private images need one image pull secret referenced
|
|
271
|
+
from `imagePullSecrets`; it is an operator prerequisite that `infra check` reports. On OpenShift
|
|
272
|
+
use the platform router through `ingress` or its Gateway implementation.
|
|
273
|
+
|
|
274
|
+
## Disconnected profile (cluster side)
|
|
275
|
+
|
|
276
|
+
Mirror the base images into the registry (`python:3.12.14-slim-bookworm` and
|
|
277
|
+
`ghcr.io/astral-sh/uv` for fastapi, through the `PYTHON_IMAGE` / `UV_IMAGE` build args;
|
|
278
|
+
`langchain/langgraph-api` only if that runtime is ever cleared for the profile), vendor the
|
|
279
|
+
subcharts and mirror their images, point `UV_INDEX_URL` at a mirror when building, set
|
|
280
|
+
`OPENAI_BASE_URL` / `JUDGE_BASE_URL` at on-network model servers, and either
|
|
281
|
+
`tracing.enabled=false` or `tracing.otlpEndpoint` at an in-cluster collector. `infra check
|
|
282
|
+
--profile disconnected` fails on any hosted dependency.
|
|
283
|
+
|
|
284
|
+
## Inspecting what will be applied
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
graph-agents-cli deploy --env staging --dry-run # commands + rendered manifests
|
|
288
|
+
helm template <name> deployment/helm/<name> -f deployment/helm/<name>/values.yaml \
|
|
289
|
+
-f deployment/helm/<name>/values-staging.yaml --set-string image.tag=<tag> \
|
|
290
|
+
--set gateway.parentRef.name=<gateway>
|
|
291
|
+
helm lint deployment/helm/<name>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Rollback (direct and helm-push)
|
|
295
|
+
|
|
296
|
+
`deploy` rolls a failed rollout back itself (`--atomic`, the default): it prints the pods'
|
|
297
|
+
states, this release's warning events since the deploy started and the logs, then rolls back to
|
|
298
|
+
the newest good revision, or uninstalls a first install that never succeeded. It only acts on the
|
|
299
|
+
revision this run created; if another helm operation holds the release it refuses and prints the
|
|
300
|
+
command that clears a stale lock. Once the release is back where it was (rolled back, never
|
|
301
|
+
changed, or uninstalled), the app Secret this deploy applied is put back too (a Secret it created
|
|
302
|
+
is deleted), unless someone changed it in the meantime; when the release stays on the failed
|
|
303
|
+
revision (`--no-atomic`, another deploy, an unreadable history) the Secret keeps the new values
|
|
304
|
+
and the error says how to put the old ones back. `deploy --status` reports the rollout within
|
|
305
|
+
`--timeout` (default 60 s: replicas, image, helm revision, each pod's readiness and restarts;
|
|
306
|
+
diagnostics and exit 1 when not ready), and `deploy --restart` waits for the new pods (default
|
|
307
|
+
5 m; diagnostics and exit 2 when they do not become ready, while the old pods keep serving).
|
|
308
|
+
A manual rollback of a release that deployed fine:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
helm -n <name>-<env> history <name>
|
|
312
|
+
helm -n <name>-<env> rollback <name> <revision>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
argocd mode: revert on `main` through a PR (see `gitops.md`).
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Secrets
|
|
2
|
+
|
|
3
|
+
Plain Kubernetes Secrets, provisioned per environment, restricted to allow-listed keys. No
|
|
4
|
+
secrets controller is required.
|
|
5
|
+
|
|
6
|
+
## Contract
|
|
7
|
+
|
|
8
|
+
- The chart references the app Secret by name (`existingSecret`, default `<release>-app`) and
|
|
9
|
+
mounts it with `envFrom`. The chart never templates the app Secret.
|
|
10
|
+
- Type `Opaque`, one key per allow-listed variable, in namespace `<name>-<env>`.
|
|
11
|
+
- Outside dev the Secret is required (`secretOptional: false`): pods do not start without it
|
|
12
|
+
(`CreateContainerConfigError`). `values-dev.yaml` sets `secretOptional: true`.
|
|
13
|
+
- Non-secret configuration is the chart's ConfigMap plus Deployment `env` (which overrides
|
|
14
|
+
`envFrom`).
|
|
15
|
+
|
|
16
|
+
## Allow-listed keys (`secrets.keys` in the manifest)
|
|
17
|
+
|
|
18
|
+
Scaffold default:
|
|
19
|
+
|
|
20
|
+
| Key | When |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `OPENAI_API_KEY` \| `ANTHROPIC_API_KEY` \| `GOOGLE_API_KEY` \| `MODEL_API_KEY` | the provider key for `model_provider` |
|
|
23
|
+
| `JUDGE_API_KEY` | always (defaults to the provider key at runtime) |
|
|
24
|
+
| `POSTGRES_DSN` | runtime `fastapi` |
|
|
25
|
+
| `DATABASE_URI`, `REDIS_URI` | runtime `langgraph-server` |
|
|
26
|
+
| `API_KEY` | `shared-bearer` only (the one policy that reads it; generated when missing). Projects created before this change keep it listed; it is harmless there |
|
|
27
|
+
| `LANGSMITH_API_KEY` | always (used only when tracing is enabled) |
|
|
28
|
+
| each `auth: bearer` API's `token_env` | when `api-policy.yaml` declares that API |
|
|
29
|
+
| `AUTH_JWT_SECRET` | added automatically (not listed) under `jwt` when the chart values or the env file opt into HS* (`AUTH_JWT_ALLOW_HS=true` or an HS* algorithm) |
|
|
30
|
+
|
|
31
|
+
Only these are exported from the env file, never the whole file. Add any other secret the
|
|
32
|
+
project uses to the list: for example `METRICS_TOKEN`, `PRINCIPAL_HASH_SALT`, a LangGraph
|
|
33
|
+
licence key, or a tool credential. `scaffold enhance` recomputes the list when the runtime or
|
|
34
|
+
provider changes (keys you added are kept).
|
|
35
|
+
|
|
36
|
+
`METRICS_TOKEN`, once the app Secret holds it, is also written alone into `<name>-metrics`, the
|
|
37
|
+
Secret the chart's ServiceMonitor reads its bearer token from
|
|
38
|
+
(`metrics.serviceMonitor.bearerToken`): the scraper then needs read access to that Secret only,
|
|
39
|
+
never to the app Secret with the provider key, the API tokens and the DSN.
|
|
40
|
+
|
|
41
|
+
## Required keys
|
|
42
|
+
|
|
43
|
+
`deploy` and `secrets status` treat these as required, following the environment's merged chart
|
|
44
|
+
values: the provider key (not for `openai-compatible` or `fake`), `API_KEY` under
|
|
45
|
+
`shared-bearer`, `AUTH_JWT_SECRET` under `jwt` when the chart values list an HS* algorithm, and
|
|
46
|
+
`POSTGRES_DSN` or `DATABASE_URI`/`REDIS_URI` unless the bundled subchart provides them or
|
|
47
|
+
`CHECKPOINTER=memory`. A key set as a plain chart `env` value is satisfied. Only keys in
|
|
48
|
+
`secrets.keys` can be required: removing one from the allow-list is how an environment opts out.
|
|
49
|
+
|
|
50
|
+
## Commands
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
graph-agents-cli secrets apply --env <env> [--env-file <file>] [--context <ctx>] [--yes] [--rotate-api-key] [--dry-run]
|
|
54
|
+
graph-agents-cli secrets status --env <env> [--context <ctx>] [--strict] [--dry-run]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- **Env file:** `--env-file`, else `.env.<env>`. Only `dev` falls back to `.env` (a developer's
|
|
58
|
+
local keys never reach staging or prod); any other environment without one exits 3 with the
|
|
59
|
+
list of allow-listed keys.
|
|
60
|
+
- **Kube context:** `--context`, else `environments.<env>.context`, else the kubeconfig's
|
|
61
|
+
current context, which outside dev needs a confirmation (`Apply the Secret for <env> on
|
|
62
|
+
context '<ctx>'? [y/N]`) or `--yes`. The context and API server are printed first. An explicit
|
|
63
|
+
context missing from the kubeconfig is exit 3.
|
|
64
|
+
- **`apply`** creates the namespace when it is missing, then applies `<name>-app` with
|
|
65
|
+
`kubectl create secret generic <name>-app --from-env-file=<0600 temporary file> --dry-run=client -o yaml | kubectl apply --server-side --field-manager=graph-agents-cli --force-conflicts -f -`.
|
|
66
|
+
The temporary file holds only the allow-listed keys and is deleted afterwards, so no value
|
|
67
|
+
appears on a command line; server-side apply writes no `last-applied-configuration` annotation
|
|
68
|
+
(one left by an older client-side apply is removed and reported). `--force-conflicts` makes
|
|
69
|
+
the CLI the owner of the allow-listed keys: do not let another controller manage them.
|
|
70
|
+
- **Merge:** a key the env file sets replaces the live value; an allow-listed key the env file
|
|
71
|
+
leaves out is kept from the live Secret (so a partial env file never deletes keys). Remove a key
|
|
72
|
+
by dropping it from `secrets.keys` (the next apply removes it) or with `kubectl`.
|
|
73
|
+
- **`API_KEY`:** the live key wins. When the env file sets a different one, the live key is kept
|
|
74
|
+
with a warning unless `--rotate-api-key` is passed (clients with the old key then get 401;
|
|
75
|
+
restart the pods with `deploy --restart`). Under `shared-bearer` (the policy in the chart's
|
|
76
|
+
`env.AUTH_POLICY`, else the manifest's), when it is in neither the env file nor the live
|
|
77
|
+
Secret, a key (32 random bytes, hex) is generated, applied, and written to the env file (mode
|
|
78
|
+
0600) after the apply succeeds; it is never printed. If writing the file fails, the kubectl
|
|
79
|
+
command to read it back is printed. `jwt` and `custom` never get a generated key (one the env
|
|
80
|
+
file or the live Secret already holds is kept; drop `API_KEY` from `secrets.keys` to remove
|
|
81
|
+
it).
|
|
82
|
+
- Values must be single-line (the env-file format cannot carry a newline; exit 3 naming the
|
|
83
|
+
key). Exit 2 when kubectl fails (including a `get secret` failure that is not `NotFound`, which
|
|
84
|
+
is never treated as "generate a new key").
|
|
85
|
+
- **`apply --dry-run`** prints the pipeline and a redacted manifest; it reads nothing from the
|
|
86
|
+
cluster, prints no key, and notes that allow-listed keys absent from the env file would be kept
|
|
87
|
+
from the live Secret.
|
|
88
|
+
- Outside dev, `apply` warns when the external database's DSN does not require TLS (see
|
|
89
|
+
`kubernetes.md`, "External database"); the value is never printed.
|
|
90
|
+
- **`status`** runs `kubectl get secret <name>-app -o json` and lists `present`, `missing
|
|
91
|
+
required`, `missing optional` and `not in the allow-list` keys, never values. Exit codes
|
|
92
|
+
(usable as a gate): 0 every required key present, 1 the Secret or a required key is missing
|
|
93
|
+
(any allow-listed key with `--strict`), 2 kubectl failed (connection, credentials, RBAC), 3
|
|
94
|
+
configuration error (unknown environment or context, no manifest). `--dry-run` prints the
|
|
95
|
+
command only.
|
|
96
|
+
- `apply` is **not** refused under CI: keeping application secrets out of CI is the procedure
|
|
97
|
+
below (the scaffolded workflows never hold them), not a runtime guard.
|
|
98
|
+
- In `helm-push` and `argocd` modes `deploy` never applies Secrets (`--env-file` and
|
|
99
|
+
`--rotate-api-key` are refused there); helm-push `deploy` still checks the live Secret's
|
|
100
|
+
required keys before helm runs.
|
|
101
|
+
|
|
102
|
+
## Direct-mode `deploy`
|
|
103
|
+
|
|
104
|
+
`deploy --env <env>` in `cd: skip` applies the Secret with the same rules as `secrets apply`,
|
|
105
|
+
but first checks, read-only, that the Secret it would produce holds every required key: when
|
|
106
|
+
one is missing it exits 1 before anything is built, pushed or changed, naming the keys and the
|
|
107
|
+
env file to add them to. `dev` without an env file leaves the Secret as it is (and still checks
|
|
108
|
+
it). So does an env file that sets none of the allow-listed keys (a keyless project: the `fake`
|
|
109
|
+
model, a keyless `openai-compatible` endpoint, no `shared-bearer` key), decided before anything
|
|
110
|
+
is built; outside dev, where the chart requires the Secret (`secretOptional: false`), a missing
|
|
111
|
+
Secret then exits 1 before the build. `deploy --dry-run` makes the same read of the live Secret
|
|
112
|
+
and refuses (exit 1) what the real run would; when the cluster cannot be read it says the check
|
|
113
|
+
could not be completed.
|
|
114
|
+
|
|
115
|
+
When the rollout fails and the release is put back where it was (rolled back, failed before a
|
|
116
|
+
new revision, or a first install uninstalled), `deploy` also puts the app Secret (and
|
|
117
|
+
`<name>-metrics`) back to the values it held before, byte for byte, with a `resourceVersion`
|
|
118
|
+
precondition; a Secret this deploy created is deleted. A Secret someone changed after this
|
|
119
|
+
deploy applied it is left alone. When the release stays on the failed revision (`--no-atomic`,
|
|
120
|
+
another deploy, an unreadable history), the Secret keeps the new values and the error names the
|
|
121
|
+
changed keys and how to re-apply the previous ones. When the pods are not replaced (same image
|
|
122
|
+
and chart values) but the Secret changed, `deploy` says to run `deploy --restart`.
|
|
123
|
+
|
|
124
|
+
## Ownership in CD modes
|
|
125
|
+
|
|
126
|
+
The manifest records `secrets.owner` (free text: a team or role). That owner creates the Secret
|
|
127
|
+
once per environment with `graph-agents-cli secrets apply --env <env>` (from `.env.<env>`) from a
|
|
128
|
+
workstation with cluster access, or with `kubectl create secret generic`. CI never holds
|
|
129
|
+
application secrets. Argo never manages the app Secret. External Secrets Operator or Sealed
|
|
130
|
+
Secrets can replace this procedure; drop the keys they manage from `secrets.keys`.
|
|
131
|
+
|
|
132
|
+
## Rotation
|
|
133
|
+
|
|
134
|
+
1. Put the new value in `.env.<env>`.
|
|
135
|
+
2. `graph-agents-cli secrets apply --env <env>` (add `--rotate-api-key` for `API_KEY`).
|
|
136
|
+
3. `graph-agents-cli deploy --restart --env <env>` (`kubectl rollout restart`), because an
|
|
137
|
+
externally managed Secret does not change the pod template checksum. It waits until the new
|
|
138
|
+
pods are ready (`--timeout`, default 5m) and exits 2 with the pods' states, events and logs
|
|
139
|
+
when they are not (a bad value: the old pods keep serving). In argocd environments, prefer an
|
|
140
|
+
Argo resource action; self-heal may revert the restart annotation.
|
|
141
|
+
4. For `API_KEY`, update the client application's configuration in the same window.
|
|
142
|
+
|
|
143
|
+
## What is never a secret
|
|
144
|
+
|
|
145
|
+
`MODEL_PROVIDER`, `MODEL_NAME`, `OPENAI_BASE_URL`, `CHECKPOINTER`, `AUTH_POLICY`,
|
|
146
|
+
`AUTH_READ_ACROSS_ROLES`, `AUTH_ADMIN_ROLES`, the `AUTH_JWT_*` settings except
|
|
147
|
+
`AUTH_JWT_SECRET`, each API's `base_url_env`, the limits and logging settings, `TRACING_ENABLED`,
|
|
148
|
+
`TRACE_CAPTURE`, `LANGSMITH_PROJECT`, `LANGSMITH_ENDPOINT`, `OTEL_EXPORTER_OTLP_ENDPOINT`,
|
|
149
|
+
`APP_ENV`, `PORT`: these live in `values-<env>.yaml` `env:`. Settings that appear only in the
|
|
150
|
+
env file never reach the pods (the CLI warns about HS* JWT settings found there).
|
|
151
|
+
|
|
152
|
+
## Checks
|
|
153
|
+
|
|
154
|
+
- `secrets status --env <env>` after `apply`: exit 0 means every required key is there.
|
|
155
|
+
- A pod in `CreateContainerConfigError` means the Secret is missing (outside dev).
|
|
156
|
+
- `infra check --env <env>` reports the app Secret's missing required keys (with a `secrets
|
|
157
|
+
apply` command that runs as printed), the `<name>-metrics` token Secret when the ServiceMonitor
|
|
158
|
+
sends a token, whether the external DSN requires TLS, and whether the image pull secret named
|
|
159
|
+
in `imagePullSecrets` exists; that pull secret is an operator prerequisite, not managed by
|
|
160
|
+
`secrets apply`.
|