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,562 @@
|
|
|
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
|
+
"""argocd-mode desired-state writer: values tag rewrite, branch, pull request.
|
|
15
|
+
|
|
16
|
+
``deploy`` never runs helm in argocd mode (Argo CD applies the change). It takes ``values-<env>.yaml``
|
|
17
|
+
as ``origin/main`` holds it (the pull request's base; ``HEAD`` when that ref
|
|
18
|
+
is unavailable), rewrites ``image.tag`` in that text and nothing else, commits
|
|
19
|
+
the one file on ``deploy/<env>/<tag>`` with git plumbing (the developer's
|
|
20
|
+
checkout, index and working tree are left alone), pushes, and opens or
|
|
21
|
+
updates a pull request through ``gh``, or the GitHub REST API with
|
|
22
|
+
``GITHUB_TOKEN`` when ``gh`` is not installed. Only
|
|
23
|
+
GitHub and GitHub Enterprise Server remotes are supported. Index paths are
|
|
24
|
+
relative to the repository root, so a project below the git root is handled.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import re
|
|
32
|
+
import tempfile
|
|
33
|
+
from dataclasses import dataclass
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
from graph_agents_cli._output import Console
|
|
37
|
+
from graph_agents_cli.deploy import _kube
|
|
38
|
+
from graph_agents_cli.deploy._kube import ConfigError, Refused, ToolFailed, run_cmd
|
|
39
|
+
from graph_agents_cli.deploy._values import rewrite_image_tag
|
|
40
|
+
|
|
41
|
+
DEFAULT_BASE_BRANCH = "main"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class Remote:
|
|
46
|
+
host: str
|
|
47
|
+
owner: str
|
|
48
|
+
repo: str
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def slug(self) -> str:
|
|
52
|
+
return f"{self.owner}/{self.repo}"
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def gh_repo(self) -> str:
|
|
56
|
+
return f"{self.host}/{self.owner}/{self.repo}"
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def api_base(self) -> str:
|
|
60
|
+
if self.host == "github.com":
|
|
61
|
+
return "https://api.github.com"
|
|
62
|
+
return f"https://{self.host}/api/v3"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
_REMOTE_RES = (
|
|
66
|
+
re.compile(
|
|
67
|
+
r"^(?:https?|ssh|git)://(?:[^@/]+@)?(?P<host>[^/:]+)(?::\d+)?/(?P<owner>[^/]+)/(?P<repo>[^/]+?)(?:\.git)?/?$"
|
|
68
|
+
),
|
|
69
|
+
re.compile(r"^(?:[^@]+@)?(?P<host>[^:/]+):(?P<owner>[^/]+)/(?P<repo>[^/]+?)(?:\.git)?/?$"),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def parse_remote(url: str) -> Remote:
|
|
74
|
+
url = url.strip()
|
|
75
|
+
for pattern in _REMOTE_RES:
|
|
76
|
+
m = pattern.match(url)
|
|
77
|
+
if m:
|
|
78
|
+
return Remote(m.group("host").lower(), m.group("owner"), m.group("repo"))
|
|
79
|
+
raise Refused(f"Could not parse the git remote URL {url!r}.")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def enterprise_hosts() -> set[str]:
|
|
83
|
+
"""Hosts the operator declared as GitHub Enterprise Server (GH_HOST / GITHUB_SERVER_URL)."""
|
|
84
|
+
hosts: set[str] = set()
|
|
85
|
+
for var in ("GH_HOST", "GITHUB_HOST"):
|
|
86
|
+
value = os.environ.get(var, "").strip().lower()
|
|
87
|
+
if value:
|
|
88
|
+
hosts.add(value)
|
|
89
|
+
server = os.environ.get("GITHUB_SERVER_URL", "").strip().lower()
|
|
90
|
+
if server:
|
|
91
|
+
hosts.add(re.sub(r"^https?://", "", server).split("/")[0])
|
|
92
|
+
return hosts
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def ensure_github(remote: Remote) -> None:
|
|
96
|
+
if remote.host == "github.com" or remote.host in enterprise_hosts():
|
|
97
|
+
return
|
|
98
|
+
raise Refused(
|
|
99
|
+
f"The origin remote is on {remote.host!r}, which is not GitHub.\n"
|
|
100
|
+
" argocd mode opens pull requests only on GitHub or GitHub Enterprise Server in v1.\n"
|
|
101
|
+
" For a GitHub Enterprise Server, set GH_HOST=<host> (and GH_ENTERPRISE_TOKEN or GITHUB_TOKEN)."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def origin_url() -> str:
|
|
106
|
+
result = run_cmd(["git", "remote", "get-url", "origin"], check=False, quiet=True)
|
|
107
|
+
url = (result.stdout or "").strip()
|
|
108
|
+
if result.returncode != 0 or not url:
|
|
109
|
+
raise ConfigError("This project has no git remote named 'origin'; argocd mode needs one.")
|
|
110
|
+
return url
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def short_sha() -> str | None:
|
|
114
|
+
try:
|
|
115
|
+
result = run_cmd(["git", "rev-parse", "--short", "HEAD"], check=False, quiet=True)
|
|
116
|
+
except ToolFailed:
|
|
117
|
+
return None
|
|
118
|
+
if result.returncode != 0:
|
|
119
|
+
return None
|
|
120
|
+
return (result.stdout or "").strip() or None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# Project paths the scaffold's .dockerignore keeps out of the image: changing them
|
|
124
|
+
# (chart values, Chart.lock written by `helm dependency build`, workflows, tests)
|
|
125
|
+
# does not change what a rebuild would contain.
|
|
126
|
+
NOT_IN_IMAGE = ("deployment", ".github", "tests", "docs")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def worktree_dirty() -> bool | None:
|
|
130
|
+
"""Whether the image's sources in the project have uncommitted changes (``None`` outside git).
|
|
131
|
+
|
|
132
|
+
Limited to the project directory (``-- .``), so edits elsewhere in a monorepo
|
|
133
|
+
do not count, and to paths that reach the image (``NOT_IN_IMAGE`` excluded).
|
|
134
|
+
Untracked files count (a new module not yet added is part of the build
|
|
135
|
+
context), ignored ones do not.
|
|
136
|
+
"""
|
|
137
|
+
excludes = [f":(exclude){path}" for path in NOT_IN_IMAGE]
|
|
138
|
+
try:
|
|
139
|
+
result = run_cmd(
|
|
140
|
+
["git", "status", "--porcelain", "--", ".", *excludes], check=False, quiet=True
|
|
141
|
+
)
|
|
142
|
+
except ToolFailed:
|
|
143
|
+
return None
|
|
144
|
+
if result.returncode != 0:
|
|
145
|
+
return None
|
|
146
|
+
return bool((result.stdout or "").strip())
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def branch_name(env: str, tag: str) -> str:
|
|
150
|
+
safe = re.sub(r"[^A-Za-z0-9._-]+", "-", tag).strip("-.") or "image"
|
|
151
|
+
return f"deploy/{env}/{safe}"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def github_token() -> str | None:
|
|
155
|
+
for var in ("GITHUB_TOKEN", "GH_TOKEN", "GH_ENTERPRISE_TOKEN"):
|
|
156
|
+
value = os.environ.get(var)
|
|
157
|
+
if value:
|
|
158
|
+
return value
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass
|
|
163
|
+
class PullRequestResult:
|
|
164
|
+
branch: str
|
|
165
|
+
url: str | None
|
|
166
|
+
created: bool
|
|
167
|
+
changed: bool
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def repo_relative(path: Path) -> str:
|
|
171
|
+
"""``path`` (project-relative, cwd = project root) as git index and tree paths need it.
|
|
172
|
+
|
|
173
|
+
``git rev-parse --show-prefix`` is empty at the repository toplevel and
|
|
174
|
+
``apps/agent/`` for a project below it; ``update-index --cacheinfo`` and
|
|
175
|
+
``<ref>:<path>`` take that repo-relative form, unlike ``hash-object``.
|
|
176
|
+
"""
|
|
177
|
+
result = run_cmd(["git", "rev-parse", "--show-prefix"], check=False, quiet=True)
|
|
178
|
+
if result.returncode != 0:
|
|
179
|
+
raise ConfigError("This project is not inside a git repository; argocd mode needs one.")
|
|
180
|
+
prefix = (result.stdout or "").strip()
|
|
181
|
+
if path.is_absolute():
|
|
182
|
+
try:
|
|
183
|
+
path = path.resolve().relative_to(Path.cwd().resolve())
|
|
184
|
+
except ValueError as e:
|
|
185
|
+
raise ConfigError(f"{path} is outside the project directory {Path.cwd()}.") from e
|
|
186
|
+
rel = path.as_posix()
|
|
187
|
+
if rel == ".." or rel.startswith("../") or "/../" in rel:
|
|
188
|
+
raise ConfigError(f"{path} is outside the project directory; argocd mode cannot commit it.")
|
|
189
|
+
return f"{prefix}{rel}"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _resolve_base(base: str, *, dry_run: bool, console: Console) -> str:
|
|
193
|
+
"""Fetch ``origin/<base>`` and return the ref to branch from (``HEAD`` when unavailable)."""
|
|
194
|
+
run_cmd(["git", "fetch", "origin", base], check=False, dry_run=dry_run, console=console)
|
|
195
|
+
base_ref = f"origin/{base}"
|
|
196
|
+
probe = run_cmd(["git", "rev-parse", "--verify", "--quiet", base_ref], check=False, quiet=True)
|
|
197
|
+
if probe.returncode != 0:
|
|
198
|
+
if dry_run:
|
|
199
|
+
console.print(
|
|
200
|
+
f" [dry-run] origin/{base} is not available locally; the real run would branch "
|
|
201
|
+
"from HEAD.",
|
|
202
|
+
style="yellow",
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
console.print(
|
|
206
|
+
f" origin/{base} is not available locally; branching from HEAD instead.",
|
|
207
|
+
style="yellow",
|
|
208
|
+
)
|
|
209
|
+
base_ref = "HEAD"
|
|
210
|
+
return base_ref
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _base_values_text(base_ref: str, index_path: str, values_path: Path, *, dry_run: bool) -> str:
|
|
214
|
+
"""The values file as ``base_ref`` holds it (the content the PR is built on)."""
|
|
215
|
+
result = run_cmd(
|
|
216
|
+
["git", "cat-file", "blob", f"{base_ref}:{index_path}"], check=False, quiet=True
|
|
217
|
+
)
|
|
218
|
+
if result.returncode == 0 and (result.stdout or "").strip():
|
|
219
|
+
return result.stdout
|
|
220
|
+
if dry_run and values_path.is_file():
|
|
221
|
+
return values_path.read_text(encoding="utf-8")
|
|
222
|
+
raise ConfigError(
|
|
223
|
+
f"{index_path} is not committed on {base_ref}; commit it there first.\n"
|
|
224
|
+
" argocd mode rewrites image.tag in the copy the pull request is based on, "
|
|
225
|
+
"never the working tree."
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _remote_branch_sha(branch: str) -> str:
|
|
230
|
+
"""The commit ``origin`` holds for ``refs/heads/<branch>``; ``""`` when the branch is absent."""
|
|
231
|
+
ref = f"refs/heads/{branch}"
|
|
232
|
+
result = run_cmd(["git", "ls-remote", "--heads", "origin", ref], check=False, quiet=True)
|
|
233
|
+
if result.returncode != 0:
|
|
234
|
+
raise ToolFailed(
|
|
235
|
+
f"git ls-remote origin {ref} failed (exit code {result.returncode}):\n"
|
|
236
|
+
f"{(result.stderr or result.stdout or '').strip()}"
|
|
237
|
+
)
|
|
238
|
+
for line in (result.stdout or "").splitlines():
|
|
239
|
+
sha, _, name = line.partition("\t")
|
|
240
|
+
if name.strip() == ref:
|
|
241
|
+
return sha.strip()
|
|
242
|
+
return ""
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _rev(spec: str) -> str | None:
|
|
246
|
+
result = run_cmd(["git", "rev-parse", "--verify", "--quiet", spec], check=False, quiet=True)
|
|
247
|
+
if result.returncode != 0:
|
|
248
|
+
return None
|
|
249
|
+
return (result.stdout or "").strip() or None
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _remote_has_same_change(remote_sha: str, branch: str, tree: str, base_ref: str) -> bool:
|
|
253
|
+
"""True when ``origin/<branch>`` already is this change: same tree, parented on the base."""
|
|
254
|
+
if _rev(f"{remote_sha}^{{commit}}") is None:
|
|
255
|
+
# A fresh (shallow) CI checkout has never seen the branch: fetch just that commit.
|
|
256
|
+
run_cmd(
|
|
257
|
+
["git", "fetch", "--no-tags", "origin", f"refs/heads/{branch}"],
|
|
258
|
+
check=False,
|
|
259
|
+
quiet=True,
|
|
260
|
+
)
|
|
261
|
+
remote_tree = _rev(f"{remote_sha}^{{tree}}")
|
|
262
|
+
remote_parent = _rev(f"{remote_sha}^")
|
|
263
|
+
base_commit = _rev(f"{base_ref}^{{commit}}")
|
|
264
|
+
return (
|
|
265
|
+
bool(remote_tree)
|
|
266
|
+
and remote_tree == tree
|
|
267
|
+
and bool(base_commit)
|
|
268
|
+
and (remote_parent == base_commit)
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _commit_single_file(
|
|
273
|
+
*,
|
|
274
|
+
index_path: str,
|
|
275
|
+
content: str,
|
|
276
|
+
branch: str,
|
|
277
|
+
base_ref: str,
|
|
278
|
+
message: str,
|
|
279
|
+
dry_run: bool,
|
|
280
|
+
console: Console,
|
|
281
|
+
) -> None:
|
|
282
|
+
"""Commit ``content`` at ``index_path`` on ``branch`` from ``base_ref`` and push it.
|
|
283
|
+
|
|
284
|
+
No checkout switch. The branch is bot-owned and deterministic (one tag, one
|
|
285
|
+
change), so a re-run replaces it. The push leases on the commit ``origin``
|
|
286
|
+
holds right now (read with ``ls-remote``, ``""`` for "must not exist"): a
|
|
287
|
+
bare ``--force-with-lease`` compares against a remote-tracking ref that a
|
|
288
|
+
fresh CI checkout does not have and is rejected as "stale info", while an
|
|
289
|
+
explicit lease still refuses to overwrite a push that lands in between.
|
|
290
|
+
When ``origin`` already holds the same change on the same base, nothing is
|
|
291
|
+
pushed, so a retried promotion does not reset an approved pull request.
|
|
292
|
+
"""
|
|
293
|
+
blob = run_cmd(
|
|
294
|
+
["git", "hash-object", "-w", "--stdin"],
|
|
295
|
+
input_text=content,
|
|
296
|
+
dry_run=dry_run,
|
|
297
|
+
console=console,
|
|
298
|
+
).stdout.strip()
|
|
299
|
+
blob = blob or "<blob>"
|
|
300
|
+
with tempfile.TemporaryDirectory(prefix="graph-agents-cli-index-") as tmp:
|
|
301
|
+
env = {"GIT_INDEX_FILE": str(Path(tmp) / "index")}
|
|
302
|
+
run_cmd(["git", "read-tree", base_ref], env=env, dry_run=dry_run, console=console)
|
|
303
|
+
run_cmd(
|
|
304
|
+
["git", "update-index", "--add", "--cacheinfo", f"100644,{blob},{index_path}"],
|
|
305
|
+
env=env,
|
|
306
|
+
dry_run=dry_run,
|
|
307
|
+
console=console,
|
|
308
|
+
)
|
|
309
|
+
tree = run_cmd(
|
|
310
|
+
["git", "write-tree"], env=env, dry_run=dry_run, console=console
|
|
311
|
+
).stdout.strip()
|
|
312
|
+
tree = tree or "<tree>"
|
|
313
|
+
remote_sha = "<sha on origin, empty when absent>" if dry_run else _remote_branch_sha(branch)
|
|
314
|
+
if not dry_run and remote_sha and _remote_has_same_change(remote_sha, branch, tree, base_ref):
|
|
315
|
+
run_cmd(["git", "update-ref", f"refs/heads/{branch}", remote_sha], console=console)
|
|
316
|
+
console.print(
|
|
317
|
+
f" origin/{branch} already holds this change on {base_ref}; nothing to push.",
|
|
318
|
+
markup=False,
|
|
319
|
+
)
|
|
320
|
+
return
|
|
321
|
+
commit = run_cmd(
|
|
322
|
+
["git", "commit-tree", tree, "-p", base_ref, "-m", message],
|
|
323
|
+
dry_run=dry_run,
|
|
324
|
+
console=console,
|
|
325
|
+
).stdout.strip()
|
|
326
|
+
commit = commit or "<commit>"
|
|
327
|
+
run_cmd(["git", "update-ref", f"refs/heads/{branch}", commit], dry_run=dry_run, console=console)
|
|
328
|
+
run_cmd(
|
|
329
|
+
[
|
|
330
|
+
"git",
|
|
331
|
+
"push",
|
|
332
|
+
f"--force-with-lease=refs/heads/{branch}:{remote_sha}",
|
|
333
|
+
"-u",
|
|
334
|
+
"origin",
|
|
335
|
+
f"{branch}:{branch}",
|
|
336
|
+
],
|
|
337
|
+
dry_run=dry_run,
|
|
338
|
+
console=console,
|
|
339
|
+
capture=False,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _pr_via_gh(
|
|
344
|
+
*,
|
|
345
|
+
remote: Remote,
|
|
346
|
+
branch: str,
|
|
347
|
+
base: str,
|
|
348
|
+
title: str,
|
|
349
|
+
body: str,
|
|
350
|
+
dry_run: bool,
|
|
351
|
+
console: Console,
|
|
352
|
+
) -> tuple[str | None, bool]:
|
|
353
|
+
listing = run_cmd(
|
|
354
|
+
[
|
|
355
|
+
"gh",
|
|
356
|
+
"pr",
|
|
357
|
+
"list",
|
|
358
|
+
"--repo",
|
|
359
|
+
remote.gh_repo,
|
|
360
|
+
"--head",
|
|
361
|
+
branch,
|
|
362
|
+
"--base",
|
|
363
|
+
base,
|
|
364
|
+
"--state",
|
|
365
|
+
"open",
|
|
366
|
+
"--json",
|
|
367
|
+
"number,url",
|
|
368
|
+
],
|
|
369
|
+
dry_run=dry_run,
|
|
370
|
+
console=console,
|
|
371
|
+
)
|
|
372
|
+
existing: list[dict] = []
|
|
373
|
+
if listing.stdout.strip():
|
|
374
|
+
try:
|
|
375
|
+
existing = json.loads(listing.stdout)
|
|
376
|
+
except json.JSONDecodeError:
|
|
377
|
+
existing = []
|
|
378
|
+
if existing:
|
|
379
|
+
number = str(existing[0].get("number"))
|
|
380
|
+
run_cmd(
|
|
381
|
+
[
|
|
382
|
+
"gh",
|
|
383
|
+
"pr",
|
|
384
|
+
"edit",
|
|
385
|
+
number,
|
|
386
|
+
"--repo",
|
|
387
|
+
remote.gh_repo,
|
|
388
|
+
"--title",
|
|
389
|
+
title,
|
|
390
|
+
"--body",
|
|
391
|
+
body,
|
|
392
|
+
],
|
|
393
|
+
dry_run=dry_run,
|
|
394
|
+
console=console,
|
|
395
|
+
)
|
|
396
|
+
return existing[0].get("url"), False
|
|
397
|
+
created = run_cmd(
|
|
398
|
+
[
|
|
399
|
+
"gh",
|
|
400
|
+
"pr",
|
|
401
|
+
"create",
|
|
402
|
+
"--repo",
|
|
403
|
+
remote.gh_repo,
|
|
404
|
+
"--base",
|
|
405
|
+
base,
|
|
406
|
+
"--head",
|
|
407
|
+
branch,
|
|
408
|
+
"--title",
|
|
409
|
+
title,
|
|
410
|
+
"--body",
|
|
411
|
+
body,
|
|
412
|
+
],
|
|
413
|
+
dry_run=dry_run,
|
|
414
|
+
console=console,
|
|
415
|
+
)
|
|
416
|
+
url = (created.stdout or "").strip().splitlines()
|
|
417
|
+
return (url[-1] if url else None), True
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _pr_via_rest(
|
|
421
|
+
*,
|
|
422
|
+
remote: Remote,
|
|
423
|
+
token: str,
|
|
424
|
+
branch: str,
|
|
425
|
+
base: str,
|
|
426
|
+
title: str,
|
|
427
|
+
body: str,
|
|
428
|
+
dry_run: bool,
|
|
429
|
+
console: Console,
|
|
430
|
+
) -> tuple[str | None, bool]:
|
|
431
|
+
pulls = f"{remote.api_base}/repos/{remote.owner}/{remote.repo}/pulls"
|
|
432
|
+
headers = {
|
|
433
|
+
"Authorization": f"Bearer {token}",
|
|
434
|
+
"Accept": "application/vnd.github+json",
|
|
435
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
436
|
+
}
|
|
437
|
+
console.print(
|
|
438
|
+
f" {'[dry-run] ' if dry_run else ''}GET {pulls}?head={remote.owner}:{branch}&base={base}",
|
|
439
|
+
style="cyan",
|
|
440
|
+
markup=False,
|
|
441
|
+
)
|
|
442
|
+
if dry_run:
|
|
443
|
+
console.print(
|
|
444
|
+
f" [dry-run] POST {pulls} (or PATCH the open pull request)", style="cyan", markup=False
|
|
445
|
+
)
|
|
446
|
+
return None, False
|
|
447
|
+
from graph_agents_cli import _http
|
|
448
|
+
|
|
449
|
+
# Honours the environment's proxies; one that cannot be used is a one-line error.
|
|
450
|
+
with _http.client(pulls, headers=headers, timeout=30) as client:
|
|
451
|
+
resp = client.get(
|
|
452
|
+
pulls, params={"head": f"{remote.owner}:{branch}", "base": base, "state": "open"}
|
|
453
|
+
)
|
|
454
|
+
if resp.status_code != 200:
|
|
455
|
+
raise ToolFailed(
|
|
456
|
+
f"GitHub API GET {pulls} returned {resp.status_code}: {resp.text[:200]}"
|
|
457
|
+
)
|
|
458
|
+
existing = resp.json()
|
|
459
|
+
if existing:
|
|
460
|
+
number = existing[0]["number"]
|
|
461
|
+
resp = client.patch(f"{pulls}/{number}", json={"title": title, "body": body})
|
|
462
|
+
if resp.status_code not in (200, 201):
|
|
463
|
+
raise ToolFailed(f"GitHub API PATCH {pulls}/{number} returned {resp.status_code}")
|
|
464
|
+
return existing[0].get("html_url"), False
|
|
465
|
+
resp = client.post(pulls, json={"title": title, "body": body, "head": branch, "base": base})
|
|
466
|
+
if resp.status_code not in (200, 201):
|
|
467
|
+
raise ToolFailed(
|
|
468
|
+
f"GitHub API POST {pulls} returned {resp.status_code}: {resp.text[:200]}"
|
|
469
|
+
)
|
|
470
|
+
return resp.json().get("html_url"), True
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def write_desired_state(
|
|
474
|
+
*,
|
|
475
|
+
env: str,
|
|
476
|
+
values_path: Path,
|
|
477
|
+
image_repository: str | None,
|
|
478
|
+
tag: str,
|
|
479
|
+
project_name: str,
|
|
480
|
+
base: str = DEFAULT_BASE_BRANCH,
|
|
481
|
+
dry_run: bool = False,
|
|
482
|
+
console: Console | None = None,
|
|
483
|
+
) -> PullRequestResult:
|
|
484
|
+
"""Rewrite the tag on the base branch's copy, commit on ``deploy/<env>/<tag>``, push, open the PR.
|
|
485
|
+
|
|
486
|
+
The content comes from ``origin/<base>`` (``HEAD`` when unavailable), so a
|
|
487
|
+
checkout behind ``main`` or with uncommitted edits never leaks into the
|
|
488
|
+
pull request, and "nothing to change" is judged against the base branch.
|
|
489
|
+
The developer's working tree is not modified.
|
|
490
|
+
"""
|
|
491
|
+
console = console or Console()
|
|
492
|
+
remote = parse_remote(origin_url())
|
|
493
|
+
ensure_github(remote)
|
|
494
|
+
|
|
495
|
+
index_path = repo_relative(values_path)
|
|
496
|
+
base_ref = _resolve_base(base, dry_run=dry_run, console=console)
|
|
497
|
+
base_text = _base_values_text(base_ref, index_path, values_path, dry_run=dry_run)
|
|
498
|
+
old, new_text, changed = rewrite_image_tag(base_text, tag, source=f"{base_ref}:{index_path}")
|
|
499
|
+
if not changed:
|
|
500
|
+
console.print(
|
|
501
|
+
f" {base_ref}:{index_path} already has image.tag {tag!r}; nothing to change.",
|
|
502
|
+
markup=False,
|
|
503
|
+
)
|
|
504
|
+
return PullRequestResult(
|
|
505
|
+
branch=branch_name(env, tag), url=None, created=False, changed=False
|
|
506
|
+
)
|
|
507
|
+
verb = "[dry-run] would set" if dry_run else "Setting"
|
|
508
|
+
console.print(
|
|
509
|
+
f" {verb} image.tag {old!r} -> {tag!r} in {index_path} (from {base_ref}; "
|
|
510
|
+
"the working tree is left unchanged).",
|
|
511
|
+
style="cyan" if dry_run else None,
|
|
512
|
+
markup=False,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
branch = branch_name(env, tag)
|
|
516
|
+
image = f"{image_repository}:{tag}" if image_repository else tag
|
|
517
|
+
title = f"deploy({env}): {project_name} -> {tag}"
|
|
518
|
+
body = (
|
|
519
|
+
f"Set `image.tag` to `{tag}` in `{index_path}`.\n\n"
|
|
520
|
+
f"Image: `{image}`\n\n"
|
|
521
|
+
"Opened by `graph-agents-cli deploy`. Argo CD reconciles this environment from "
|
|
522
|
+
f"`{base}` once the pull request merges."
|
|
523
|
+
)
|
|
524
|
+
_commit_single_file(
|
|
525
|
+
index_path=index_path,
|
|
526
|
+
content=new_text,
|
|
527
|
+
branch=branch,
|
|
528
|
+
base_ref=base_ref,
|
|
529
|
+
message=title,
|
|
530
|
+
dry_run=dry_run,
|
|
531
|
+
console=console,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
if _kube.tool_available("gh"):
|
|
535
|
+
url, created = _pr_via_gh(
|
|
536
|
+
remote=remote,
|
|
537
|
+
branch=branch,
|
|
538
|
+
base=base,
|
|
539
|
+
title=title,
|
|
540
|
+
body=body,
|
|
541
|
+
dry_run=dry_run,
|
|
542
|
+
console=console,
|
|
543
|
+
)
|
|
544
|
+
else:
|
|
545
|
+
token = github_token()
|
|
546
|
+
if not token:
|
|
547
|
+
raise ConfigError(
|
|
548
|
+
"Neither the gh CLI nor a GITHUB_TOKEN is available to open the pull request.\n"
|
|
549
|
+
f" The branch {branch} was pushed; install gh (https://cli.github.com/) or export "
|
|
550
|
+
"GITHUB_TOKEN and re-run, or open the pull request by hand."
|
|
551
|
+
)
|
|
552
|
+
url, created = _pr_via_rest(
|
|
553
|
+
remote=remote,
|
|
554
|
+
token=token,
|
|
555
|
+
branch=branch,
|
|
556
|
+
base=base,
|
|
557
|
+
title=title,
|
|
558
|
+
body=body,
|
|
559
|
+
dry_run=dry_run,
|
|
560
|
+
console=console,
|
|
561
|
+
)
|
|
562
|
+
return PullRequestResult(branch=branch, url=url, created=created, changed=changed)
|