keboola-cli 0.65.0__tar.gz → 0.66.0__tar.gz
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.
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/CLAUDE.md +13 -8
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/PKG-INFO +1 -1
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/pyproject.toml +1 -1
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/hatch_build.py +13 -1
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/__init__.py +4 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/changelog.py +46 -1
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/cli.py +5 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/client.py +278 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/_data_app_git.py +0 -67
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/context.py +18 -12
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/serve.py +9 -0
- keboola_cli-0.66.0/src/keboola_agent_cli/commands/token.py +197 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/lib.py +100 -1
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/permissions.py +5 -1
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/result_models.py +74 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/app.py +59 -7
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/dependencies.py +3 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/data_apps.py +0 -35
- keboola_cli-0.66.0/src/keboola_agent_cli/server/routers/token.py +74 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_encryption.py +21 -2
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/data_app_service.py +20 -214
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/stream_service.py +6 -30
- keboola_cli-0.66.0/src/keboola_agent_cli/services/token_service.py +114 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/version_service.py +24 -8
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/stream_client.py +53 -1
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_build_hook.py +12 -2
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_client.py +44 -0
- keboola_cli-0.66.0/tests/test_client_device_enrollment.py +364 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_data_app_cli.py +0 -69
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_data_app_service.py +4 -67
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_e2e.py +241 -23
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_encryption.py +49 -0
- keboola_cli-0.66.0/tests/test_lib_device_enrollment.py +255 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_serve_ui.py +60 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_server_router_calls.py +0 -20
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_server_smoke.py +1 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_encrypt.py +6 -3
- keboola_cli-0.66.0/tests/test_token_cli.py +190 -0
- keboola_cli-0.66.0/tests/test_token_service.py +143 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_version_service.py +43 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/.gitignore +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/CONTRIBUTING.md +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/Makefile +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/README.md +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/examples/README.md +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/examples/storage_tui/README.md +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/plugins/kbagent/.claude-plugin/CLAUDE.md +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/benchmark.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/check_command_sync.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/check_error_codes.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/check_wheel_ui.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/debug_linkedby_type.sh +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/generate_changelog.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/generate_skill.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/lineage_deep.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/post-edit-quality.sh +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/pre-commit +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/sync_version.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/scripts/web-dev.sh +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/__main__.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/ai_client.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/auto_update.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/__init__.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/_data_app_runtime.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/_helpers.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/_metadata_input.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/_semantic_layer_crud.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/_semantic_layer_helpers.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/_semantic_layer_reference_data.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/agent.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/branch.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/changelog.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/component.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/config.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/data_app.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/dev_portal.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/doctor.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/encrypt.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/feature.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/flow.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/http_client.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/init.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/job.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/kai.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/lineage.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/org.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/permissions.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/project.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/repl.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/schedule.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/search.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/semantic_layer.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/sharing.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/storage.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/stream.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/sync.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/tool.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/version.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/commands/workspace.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/config_store.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/constants.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/data_science_client.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/dev_portal_client.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/errors.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/http_base.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/json_utils.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/manage_client.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/metastore_client.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/models.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/output.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/py.typed +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/__init__.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/agent_runner.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/agents_store.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/auth.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/pricing.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/__init__.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/agents.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/ai_chat.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/branches.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/components.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/configs.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/dev_portal.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/encrypt.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/feature.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/flows.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/health.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/jobs.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/kai.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/lineage.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/mcp.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/members.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/org.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/projects.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/schedules.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/search.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/semantic_layer.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/sharing.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/storage.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/stream.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/routers/workspaces.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/run_broadcaster.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/server/sse.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/__init__.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_semantic_layer_cascade.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_semantic_layer_crud.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_semantic_layer_internals.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_semantic_layer_lookup.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_semantic_layer_reference_data.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_sync_bindings.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_sync_branch.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_sync_bulk.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_sync_clone.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_sync_models.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_sync_push_ops.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_sync_storage.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/_sync_writeback.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/agent_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/base.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/branch_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/component_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/config_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/data_app_git_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/deep_lineage_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/dev_portal_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/doctor_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/encrypt_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/feature_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/flow_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/flow_validation.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/http_forwarder_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/job_idempotency_store.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/job_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/kai_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/lineage_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/mcp_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/mcp_transport.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/member_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/org_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/project_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/repo_validate_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/schedule_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/search_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/semantic_layer_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/sharing_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/storage_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/sync_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/variables_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/services/workspace_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/sync/__init__.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/sync/branch_mapping.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/sync/clone.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/sync/code_extraction.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/sync/config_format.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/sync/diff_engine.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/sync/git_utils.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/sync/manifest.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/sync/naming.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/sync/secrets.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/src/keboola_agent_cli/sync/sql_split.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/__init__.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/conftest.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/helpers.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_agent_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_agent_prompt.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_agent_prompt_helper.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_agent_runner.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_agent_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_agents_store_events.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_ai_client.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_auto_update.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_base_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_branch_metadata_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_branch_metadata_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_branch_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_changelog_check.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_changelog_render.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_check_command_sync.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_choice_enums.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_component_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_component_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_create_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_create_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_encryption.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_metadata.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_rename.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_rename_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_resolution.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_row_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_row_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_search.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_search_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_set_default_bucket.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_store.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_config_update.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_data_app_git_repo.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_data_app_plain_env_keys.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_data_app_secrets_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_data_app_secrets_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_data_app_validate_repo_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_deep_lineage_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_dev_portal_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_dev_portal_client.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_dev_portal_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_doctor_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_e2e_config_dir_mode.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_e2e_lineage_deep.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_encrypt_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_encrypt_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_errors.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_feature_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_feature_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_file_locking.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_flow_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_flow_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_flow_validation.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_helpers.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_http_base.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_http_client_cmd.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_http_forwarder_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_integration.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_job_idempotency_store.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_json_utils.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_kai_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_kai_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_lib.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_lineage_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_local_ai_chat.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_manage_client.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_manage_token_bulk.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_manage_token_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_mcp_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_mcp_transport.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_member_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_member_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_metastore_client.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_models.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_normalize_script.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_org_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_output.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_permissions.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_permissions_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_pricing.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_project_edit.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_project_edit_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_project_info_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_project_info_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_repl.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_result_models.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_run_broadcaster.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_schedule_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_schedule_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_search_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_search_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_semantic_layer_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_semantic_layer_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_serve_conversation_id.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_server_semantic_layer_routes_e2e.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_services.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sharing_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sql_split.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_storage_clone.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_storage_delete.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_storage_describe_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_storage_describe_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_storage_empty_definition.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_storage_export_filters.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_storage_files.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_storage_swap.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_storage_tables.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_storage_truncate.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_storage_write.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_stream_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_stream_client.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_stream_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_branch_mapping.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_clone.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_code_extraction.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_config_format.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_diff_engine.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_e2e.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_force_pull_baseline.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_manifest.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_naming.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_plaintext_audit.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_secrets.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_storage_jobs.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_sync_version_script.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_variables_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_variables_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_workspace_cli.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_workspace_gc.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_workspace_service.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/tests/test_workspace_sql_helper.py +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/README.md +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/index.html +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/package-lock.json +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/package.json +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/postcss.config.js +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/App.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/api/client.ts +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/components/AgentRunView.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/components/Drawer.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/components/Empty.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/components/JsonView.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/components/ManageTokenModal.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/components/Table.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/index.css +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/layout/Shell.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/layout/Sidebar.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/layout/StatusBar.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/layout/TopBar.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/main.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Agents.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Branches.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Changelog.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Components.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Configs.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Dashboard.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/DataApps.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Doctor.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Encrypt.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Flows.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Jobs.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Lineage.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/LocalAi.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Mcp.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Members.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Org.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Projects.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Schedules.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Search.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/SemanticLayer.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/SemanticLayerDialogs.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Sharing.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Storage.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Streams.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/pages/Workspaces.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/state.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/theme.tsx +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/src/types.ts +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/tailwind.config.ts +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/tsconfig.json +0 -0
- {keboola_cli-0.65.0 → keboola_cli-0.66.0}/web/frontend/vite.config.ts +0 -0
|
@@ -385,6 +385,14 @@ kbagent feature user-show --project ALIAS --email EMAIL
|
|
|
385
385
|
kbagent feature user-add --project ALIAS --email EMAIL --feature NAME [--dry-run] [--yes]
|
|
386
386
|
kbagent feature user-remove --project ALIAS --email EMAIL --feature NAME [--dry-run] [--yes]
|
|
387
387
|
|
|
388
|
+
# token: scoped Storage tokens (Keboola single-bucket-write pattern; acting token needs canManageTokens; secret shown once).
|
|
389
|
+
kbagent token create --project NAME --description DESC [--bucket-write BUCKET ...] [--bucket-read BUCKET ...] [--component-access ID ...] [--can-read-all-file-uploads] [--expires-in N]
|
|
390
|
+
kbagent token delete --project NAME --token-id ID [--yes]
|
|
391
|
+
kbagent token refresh --project NAME --token-id ID [--yes]
|
|
392
|
+
# SDK (importable Client(url,token)) now exposes create_scoped_token / delete_token / refresh_token /
|
|
393
|
+
# create_stream_source / get_stream_source / list_stream_sources / delete_stream_source: dicts on .raw,
|
|
394
|
+
# typed ScopedTokenResult / StreamSourceResult on the facade. See docs/sdk.md.
|
|
395
|
+
|
|
388
396
|
# permissions: session write/destructive firewall. The top-level --deny-writes / --deny-destructive
|
|
389
397
|
# flags are the one-shot form; `permissions set` persists a policy (mode allow|deny + allow/deny patterns
|
|
390
398
|
# like cli:write, cli:destructive, tool:write). The agent guards rails against mistakes; not a sandbox.
|
|
@@ -430,13 +438,11 @@ kbagent data-app create --project ALIAS --name NAME --slug SLUG (--git-repo URL
|
|
|
430
438
|
# NO parameters.dataApp.git block, and forces --no-deploy (nothing to run yet). Mutually exclusive with
|
|
431
439
|
# --git-repo and all --git-*/PAT auth flags. Full flow to a RUNNING managed-repo app (0.65.0+):
|
|
432
440
|
# (1) create --use-managed-git-repo -> (2) `git-credentials-create --type http_token --permissions
|
|
433
|
-
# readWrite` + `git push` code to the managed repo URL (from `git-repo`) -> (3) `data-app
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
#
|
|
438
|
-
# managedGitRepoId). Use `data-app runs` to debug a deploy that reverts to stopped (setup-phase
|
|
439
|
-
# failures, e.g. git-clone "could not read Username", produce no container logs).
|
|
441
|
+
# readWrite` + `git push` code to the managed repo URL (from `git-repo`) -> (3) `data-app deploy`.
|
|
442
|
+
# The platform injects the clone credentials at deploy time, so no credential wiring is needed.
|
|
443
|
+
# deploy pins the LATEST configVersion when a git block is present and omits it for a PURE managed
|
|
444
|
+
# repo (deploys from managedGitRepoId). Use `data-app runs` to debug a deploy that reverts to
|
|
445
|
+
# stopped (setup-phase failures produce no container logs).
|
|
440
446
|
kbagent data-app deploy --project NAME --app-id ID [--config-version N] [--wait] [--timeout SECONDS] [--branch ID]
|
|
441
447
|
kbagent data-app start --project NAME --app-id ID [--wait] [--timeout SECONDS]
|
|
442
448
|
kbagent data-app stop --project NAME --app-id ID [--wait] [--timeout SECONDS]
|
|
@@ -454,7 +460,6 @@ kbagent data-app git-branches --project NAME --app-id ID
|
|
|
454
460
|
kbagent data-app git-entrypoints --project NAME --app-id ID
|
|
455
461
|
kbagent data-app git-credentials --project NAME --app-id ID
|
|
456
462
|
kbagent data-app git-credentials-create --project NAME --app-id ID --type ssh_key|http_token --permissions readOnly|readWrite [--public-key KEY | --public-key-file PATH] [--name LABEL] [--yes]
|
|
457
|
-
kbagent data-app git-bind-credential --project NAME --app-id ID [--branch-name main] [--permissions readOnly|readWrite] [--dry-run]
|
|
458
463
|
# git-repo/git-branches/git-entrypoints introspect the deployed-from git repo (sandboxes-service /apps/{id}/git-repo/*); they return 409 "no Git repository configured" until the app has been DEPLOYED at least once (git config syncs Storage->DS record at deploy). git-credentials* manage credentials for a MANAGED repo only; apps from `data-app create --git-repo` are external => git-credentials-create returns 409. http_token mints a ONE-TIME secret (shown once); credentials endpoints need an admin storage token.
|
|
459
464
|
|
|
460
465
|
kbagent component list [--project NAME] [--type TYPE] [--query QUERY]
|
|
@@ -138,7 +138,19 @@ def _bundle_ui(repo_root: Path, log: Callable[[str], None] = print) -> None:
|
|
|
138
138
|
log("no prebuilt dist found; running npm build")
|
|
139
139
|
try:
|
|
140
140
|
subprocess.check_call(
|
|
141
|
-
|
|
141
|
+
# --ignore-scripts: do NOT run npm lifecycle scripts from the
|
|
142
|
+
# (transitive) dependency tree at wheel-build / `git+` install time.
|
|
143
|
+
# Without it, a compromised dependency's postinstall would execute
|
|
144
|
+
# arbitrary code on the user's machine during install (GHSA-pfvh).
|
|
145
|
+
# Verified non-breaking: `tsc -b && vite build` needs no dependency
|
|
146
|
+
# install-scripts -- vite 8 (rolldown) ships prebuilt platform
|
|
147
|
+
# binaries rather than fetching them via a postinstall.
|
|
148
|
+
# NOTE: this also suppresses any *root* web/frontend/package.json
|
|
149
|
+
# prepare/postinstall script (there is none today). If one is ever
|
|
150
|
+
# added AND required for the build, invoke it explicitly here rather
|
|
151
|
+
# than dropping --ignore-scripts, which would re-open the dependency
|
|
152
|
+
# lifecycle-script RCE surface.
|
|
153
|
+
[npm, "ci", "--prefer-offline", "--no-audit", "--no-fund", "--ignore-scripts"],
|
|
142
154
|
cwd=frontend_dir,
|
|
143
155
|
)
|
|
144
156
|
subprocess.check_call([npm, "run", "build"], cwd=frontend_dir)
|
|
@@ -9,6 +9,8 @@ from .result_models import (
|
|
|
9
9
|
ConfigDetailResult,
|
|
10
10
|
JobResult,
|
|
11
11
|
QueryResult,
|
|
12
|
+
ScopedTokenResult,
|
|
13
|
+
StreamSourceResult,
|
|
12
14
|
SyncPushResult,
|
|
13
15
|
UploadTableResult,
|
|
14
16
|
)
|
|
@@ -28,6 +30,8 @@ __all__ = [
|
|
|
28
30
|
"JobIdempotencyStore",
|
|
29
31
|
"JobResult",
|
|
30
32
|
"QueryResult",
|
|
33
|
+
"ScopedTokenResult",
|
|
34
|
+
"StreamSourceResult",
|
|
31
35
|
"SyncPushResult",
|
|
32
36
|
"UploadTableResult",
|
|
33
37
|
"__version__",
|
|
@@ -24,6 +24,49 @@ from .constants import CHANGELOG_HEADLINE_MAX_CHARS
|
|
|
24
24
|
|
|
25
25
|
# Ordered newest-first. Each value is a list of brief one-line descriptions.
|
|
26
26
|
CHANGELOG: dict[str, list[str]] = {
|
|
27
|
+
"0.66.0": [
|
|
28
|
+
"New: device-enrollment primitives on the importable library -- a hosted Data App can now mint "
|
|
29
|
+
"per-device credentials in-process (no CLI subprocess, no master token on the device). "
|
|
30
|
+
"`Client(url, token)` / `.raw` (`KeboolaClient`) gains `create_scoped_token`, `delete_token`, "
|
|
31
|
+
"`refresh_token`, and per-device Data Streams `create_stream_source` / `get_stream_source` / "
|
|
32
|
+
"`list_stream_sources` / `delete_stream_source`. The facade returns typed `ScopedTokenResult` / "
|
|
33
|
+
"`StreamSourceResult` (exported from the package root); `.raw` returns plain dicts. Built for "
|
|
34
|
+
"keboola/jasnost device enrollment (ADR 0005).",
|
|
35
|
+
"New: `create_scoped_token(description, bucket_permissions, component_access, "
|
|
36
|
+
"can_read_all_file_uploads, expires_in)` generalises `create_short_lived_token` beyond "
|
|
37
|
+
"component scope -- it maps straight to `POST /v2/storage/tokens` so you can mint the narrow "
|
|
38
|
+
"'upload Files + write one sink bucket, expiring' token a capture device needs (the Keboola "
|
|
39
|
+
"single-bucket-write pattern). The acting token must carry `canManageTokens`.",
|
|
40
|
+
"New: `create_stream_source` provisions a per-device OTLP source AND (for `otlp`, default) its "
|
|
41
|
+
"logs/metrics/traces sinks + `in.c-otlp-<sourceId>` bucket, and returns `sink_bucket_id` so the "
|
|
42
|
+
"scoped device token can be granted write on exactly that bucket. Per-device sources are the "
|
|
43
|
+
"unit of isolated event-plane revocation (`delete_stream_source`) -- no shared-secret rotation. "
|
|
44
|
+
"`otlp_url` carries the ingest secret unmasked (revealed to the device once, never persisted).",
|
|
45
|
+
"New: `kbagent token` command group (create | delete | refresh) mirrors the scoped-token "
|
|
46
|
+
"primitives on the CLI (Project Management). `token create` prints the secret once; `token "
|
|
47
|
+
"delete` revokes immediately; `token refresh` rotates and invalidates the old value. These are "
|
|
48
|
+
"Storage-API operations (no manage token).",
|
|
49
|
+
"Note: contrary to earlier assumptions, a Data Streams source create is authenticated with a "
|
|
50
|
+
"normal per-project Storage token (NOT a master token) and there is no `masterTokenRequired` "
|
|
51
|
+
"error code; and a Files upload is NOT gated by `componentAccess` / `canReadAllFileUploads` -- "
|
|
52
|
+
"any valid Storage token can upload its own Files. `canReadAllFileUploads` only widens reading "
|
|
53
|
+
"files uploaded by OTHER tokens.",
|
|
54
|
+
],
|
|
55
|
+
"0.65.1": [
|
|
56
|
+
"BREAKING: Removed `data-app git-bind-credential` (and its `kbagent serve` endpoint). It shipped in "
|
|
57
|
+
"0.65.0 on a misdiagnosis: managed-repo deploys were failing and we believed the platform "
|
|
58
|
+
"did not inject the git-clone credential, so the command wired an encrypted credential into "
|
|
59
|
+
"`parameters.dataApp.git`. A clean reproduction on `data-science.us-east4.gcp` confirmed the "
|
|
60
|
+
"platform DOES inject the clone credential at deploy time (matching the sandboxes-service "
|
|
61
|
+
"`testManagedGitRepo.sh` contract) -- the command was unnecessary. The real fix was the "
|
|
62
|
+
"0.65.0 `configVersion`-omit change; pinning a managed app's no-git-block config is what made "
|
|
63
|
+
"the runtime demand `dataApp.git.repository` and revert the deploy to stopped.",
|
|
64
|
+
"Change: Corrected the managed-repo guidance everywhere: the canonical flow is `data-app create "
|
|
65
|
+
"--use-managed-git-repo` -> `git-credentials-create --type http_token --permissions readWrite` "
|
|
66
|
+
"+ `git push` your code -> `data-app deploy`. No credential wiring is needed. The earlier "
|
|
67
|
+
"'platform does not inject credentials' / `could not read Username` framing was wrong "
|
|
68
|
+
"(GitHub issue #454 closed as not-a-bug).",
|
|
69
|
+
],
|
|
27
70
|
"0.65.0": [
|
|
28
71
|
"New: deploy a data app from a Keboola-MANAGED git repository end-to-end. "
|
|
29
72
|
"`data-app create --use-managed-git-repo` provisions an EMPTY Keboola-hosted repo (POST "
|
|
@@ -39,7 +82,9 @@ CHANGELOG: dict[str, list[str]] = {
|
|
|
39
82
|
"credentials at deploy time -- without it the runtime's `git clone` of the managed repo "
|
|
40
83
|
"fails `could not read Username` and the deploy reverts to stopped. The token is encrypted "
|
|
41
84
|
"in place and never printed; `--dry-run` previews what would be wired without minting the "
|
|
42
|
-
"one-time credential or touching the config."
|
|
85
|
+
"one-time credential or touching the config. (Removed in 0.65.1 -- this was based on a "
|
|
86
|
+
"misdiagnosis; the platform does inject the clone credential, so the command was "
|
|
87
|
+
"unnecessary. See the 0.65.1 notes.)",
|
|
43
88
|
"New: `data-app runs --project P --app-id ID [--limit N]` lists a data app's recent "
|
|
44
89
|
"deployment attempts with `failure_reason` + `startup_logs`, including setup-phase failures "
|
|
45
90
|
"(e.g. a git-clone error during `app_setup`) that produce no container logs. It works on "
|
|
@@ -35,6 +35,7 @@ from .commands.sharing import sharing_app
|
|
|
35
35
|
from .commands.storage import storage_app
|
|
36
36
|
from .commands.stream import stream_app
|
|
37
37
|
from .commands.sync import sync_app
|
|
38
|
+
from .commands.token import token_app
|
|
38
39
|
from .commands.tool import tool_app
|
|
39
40
|
from .commands.version import update_command, version_command
|
|
40
41
|
from .commands.workspace import workspace_app
|
|
@@ -71,6 +72,7 @@ from .services.sharing_service import SharingService
|
|
|
71
72
|
from .services.storage_service import StorageService
|
|
72
73
|
from .services.stream_service import StreamService
|
|
73
74
|
from .services.sync_service import SyncService
|
|
75
|
+
from .services.token_service import TokenService
|
|
74
76
|
from .services.variables_service import VariablesService
|
|
75
77
|
from .services.version_service import VersionService
|
|
76
78
|
from .services.workspace_service import WorkspaceService
|
|
@@ -98,6 +100,7 @@ _PROJ = "Project Management"
|
|
|
98
100
|
app.add_typer(project_app, name="project", rich_help_panel=_PROJ)
|
|
99
101
|
app.add_typer(org_app, name="org", rich_help_panel=_PROJ)
|
|
100
102
|
app.add_typer(feature_app, name="feature", rich_help_panel=_PROJ)
|
|
103
|
+
app.add_typer(token_app, name="token", rich_help_panel=_PROJ)
|
|
101
104
|
|
|
102
105
|
# -- Browse & Inspect --
|
|
103
106
|
_BROWSE = "Browse & Inspect"
|
|
@@ -320,6 +323,7 @@ def main(
|
|
|
320
323
|
search_service = SearchService(config_store=config_store)
|
|
321
324
|
storage_service = StorageService(config_store=config_store)
|
|
322
325
|
stream_service = StreamService(config_store=config_store)
|
|
326
|
+
token_service = TokenService(config_store=config_store)
|
|
323
327
|
sync_service = SyncService(config_store=config_store)
|
|
324
328
|
variables_service = VariablesService(config_store=config_store)
|
|
325
329
|
encrypt_service = EncryptService(config_store=config_store)
|
|
@@ -375,6 +379,7 @@ def main(
|
|
|
375
379
|
ctx.obj["search_service"] = search_service
|
|
376
380
|
ctx.obj["storage_service"] = storage_service
|
|
377
381
|
ctx.obj["stream_service"] = stream_service
|
|
382
|
+
ctx.obj["token_service"] = token_service
|
|
378
383
|
ctx.obj["sync_service"] = sync_service
|
|
379
384
|
ctx.obj["variables_service"] = variables_service
|
|
380
385
|
ctx.obj["encrypt_service"] = encrypt_service
|
|
@@ -34,16 +34,19 @@ from .constants import (
|
|
|
34
34
|
METADATA_NOT_FOUND,
|
|
35
35
|
OAUTH_HOST,
|
|
36
36
|
OAUTH_PATH,
|
|
37
|
+
OTLP_BUCKET_PREFIX,
|
|
37
38
|
QUERY_JOB_MAX_WAIT,
|
|
38
39
|
QUERY_JOB_POLL_INTERVAL,
|
|
39
40
|
QUERY_RESULTS_PAGE_SIZE,
|
|
40
41
|
STORAGE_JOB_MAX_WAIT,
|
|
41
42
|
STORAGE_JOB_POLL_INTERVAL,
|
|
43
|
+
STREAM_DEFAULT_BRANCH,
|
|
42
44
|
VALID_POLL_STRATEGIES,
|
|
43
45
|
)
|
|
44
46
|
from .errors import ErrorCode, KeboolaApiError
|
|
45
47
|
from .http_base import BaseHttpClient
|
|
46
48
|
from .models import TokenVerifyResponse
|
|
49
|
+
from .stream_client import StreamClient, provision_otlp_sinks, stream_task_source_id
|
|
47
50
|
|
|
48
51
|
logger = logging.getLogger(__name__)
|
|
49
52
|
|
|
@@ -171,6 +174,9 @@ class KeboolaClient(BaseHttpClient):
|
|
|
171
174
|
self._queue_client: httpx.Client | None = None
|
|
172
175
|
self._query_client: httpx.Client | None = None
|
|
173
176
|
self._encrypt_client: httpx.Client | None = None
|
|
177
|
+
# Lazily built on first Data Streams call (per-device OTLP sources); the
|
|
178
|
+
# Stream control plane is a sibling host reachable from this stack+token.
|
|
179
|
+
self._stream_client: StreamClient | None = None
|
|
174
180
|
# Cache of project feature flags. Populated lazily on first
|
|
175
181
|
# has_feature() / get_project_features() call so we don't pay an
|
|
176
182
|
# extra verify_token round-trip on every kbagent invocation, and
|
|
@@ -199,6 +205,8 @@ class KeboolaClient(BaseHttpClient):
|
|
|
199
205
|
self._query_client.close()
|
|
200
206
|
if self._encrypt_client is not None:
|
|
201
207
|
self._encrypt_client.close()
|
|
208
|
+
if self._stream_client is not None:
|
|
209
|
+
self._stream_client.close()
|
|
202
210
|
|
|
203
211
|
def __enter__(self) -> "KeboolaClient":
|
|
204
212
|
return self
|
|
@@ -376,6 +384,209 @@ class KeboolaClient(BaseHttpClient):
|
|
|
376
384
|
)
|
|
377
385
|
return response.json()
|
|
378
386
|
|
|
387
|
+
def create_scoped_token(
|
|
388
|
+
self,
|
|
389
|
+
*,
|
|
390
|
+
description: str,
|
|
391
|
+
bucket_permissions: dict[str, str] | None = None,
|
|
392
|
+
component_access: list[str] | None = None,
|
|
393
|
+
can_read_all_file_uploads: bool = False,
|
|
394
|
+
expires_in: int | None = None,
|
|
395
|
+
) -> dict[str, Any]:
|
|
396
|
+
"""Create a scoped Storage API token (``POST /v2/storage/tokens``).
|
|
397
|
+
|
|
398
|
+
The general form of :meth:`create_short_lived_token`: instead of only a
|
|
399
|
+
component allow-list it also expresses **bucket** permissions, so a
|
|
400
|
+
caller can mint the narrow "upload Files + write one sink bucket,
|
|
401
|
+
expiring, nothing else" token a capture device needs (Keboola's
|
|
402
|
+
single-bucket-write pattern).
|
|
403
|
+
|
|
404
|
+
Note on Files upload: a Files upload (``POST /v2/storage/files/prepare``)
|
|
405
|
+
is a generic Storage write available to any valid Storage token -- it is
|
|
406
|
+
**not** gated by ``componentAccess`` or ``canReadAllFileUploads``. Grant
|
|
407
|
+
``bucket_permissions={sink_bucket: "write"}`` for the sink write;
|
|
408
|
+
``can_read_all_file_uploads`` only widens *reading* files uploaded by
|
|
409
|
+
*other* tokens (a device sees its own uploads regardless).
|
|
410
|
+
|
|
411
|
+
The acting token must carry ``canManageTokens`` (the API rejects the
|
|
412
|
+
create otherwise -- surfaced as an ``ACCESS_DENIED`` :class:`KeboolaApiError`
|
|
413
|
+
with the token masked). The returned dict is the raw API response; its
|
|
414
|
+
``token`` field is a **one-time** secret reveal -- persist only ``id``
|
|
415
|
+
(for :meth:`delete_token` / :meth:`refresh_token`) and ``expires``.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
description: Human-readable token description (per-device label).
|
|
419
|
+
bucket_permissions: ``{bucketId: "read" | "write"}`` grants.
|
|
420
|
+
component_access: Component IDs the token may run (often empty for a
|
|
421
|
+
capture device that only uploads Files + streams OTLP).
|
|
422
|
+
can_read_all_file_uploads: If True the token may read files uploaded
|
|
423
|
+
by other tokens (default False = only its own uploads).
|
|
424
|
+
expires_in: Lifetime in seconds; ``None`` = never expires.
|
|
425
|
+
"""
|
|
426
|
+
data: dict[str, Any] = {"description": description}
|
|
427
|
+
if expires_in is not None:
|
|
428
|
+
data["expiresIn"] = str(expires_in)
|
|
429
|
+
if can_read_all_file_uploads:
|
|
430
|
+
# Storage API reads this form field as truthy; omit it (=> default
|
|
431
|
+
# false) rather than sending "0" so an unset scope stays minimal.
|
|
432
|
+
data["canReadAllFileUploads"] = "1"
|
|
433
|
+
for bucket_id, permission in (bucket_permissions or {}).items():
|
|
434
|
+
data[f"bucketPermissions[{bucket_id}]"] = permission
|
|
435
|
+
if component_access:
|
|
436
|
+
data["componentAccess[]"] = component_access
|
|
437
|
+
response = self._request("POST", "/v2/storage/tokens", data=data)
|
|
438
|
+
return response.json()
|
|
439
|
+
|
|
440
|
+
def delete_token(self, token_id: str) -> None:
|
|
441
|
+
"""Revoke a Storage API token immediately (``DELETE /v2/storage/tokens/{id}``).
|
|
442
|
+
|
|
443
|
+
Returns 204 and the token stops authenticating at once. Only a
|
|
444
|
+
**non-master** token can be deleted (the API refuses to delete the master
|
|
445
|
+
token). Use this for active per-device revocation instead of waiting for
|
|
446
|
+
the token to expire.
|
|
447
|
+
"""
|
|
448
|
+
self._request("DELETE", f"/v2/storage/tokens/{quote(str(token_id), safe='')}")
|
|
449
|
+
|
|
450
|
+
def refresh_token(self, token_id: str) -> dict[str, Any]:
|
|
451
|
+
"""Rotate a Storage API token (``POST /v2/storage/tokens/{id}/refresh``).
|
|
452
|
+
|
|
453
|
+
Generates a **new** token value and returns the updated token dict; the
|
|
454
|
+
**old** token string becomes immediately invalid (rotation, not
|
|
455
|
+
additive), so every place using it must be updated. The token id is
|
|
456
|
+
stable across a refresh.
|
|
457
|
+
"""
|
|
458
|
+
response = self._request(
|
|
459
|
+
"POST", f"/v2/storage/tokens/{quote(str(token_id), safe='')}/refresh"
|
|
460
|
+
)
|
|
461
|
+
return response.json()
|
|
462
|
+
|
|
463
|
+
# ------------------------------------------------------------------
|
|
464
|
+
# Data Streams (per-device OTLP sources) -- delegate to StreamClient
|
|
465
|
+
# ------------------------------------------------------------------
|
|
466
|
+
|
|
467
|
+
def _get_stream_client(self) -> StreamClient:
|
|
468
|
+
"""Lazily build (and cache) a :class:`StreamClient` over this stack+token.
|
|
469
|
+
|
|
470
|
+
The Stream control plane lives on a sibling host (``stream.<region>``)
|
|
471
|
+
and authenticates with the same Storage token, so it is reachable from
|
|
472
|
+
the same ``(stack_url, token)`` this client already holds.
|
|
473
|
+
"""
|
|
474
|
+
if self._stream_client is None:
|
|
475
|
+
self._stream_client = StreamClient(stack_url=self._stack_url, token=self._token)
|
|
476
|
+
return self._stream_client
|
|
477
|
+
|
|
478
|
+
@staticmethod
|
|
479
|
+
def _stream_source_detail(
|
|
480
|
+
source: dict[str, Any], branch_id: str, sink_bucket_id: str | None
|
|
481
|
+
) -> dict[str, Any]:
|
|
482
|
+
"""Normalise a raw Stream ``source`` object into the returned detail dict.
|
|
483
|
+
|
|
484
|
+
Flattens the OTLP block so the caller gets ``otlp_url`` (with the ingest
|
|
485
|
+
secret embedded, **unmasked** -- the lib layer hands it to the device
|
|
486
|
+
once and never persists it) without digging into ``source["otlp"]``. The
|
|
487
|
+
raw ``source`` is echoed under ``source`` so nothing is lost.
|
|
488
|
+
"""
|
|
489
|
+
otlp = source.get("otlp") or {}
|
|
490
|
+
source_id = source.get("sourceId", "")
|
|
491
|
+
return {
|
|
492
|
+
"id": source_id,
|
|
493
|
+
"source_id": source_id,
|
|
494
|
+
"name": source.get("name", ""),
|
|
495
|
+
"type": source.get("type", ""),
|
|
496
|
+
"description": source.get("description", ""),
|
|
497
|
+
"branch_id": branch_id,
|
|
498
|
+
"otlp_url": otlp.get("url", ""),
|
|
499
|
+
"otlp_secret": otlp.get("secret", ""),
|
|
500
|
+
"base_endpoint": otlp.get("baseUrl", ""),
|
|
501
|
+
"sink_bucket_id": sink_bucket_id,
|
|
502
|
+
"source": source,
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
def create_stream_source(
|
|
506
|
+
self,
|
|
507
|
+
name: str,
|
|
508
|
+
*,
|
|
509
|
+
source_type: str = "otlp",
|
|
510
|
+
description: str = "",
|
|
511
|
+
branch_id: str = STREAM_DEFAULT_BRANCH,
|
|
512
|
+
provision_sinks: bool = True,
|
|
513
|
+
) -> dict[str, Any]:
|
|
514
|
+
"""Create a per-device OTLP (or HTTP) stream source and return its detail.
|
|
515
|
+
|
|
516
|
+
Async under the hood: the Stream API returns a 202 ``Task`` which is
|
|
517
|
+
polled to completion here (the caller sees one blocking call). For an
|
|
518
|
+
``otlp`` source with ``provision_sinks`` (default) the logs/metrics/traces
|
|
519
|
+
sinks and the ``in.c-otlp-<sourceId>`` sink bucket are auto-created so
|
|
520
|
+
OTLP data actually lands in Storage -- and so a scoped device token can be
|
|
521
|
+
granted ``write`` on that bucket. Pass ``provision_sinks=False`` for a
|
|
522
|
+
bare source.
|
|
523
|
+
|
|
524
|
+
Per-device sources are the unit of isolated event-plane revocation:
|
|
525
|
+
delete one device's source (:meth:`delete_stream_source`) without
|
|
526
|
+
rotating a secret shared with other devices.
|
|
527
|
+
|
|
528
|
+
Returns a dict with the flattened endpoint: ``id`` / ``source_id``,
|
|
529
|
+
``otlp_url`` (secret **unmasked**), ``otlp_secret``, ``sink_bucket_id``
|
|
530
|
+
(``None`` when no sinks were provisioned), and the raw ``source``.
|
|
531
|
+
Requires a Storage token privileged to manage Data Streams.
|
|
532
|
+
"""
|
|
533
|
+
stream = self._get_stream_client()
|
|
534
|
+
task = stream.create_source(
|
|
535
|
+
branch_id, name=name, source_type=source_type, description=description or None
|
|
536
|
+
)
|
|
537
|
+
finished = stream.wait_for_task(task)
|
|
538
|
+
source_id = stream_task_source_id(finished) or name
|
|
539
|
+
sink_bucket_id: str | None = None
|
|
540
|
+
if provision_sinks and source_type == "otlp":
|
|
541
|
+
provision_otlp_sinks(stream, branch_id, source_id)
|
|
542
|
+
sink_bucket_id = f"{OTLP_BUCKET_PREFIX}{source_id}"
|
|
543
|
+
source = stream.get_source(branch_id, source_id)
|
|
544
|
+
return self._stream_source_detail(source, branch_id, sink_bucket_id)
|
|
545
|
+
|
|
546
|
+
@staticmethod
|
|
547
|
+
def _sink_bucket_from_sinks(sinks_raw: dict[str, Any]) -> str | None:
|
|
548
|
+
"""Return the bucket of the source's first table sink, or None if it has none.
|
|
549
|
+
|
|
550
|
+
Derived from the ACTUAL sinks rather than assumed from the source type,
|
|
551
|
+
so a source created with ``provision_sinks=False`` (or outside kbagent)
|
|
552
|
+
truthfully reports ``None`` instead of a bucket that does not exist.
|
|
553
|
+
"""
|
|
554
|
+
for sink in sinks_raw.get("sinks", []):
|
|
555
|
+
table_id = (sink.get("table") or {}).get("tableId", "")
|
|
556
|
+
if "." in table_id:
|
|
557
|
+
return table_id.rsplit(".", 1)[0]
|
|
558
|
+
return None
|
|
559
|
+
|
|
560
|
+
def get_stream_source(
|
|
561
|
+
self, source_id: str, *, branch_id: str = STREAM_DEFAULT_BRANCH
|
|
562
|
+
) -> dict[str, Any]:
|
|
563
|
+
"""Fetch one stream source's detail (endpoint + secret + sink bucket).
|
|
564
|
+
|
|
565
|
+
``sink_bucket_id`` reflects the source's ACTUAL sinks (``None`` when it
|
|
566
|
+
has none -- e.g. created with ``provision_sinks=False`` or outside
|
|
567
|
+
kbagent), so a caller never scopes a device token to a bucket that does
|
|
568
|
+
not exist.
|
|
569
|
+
"""
|
|
570
|
+
stream = self._get_stream_client()
|
|
571
|
+
source = stream.get_source(branch_id, source_id)
|
|
572
|
+
sinks = stream.list_sinks(branch_id, source.get("sourceId", source_id))
|
|
573
|
+
return self._stream_source_detail(source, branch_id, self._sink_bucket_from_sinks(sinks))
|
|
574
|
+
|
|
575
|
+
def list_stream_sources(
|
|
576
|
+
self, *, branch_id: str = STREAM_DEFAULT_BRANCH
|
|
577
|
+
) -> list[dict[str, Any]]:
|
|
578
|
+
"""List the project's stream sources (raw source objects; find-or-create by name)."""
|
|
579
|
+
raw = self._get_stream_client().list_sources(branch_id)
|
|
580
|
+
return list(raw.get("sources", []))
|
|
581
|
+
|
|
582
|
+
def delete_stream_source(
|
|
583
|
+
self, source_id: str, *, branch_id: str = STREAM_DEFAULT_BRANCH
|
|
584
|
+
) -> None:
|
|
585
|
+
"""Delete a stream source (per-device revocation) -- async task polled to completion."""
|
|
586
|
+
stream = self._get_stream_client()
|
|
587
|
+
task = stream.delete_source(branch_id, source_id)
|
|
588
|
+
stream.wait_for_task(task)
|
|
589
|
+
|
|
379
590
|
def global_search(
|
|
380
591
|
self,
|
|
381
592
|
query: str,
|
|
@@ -2295,6 +2506,7 @@ class KeboolaClient(BaseHttpClient):
|
|
|
2295
2506
|
provider = file_detail.get("provider", "")
|
|
2296
2507
|
downloader = _CloudDownloader.create(file_detail)
|
|
2297
2508
|
|
|
2509
|
+
_assert_safe_download_url(file_detail["url"])
|
|
2298
2510
|
with httpx.Client(timeout=FILE_DOWNLOAD_TIMEOUT) as http:
|
|
2299
2511
|
resp = http.get(file_detail["url"])
|
|
2300
2512
|
resp.raise_for_status()
|
|
@@ -2393,6 +2605,7 @@ class KeboolaClient(BaseHttpClient):
|
|
|
2393
2605
|
import gzip
|
|
2394
2606
|
import shutil
|
|
2395
2607
|
|
|
2608
|
+
_assert_safe_download_url(url)
|
|
2396
2609
|
out_path = Path(output_path)
|
|
2397
2610
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2398
2611
|
is_gzipped = url.rstrip("?").split("?")[0].endswith(".gz")
|
|
@@ -3101,6 +3314,70 @@ def _build_abs_upload_url(abs_params: dict[str, Any]) -> str:
|
|
|
3101
3314
|
# ---------------------------------------------------------------------------
|
|
3102
3315
|
|
|
3103
3316
|
|
|
3317
|
+
def _assert_safe_download_url(url: str) -> None:
|
|
3318
|
+
"""Reject a download URL whose host resolves to a non-public address.
|
|
3319
|
+
|
|
3320
|
+
A malicious or compromised Storage API response can return a download URL
|
|
3321
|
+
pointing at the cloud instance-metadata endpoint (169.254.169.254) or
|
|
3322
|
+
localhost; because these fetches carry no Storage token and don't follow
|
|
3323
|
+
redirects, the residual SSRF still writes internal/credential data into the
|
|
3324
|
+
user's download file (GHSA-hjhx-mx7m-8xx2). We resolve the host and allow
|
|
3325
|
+
ONLY globally-routable (public) addresses plus the explicit BYOC private
|
|
3326
|
+
ranges below; everything else -- loopback, link-local (incl. the
|
|
3327
|
+
169.254.169.254 metadata endpoint), CGNAT 100.64.0.0/10, reserved,
|
|
3328
|
+
multicast, unspecified -- is refused.
|
|
3329
|
+
|
|
3330
|
+
RFC1918 + IPv6-ULA *private* ranges are deliberately ALLOWED: BYOC /
|
|
3331
|
+
private-tenant Keboola deployments legitimately serve storage from private
|
|
3332
|
+
endpoints, and the high-value SSRF target (instance metadata) is link-local,
|
|
3333
|
+
not private. An allow-list (public OR explicit private) rather than a
|
|
3334
|
+
block-list of `ipaddress` predicates avoids gaps like CGNAT, which none of
|
|
3335
|
+
`is_loopback/is_link_local/is_reserved/is_multicast/is_unspecified` catch.
|
|
3336
|
+
"""
|
|
3337
|
+
import ipaddress
|
|
3338
|
+
import socket
|
|
3339
|
+
from urllib.parse import urlparse
|
|
3340
|
+
|
|
3341
|
+
byoc_private = (
|
|
3342
|
+
ipaddress.ip_network("10.0.0.0/8"),
|
|
3343
|
+
ipaddress.ip_network("172.16.0.0/12"),
|
|
3344
|
+
ipaddress.ip_network("192.168.0.0/16"),
|
|
3345
|
+
ipaddress.ip_network("fc00::/7"), # IPv6 unique-local
|
|
3346
|
+
)
|
|
3347
|
+
|
|
3348
|
+
host = urlparse(url).hostname
|
|
3349
|
+
if not host:
|
|
3350
|
+
raise KeboolaApiError(
|
|
3351
|
+
message=f"Refusing to download: URL has no host ({url!r}).",
|
|
3352
|
+
status_code=0,
|
|
3353
|
+
error_code=ErrorCode.INVALID_ARGUMENT,
|
|
3354
|
+
retryable=False,
|
|
3355
|
+
)
|
|
3356
|
+
try:
|
|
3357
|
+
resolved = {info[4][0] for info in socket.getaddrinfo(host, None)}
|
|
3358
|
+
except socket.gaierror:
|
|
3359
|
+
# DNS failure is surfaced by the real fetch; don't mask it here.
|
|
3360
|
+
return
|
|
3361
|
+
for addr in resolved:
|
|
3362
|
+
try:
|
|
3363
|
+
ip = ipaddress.ip_address(addr)
|
|
3364
|
+
except ValueError:
|
|
3365
|
+
continue
|
|
3366
|
+
if ip.is_global or any(ip in net for net in byoc_private):
|
|
3367
|
+
continue # public host, or a BYOC private endpoint -- allowed
|
|
3368
|
+
raise KeboolaApiError(
|
|
3369
|
+
message=(
|
|
3370
|
+
f"Refusing to download from {host} -> {addr}: non-public address "
|
|
3371
|
+
f"(not a public host nor a BYOC private range). This indicates a "
|
|
3372
|
+
f"malicious or compromised Storage API response (possible SSRF, "
|
|
3373
|
+
f"e.g. the cloud instance-metadata endpoint)."
|
|
3374
|
+
),
|
|
3375
|
+
status_code=0,
|
|
3376
|
+
error_code=ErrorCode.INVALID_ARGUMENT,
|
|
3377
|
+
retryable=False,
|
|
3378
|
+
)
|
|
3379
|
+
|
|
3380
|
+
|
|
3104
3381
|
class _IterBytesReader:
|
|
3105
3382
|
"""Adapt an httpx iter_bytes() iterator to a .read(n) file-like interface.
|
|
3106
3383
|
|
|
@@ -3299,6 +3576,7 @@ class _CloudDownloader:
|
|
|
3299
3576
|
import gzip
|
|
3300
3577
|
import shutil
|
|
3301
3578
|
|
|
3579
|
+
_assert_safe_download_url(url)
|
|
3302
3580
|
headers = self._request_headers(url)
|
|
3303
3581
|
dest_path = Path(dest)
|
|
3304
3582
|
with (
|
|
@@ -341,70 +341,3 @@ def register_git_commands(app: typer.Typer) -> None:
|
|
|
341
341
|
)
|
|
342
342
|
|
|
343
343
|
formatter.output(result, _human)
|
|
344
|
-
|
|
345
|
-
@app.command("git-bind-credential")
|
|
346
|
-
def data_app_git_bind_credential(
|
|
347
|
-
ctx: typer.Context,
|
|
348
|
-
project: str = typer.Option(..., "--project", help="Project alias"),
|
|
349
|
-
app_id: str = typer.Option(..., "--app-id", help="Data Science numeric app id"),
|
|
350
|
-
branch: str = typer.Option(
|
|
351
|
-
"main", "--branch-name", help="Managed-repo branch to deploy from (default main)."
|
|
352
|
-
),
|
|
353
|
-
permissions: str = typer.Option(
|
|
354
|
-
"readOnly",
|
|
355
|
-
"--permissions",
|
|
356
|
-
help="Credential access level: readOnly (default) | readWrite.",
|
|
357
|
-
),
|
|
358
|
-
dry_run: bool = typer.Option(
|
|
359
|
-
False,
|
|
360
|
-
"--dry-run",
|
|
361
|
-
help="Preview what would be wired (repo/branch/permissions) WITHOUT minting a credential or editing the config.",
|
|
362
|
-
),
|
|
363
|
-
) -> None:
|
|
364
|
-
"""Make a MANAGED-repo app deployable by wiring a credential into its config.
|
|
365
|
-
|
|
366
|
-
A `--use-managed-git-repo` app owns an empty Keboola-hosted repo but its
|
|
367
|
-
Storage config has no git block. On stacks that do NOT inject managed-repo
|
|
368
|
-
credentials at deploy time, the runtime's `git clone` of the managed repo
|
|
369
|
-
fails ("could not read Username") and the deploy reverts to stopped. This
|
|
370
|
-
command mints a fresh `http_token` ON the app, encrypts it under the
|
|
371
|
-
project KMS, and writes `parameters.dataApp.git` (repository + placeholder
|
|
372
|
-
username + encrypted `#password` + branch) so the next `data-app deploy`
|
|
373
|
-
can clone. The token is encrypted in-place and never printed.
|
|
374
|
-
|
|
375
|
-
`--dry-run` validates the app + resolves the managed repo URL but mints
|
|
376
|
-
nothing and leaves the config untouched -- the http_token is one-time, so
|
|
377
|
-
a real run that you abort would orphan it.
|
|
378
|
-
"""
|
|
379
|
-
formatter = get_formatter(ctx)
|
|
380
|
-
service = get_service(ctx, "data_app_service")
|
|
381
|
-
try:
|
|
382
|
-
result = service.bind_managed_credential(
|
|
383
|
-
project, app_id, branch=branch, permissions=permissions, dry_run=dry_run
|
|
384
|
-
)
|
|
385
|
-
except KeboolaApiError as exc:
|
|
386
|
-
formatter.error(
|
|
387
|
-
message=exc.message,
|
|
388
|
-
error_code=exc.error_code,
|
|
389
|
-
retryable=exc.retryable,
|
|
390
|
-
details=exc.details,
|
|
391
|
-
)
|
|
392
|
-
raise typer.Exit(code=map_error_to_exit_code(exc)) from None
|
|
393
|
-
except ConfigError as exc:
|
|
394
|
-
formatter.error(message=exc.message, error_code=ErrorCode.CONFIG_ERROR)
|
|
395
|
-
raise typer.Exit(code=5) from None
|
|
396
|
-
|
|
397
|
-
def _human(c: Console, d: dict) -> None:
|
|
398
|
-
if d.get("dry_run"):
|
|
399
|
-
c.print("[bold]DRY RUN -- no credential minted, config unchanged.[/bold]")
|
|
400
|
-
c.print(f" [bold]Repository:[/bold] {d.get('repository', '')}")
|
|
401
|
-
c.print(f" [bold]Branch:[/bold] {d.get('branch', '')}")
|
|
402
|
-
c.print(f" [bold]Permissions:[/bold] {d.get('permissions', '')}")
|
|
403
|
-
return
|
|
404
|
-
c.print(f"[bold green]Success:[/bold green] {d['message']}")
|
|
405
|
-
c.print(f" [bold]Repository:[/bold] {d.get('repository', '')}")
|
|
406
|
-
c.print(f" [bold]Branch:[/bold] {d.get('branch', '')}")
|
|
407
|
-
c.print(f" [bold]Permissions:[/bold] {d.get('permissions', '')}")
|
|
408
|
-
c.print(f" [bold]Config version:[/bold] {d.get('config_version', '')}")
|
|
409
|
-
|
|
410
|
-
formatter.output(result, _human)
|