render 1.0.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.
- render/__init__.py +98 -0
- render/client/__init__.py +119 -0
- render/client/client.py +110 -0
- render/client/errors.py +39 -0
- render/client/sse.py +90 -0
- render/client/sse_sync.py +20 -0
- render/client/tests/__init__.py +0 -0
- render/client/tests/test_client.py +379 -0
- render/client/tests/test_method_parity.py +118 -0
- render/client/tests/test_render.py +52 -0
- render/client/tests/test_sse.py +67 -0
- render/client/tests/test_util.py +303 -0
- render/client/tests/test_util_sync.py +73 -0
- render/client/tests/test_workflows_sync.py +313 -0
- render/client/types.py +52 -0
- render/client/util.py +285 -0
- render/client/util_sync.py +83 -0
- render/client/workflows.py +363 -0
- render/client/workflows_sync.py +298 -0
- render/experimental/__init__.py +37 -0
- render/experimental/experimental.py +93 -0
- render/experimental/experimental_sync.py +78 -0
- render/experimental/key_value/__init__.py +33 -0
- render/experimental/key_value/api.py +243 -0
- render/experimental/key_value/compare.py +83 -0
- render/experimental/key_value/provider.py +285 -0
- render/experimental/key_value/tests/__init__.py +0 -0
- render/experimental/key_value/tests/test_api.py +371 -0
- render/experimental/key_value/tests/test_compare.py +136 -0
- render/experimental/key_value/tests/test_provider.py +1016 -0
- render/experimental/key_value/types.py +130 -0
- render/experimental/key_value/utils.py +11 -0
- render/experimental/object/__init__.py +38 -0
- render/experimental/object/api.py +250 -0
- render/experimental/object/api_sync.py +250 -0
- render/experimental/object/client.py +545 -0
- render/experimental/object/client_sync.py +547 -0
- render/experimental/object/e2e_helpers.py +78 -0
- render/experimental/object/tests/__init__.py +0 -0
- render/experimental/object/tests/test_client.py +228 -0
- render/experimental/object/tests/test_e2e.py +125 -0
- render/experimental/object/types.py +87 -0
- render/experimental/sandbox/__init__.py +31 -0
- render/experimental/sandbox/_tar.py +278 -0
- render/experimental/sandbox/api.py +518 -0
- render/experimental/sandbox/api_sync.py +514 -0
- render/experimental/sandbox/client.py +168 -0
- render/experimental/sandbox/client_sync.py +170 -0
- render/experimental/sandbox/errors.py +37 -0
- render/experimental/sandbox/files.py +378 -0
- render/experimental/sandbox/tests/__init__.py +0 -0
- render/experimental/sandbox/tests/test_client.py +802 -0
- render/experimental/sandbox/tests/test_download.py +728 -0
- render/experimental/sandbox/tests/test_e2e.py +252 -0
- render/experimental/sandbox/tests/test_tar.py +153 -0
- render/experimental/sandbox/tests/test_types.py +57 -0
- render/experimental/sandbox/types.py +46 -0
- render/public_api/__init__.py +8 -0
- render/public_api/api/__init__.py +1 -0
- render/public_api/api/artifact_sources/__init__.py +1 -0
- render/public_api/api/artifact_sources/add_or_update_artifact_source_secret_file.py +252 -0
- render/public_api/api/artifact_sources/create_artifact_source.py +243 -0
- render/public_api/api/artifact_sources/delete_artifact_source.py +188 -0
- render/public_api/api/artifact_sources/delete_artifact_source_env_var.py +223 -0
- render/public_api/api/artifact_sources/delete_artifact_source_secret_file.py +223 -0
- render/public_api/api/artifact_sources/get_artifact_source.py +185 -0
- render/public_api/api/artifact_sources/get_env_vars_for_artifact_source.py +241 -0
- render/public_api/api/artifact_sources/link_artifact_source_to_env_group.py +215 -0
- render/public_api/api/artifact_sources/list_artifact_sources.py +338 -0
- render/public_api/api/artifact_sources/list_artifacts_in_artifact_source.py +241 -0
- render/public_api/api/artifact_sources/list_secret_files_for_artifact_source.py +241 -0
- render/public_api/api/artifact_sources/retrieve_artifact_source_env_var.py +225 -0
- render/public_api/api/artifact_sources/retrieve_artifact_source_secret_file.py +225 -0
- render/public_api/api/artifact_sources/unlink_artifact_source_from_env_group.py +209 -0
- render/public_api/api/artifact_sources/update_artifact_source.py +244 -0
- render/public_api/api/artifact_sources/update_artifact_source_env_var.py +257 -0
- render/public_api/api/artifact_sources/update_env_vars_for_artifact_source.py +245 -0
- render/public_api/api/artifact_sources/update_secret_files_for_artifact_source.py +255 -0
- render/public_api/api/audit_logs/__init__.py +1 -0
- render/public_api/api/audit_logs/list_organization_audit_logs.py +303 -0
- render/public_api/api/audit_logs/list_owner_audit_logs.py +303 -0
- render/public_api/api/blueprints/__init__.py +1 -0
- render/public_api/api/blueprints/disconnect_blueprint.py +210 -0
- render/public_api/api/blueprints/list_blueprint_syncs.py +241 -0
- render/public_api/api/blueprints/list_blueprints.py +251 -0
- render/public_api/api/blueprints/retrieve_blueprint.py +200 -0
- render/public_api/api/blueprints/update_blueprint.py +222 -0
- render/public_api/api/blueprints/validate_blueprint.py +217 -0
- render/public_api/api/custom_domains/__init__.py +1 -0
- render/public_api/api/custom_domains/create_custom_domain.py +242 -0
- render/public_api/api/custom_domains/delete_custom_domain.py +216 -0
- render/public_api/api/custom_domains/list_custom_domains.py +346 -0
- render/public_api/api/custom_domains/refresh_custom_domain.py +216 -0
- render/public_api/api/custom_domains/retrieve_custom_domain.py +218 -0
- render/public_api/api/dedicated_i_ps/__init__.py +1 -0
- render/public_api/api/dedicated_i_ps/create_dedicated_ip.py +234 -0
- render/public_api/api/dedicated_i_ps/delete_dedicated_ip.py +198 -0
- render/public_api/api/dedicated_i_ps/list_dedicated_ips.py +223 -0
- render/public_api/api/dedicated_i_ps/retrieve_dedicated_ip.py +200 -0
- render/public_api/api/dedicated_i_ps/update_dedicated_ip.py +244 -0
- render/public_api/api/deploys/__init__.py +1 -0
- render/public_api/api/deploys/cancel_deploy.py +216 -0
- render/public_api/api/deploys/create_deploy.py +231 -0
- render/public_api/api/deploys/list_deploys.py +377 -0
- render/public_api/api/deploys/retrieve_deploy.py +213 -0
- render/public_api/api/deploys/rollback_deploy.py +241 -0
- render/public_api/api/disks/__init__.py +1 -0
- render/public_api/api/disks/add_disk.py +222 -0
- render/public_api/api/disks/delete_disk.py +210 -0
- render/public_api/api/disks/list_disks.py +381 -0
- render/public_api/api/disks/list_snapshots.py +214 -0
- render/public_api/api/disks/restore_snapshot.py +255 -0
- render/public_api/api/disks/retrieve_disk.py +200 -0
- render/public_api/api/disks/update_disk.py +243 -0
- render/public_api/api/environment_groups/__init__.py +1 -0
- render/public_api/api/environment_groups/create_env_group.py +199 -0
- render/public_api/api/environment_groups/delete_env_group.py +192 -0
- render/public_api/api/environment_groups/delete_env_group_env_var.py +201 -0
- render/public_api/api/environment_groups/delete_env_group_secret_file.py +201 -0
- render/public_api/api/environment_groups/link_service_to_env_group.py +211 -0
- render/public_api/api/environment_groups/list_env_groups.py +352 -0
- render/public_api/api/environment_groups/retrieve_env_group.py +190 -0
- render/public_api/api/environment_groups/retrieve_env_group_env_var.py +203 -0
- render/public_api/api/environment_groups/retrieve_env_group_secret_file.py +203 -0
- render/public_api/api/environment_groups/unlink_service_from_env_group.py +209 -0
- render/public_api/api/environment_groups/update_env_group.py +212 -0
- render/public_api/api/environment_groups/update_env_group_env_var.py +230 -0
- render/public_api/api/environment_groups/update_env_group_secret_file.py +225 -0
- render/public_api/api/events/__init__.py +1 -0
- render/public_api/api/events/retrieve_event.py +205 -0
- render/public_api/api/key_value/__init__.py +1 -0
- render/public_api/api/key_value/create_key_value.py +199 -0
- render/public_api/api/key_value/delete_key_value.py +188 -0
- render/public_api/api/key_value/list_key_value.py +380 -0
- render/public_api/api/key_value/resume_key_value.py +203 -0
- render/public_api/api/key_value/retrieve_key_value.py +190 -0
- render/public_api/api/key_value/retrieve_key_value_connection_info.py +194 -0
- render/public_api/api/key_value/suspend_key_value.py +203 -0
- render/public_api/api/key_value/update_key_value.py +221 -0
- render/public_api/api/logs/__init__.py +1 -0
- render/public_api/api/logs/delete_owner_log_stream.py +188 -0
- render/public_api/api/logs/delete_resource_log_stream.py +192 -0
- render/public_api/api/logs/get_owner_log_stream.py +205 -0
- render/public_api/api/logs/get_resource_log_stream.py +209 -0
- render/public_api/api/logs/list_logs.py +535 -0
- render/public_api/api/logs/list_logs_values.py +527 -0
- render/public_api/api/logs/list_resource_log_streams.py +317 -0
- render/public_api/api/logs/subscribe_logs.py +527 -0
- render/public_api/api/logs/update_owner_log_stream.py +231 -0
- render/public_api/api/logs/update_resource_log_stream.py +231 -0
- render/public_api/api/maintenance/__init__.py +1 -0
- render/public_api/api/maintenance/list_maintenance.py +249 -0
- render/public_api/api/maintenance/retrieve_maintenance.py +190 -0
- render/public_api/api/maintenance/trigger_maintenance.py +212 -0
- render/public_api/api/maintenance/update_maintenance.py +222 -0
- render/public_api/api/metrics/__init__.py +1 -0
- render/public_api/api/metrics/delete_owner_metrics_stream.py +172 -0
- render/public_api/api/metrics/get_active_connections.py +237 -0
- render/public_api/api/metrics/get_bandwidth.py +237 -0
- render/public_api/api/metrics/get_bandwidth_sources.py +250 -0
- render/public_api/api/metrics/get_cpu.py +287 -0
- render/public_api/api/metrics/get_cpu_limit.py +267 -0
- render/public_api/api/metrics/get_cpu_target.py +267 -0
- render/public_api/api/metrics/get_disk_capacity.py +252 -0
- render/public_api/api/metrics/get_disk_usage.py +252 -0
- render/public_api/api/metrics/get_http_latency.py +297 -0
- render/public_api/api/metrics/get_http_requests.py +302 -0
- render/public_api/api/metrics/get_instance_count.py +252 -0
- render/public_api/api/metrics/get_memory.py +267 -0
- render/public_api/api/metrics/get_memory_limit.py +267 -0
- render/public_api/api/metrics/get_memory_target.py +267 -0
- render/public_api/api/metrics/get_owner_metrics_stream.py +174 -0
- render/public_api/api/metrics/get_replication_lag.py +237 -0
- render/public_api/api/metrics/get_task_runs_completed.py +281 -0
- render/public_api/api/metrics/get_task_runs_queued.py +237 -0
- render/public_api/api/metrics/list_application_filter_values.py +254 -0
- render/public_api/api/metrics/list_http_filter_values.py +282 -0
- render/public_api/api/metrics/list_path_filter_values.py +289 -0
- render/public_api/api/metrics/upsert_owner_metrics_stream.py +192 -0
- render/public_api/api/notification_settings/__init__.py +1 -0
- render/public_api/api/notification_settings/list_notification_overrides.py +255 -0
- render/public_api/api/notification_settings/patch_owner_notification_settings.py +207 -0
- render/public_api/api/notification_settings/patch_service_notification_overrides.py +207 -0
- render/public_api/api/notification_settings/retrieve_owner_notification_settings.py +193 -0
- render/public_api/api/notification_settings/retrieve_service_notification_overrides.py +193 -0
- render/public_api/api/object_storage/__init__.py +1 -0
- render/public_api/api/object_storage/delete_object.py +215 -0
- render/public_api/api/object_storage/get_object.py +221 -0
- render/public_api/api/object_storage/list_objects.py +240 -0
- render/public_api/api/object_storage/put_object.py +248 -0
- render/public_api/api/one_off_jobs/__init__.py +1 -0
- render/public_api/api/one_off_jobs/cancel_job.py +203 -0
- render/public_api/api/one_off_jobs/list_job.py +367 -0
- render/public_api/api/one_off_jobs/post_job.py +216 -0
- render/public_api/api/one_off_jobs/retrieve_job.py +203 -0
- render/public_api/api/postgres/__init__.py +1 -0
- render/public_api/api/postgres/create_postgres.py +199 -0
- render/public_api/api/postgres/create_postgres_export.py +207 -0
- render/public_api/api/postgres/create_postgres_user.py +229 -0
- render/public_api/api/postgres/delete_postgres.py +192 -0
- render/public_api/api/postgres/delete_postgres_user.py +201 -0
- render/public_api/api/postgres/failover_postgres.py +207 -0
- render/public_api/api/postgres/list_available_postgres_parameter_overrides.py +190 -0
- render/public_api/api/postgres/list_postgres.py +418 -0
- render/public_api/api/postgres/list_postgres_export.py +199 -0
- render/public_api/api/postgres/list_postgres_parameter_overrides.py +190 -0
- render/public_api/api/postgres/list_postgres_processes.py +190 -0
- render/public_api/api/postgres/list_postgres_sizes.py +190 -0
- render/public_api/api/postgres/list_postgres_table_scans.py +190 -0
- render/public_api/api/postgres/list_postgres_top_queries.py +194 -0
- render/public_api/api/postgres/list_postgres_users.py +195 -0
- render/public_api/api/postgres/promote_postgres.py +203 -0
- render/public_api/api/postgres/recover_postgres.py +231 -0
- render/public_api/api/postgres/restart_postgres.py +203 -0
- render/public_api/api/postgres/resume_postgres.py +203 -0
- render/public_api/api/postgres/retrieve_postgres.py +190 -0
- render/public_api/api/postgres/retrieve_postgres_connection_info.py +194 -0
- render/public_api/api/postgres/retrieve_postgres_recovery_info.py +194 -0
- render/public_api/api/postgres/setup_postgres_replication.py +231 -0
- render/public_api/api/postgres/suspend_postgres.py +203 -0
- render/public_api/api/postgres/update_postgres.py +217 -0
- render/public_api/api/postgres/update_postgres_parameter_overrides.py +217 -0
- render/public_api/api/projects_environments/__init__.py +1 -0
- render/public_api/api/projects_environments/add_resources_to_environment.py +212 -0
- render/public_api/api/projects_environments/create_environment.py +199 -0
- render/public_api/api/projects_environments/create_project.py +199 -0
- render/public_api/api/projects_environments/delete_environment.py +216 -0
- render/public_api/api/projects_environments/delete_project.py +216 -0
- render/public_api/api/projects_environments/list_environments.py +369 -0
- render/public_api/api/projects_environments/list_projects.py +329 -0
- render/public_api/api/projects_environments/remove_resources_from_environment.py +211 -0
- render/public_api/api/projects_environments/retrieve_environment.py +190 -0
- render/public_api/api/projects_environments/retrieve_project.py +190 -0
- render/public_api/api/projects_environments/update_environment.py +212 -0
- render/public_api/api/projects_environments/update_project.py +224 -0
- render/public_api/api/redis_deprecated/__init__.py +1 -0
- render/public_api/api/redis_deprecated/create_redis.py +199 -0
- render/public_api/api/redis_deprecated/delete_redis.py +188 -0
- render/public_api/api/redis_deprecated/list_redis.py +384 -0
- render/public_api/api/redis_deprecated/resume_redis.py +203 -0
- render/public_api/api/redis_deprecated/retrieve_redis.py +190 -0
- render/public_api/api/redis_deprecated/retrieve_redis_connection_info.py +194 -0
- render/public_api/api/redis_deprecated/suspend_redis.py +203 -0
- render/public_api/api/redis_deprecated/update_redis.py +217 -0
- render/public_api/api/registry_credentials/__init__.py +1 -0
- render/public_api/api/registry_credentials/create_registry_credential.py +209 -0
- render/public_api/api/registry_credentials/delete_registry_credential.py +198 -0
- render/public_api/api/registry_credentials/list_registry_credentials.py +370 -0
- render/public_api/api/registry_credentials/retrieve_registry_credential.py +200 -0
- render/public_api/api/registry_credentials/update_registry_credential.py +241 -0
- render/public_api/api/sandboxes/__init__.py +1 -0
- render/public_api/api/sandboxes/connect_sandbox_files.py +281 -0
- render/public_api/api/sandboxes/connect_sandbox_run.py +271 -0
- render/public_api/api/sandboxes/create_sandbox.py +199 -0
- render/public_api/api/sandboxes/list_sandbox_files.py +244 -0
- render/public_api/api/sandboxes/list_sandbox_groups.py +216 -0
- render/public_api/api/sandboxes/list_sandboxes.py +269 -0
- render/public_api/api/sandboxes/retrieve_sandbox.py +211 -0
- render/public_api/api/sandboxes/retrieve_sandbox_execution.py +224 -0
- render/public_api/api/sandboxes/stream_sandbox_logs.py +274 -0
- render/public_api/api/sandboxes/terminate_sandbox.py +217 -0
- render/public_api/api/sandboxes/update_sandbox_exec.py +258 -0
- render/public_api/api/services/__init__.py +1 -0
- render/public_api/api/services/add_or_update_secret_file.py +252 -0
- render/public_api/api/services/autoscale_service.py +230 -0
- render/public_api/api/services/create_ephemeral_shell.py +217 -0
- render/public_api/api/services/create_service.py +204 -0
- render/public_api/api/services/delete_autoscaling_config.py +198 -0
- render/public_api/api/services/delete_env_var.py +223 -0
- render/public_api/api/services/delete_secret_file.py +223 -0
- render/public_api/api/services/delete_service.py +198 -0
- render/public_api/api/services/get_env_vars_for_service.py +241 -0
- render/public_api/api/services/list_events.py +288 -0
- render/public_api/api/services/list_instances.py +190 -0
- render/public_api/api/services/list_secret_files_for_service.py +241 -0
- render/public_api/api/services/list_services.py +439 -0
- render/public_api/api/services/preview_service.py +227 -0
- render/public_api/api/services/purge_cache.py +203 -0
- render/public_api/api/services/restart_service.py +211 -0
- render/public_api/api/services/resume_service.py +203 -0
- render/public_api/api/services/retrieve_env_var.py +225 -0
- render/public_api/api/services/retrieve_secret_file.py +225 -0
- render/public_api/api/services/retrieve_service.py +190 -0
- render/public_api/api/services/retrieve_service_outbound_ips.py +207 -0
- render/public_api/api/services/scale_service.py +237 -0
- render/public_api/api/services/suspend_service.py +203 -0
- render/public_api/api/services/update_env_var.py +257 -0
- render/public_api/api/services/update_env_vars_for_service.py +241 -0
- render/public_api/api/services/update_secret_files_for_service.py +255 -0
- render/public_api/api/services/update_service.py +227 -0
- render/public_api/api/services_cron_jobs/__init__.py +1 -0
- render/public_api/api/services_cron_jobs/cancel_cron_job_run.py +183 -0
- render/public_api/api/services_cron_jobs/run_cron_job.py +185 -0
- render/public_api/api/services_headers/__init__.py +1 -0
- render/public_api/api/services_headers/add_headers.py +222 -0
- render/public_api/api/services_headers/delete_header.py +211 -0
- render/public_api/api/services_headers/list_headers.py +302 -0
- render/public_api/api/services_headers/update_headers.py +243 -0
- render/public_api/api/services_routes/__init__.py +1 -0
- render/public_api/api/services_routes/add_route.py +227 -0
- render/public_api/api/services_routes/delete_route.py +211 -0
- render/public_api/api/services_routes/list_routes.py +306 -0
- render/public_api/api/services_routes/patch_route.py +259 -0
- render/public_api/api/services_routes/put_routes.py +259 -0
- render/public_api/api/users/__init__.py +1 -0
- render/public_api/api/users/get_user.py +161 -0
- render/public_api/api/webhooks/__init__.py +1 -0
- render/public_api/api/webhooks/create_webhook.py +199 -0
- render/public_api/api/webhooks/delete_webhook.py +183 -0
- render/public_api/api/webhooks/list_webhook_events.py +268 -0
- render/public_api/api/webhooks/list_webhooks.py +237 -0
- render/public_api/api/webhooks/retrieve_webhook.py +185 -0
- render/public_api/api/webhooks/update_webhook.py +212 -0
- render/public_api/api/workflow_tasks_beta/__init__.py +1 -0
- render/public_api/api/workflow_tasks_beta/cancel_task_run.py +188 -0
- render/public_api/api/workflow_tasks_beta/create_task.py +204 -0
- render/public_api/api/workflow_tasks_beta/get_task.py +190 -0
- render/public_api/api/workflow_tasks_beta/get_task_run.py +190 -0
- render/public_api/api/workflow_tasks_beta/list_task_runs.py +317 -0
- render/public_api/api/workflow_tasks_beta/list_tasks.py +298 -0
- render/public_api/api/workflow_tasks_beta/stream_task_runs_events.py +230 -0
- render/public_api/api/workflows_beta/__init__.py +1 -0
- render/public_api/api/workflows_beta/create_workflow.py +199 -0
- render/public_api/api/workflows_beta/create_workflow_version.py +197 -0
- render/public_api/api/workflows_beta/delete_workflow.py +188 -0
- render/public_api/api/workflows_beta/get_workflow.py +190 -0
- render/public_api/api/workflows_beta/get_workflow_version.py +190 -0
- render/public_api/api/workflows_beta/list_workflow_versions.py +275 -0
- render/public_api/api/workflows_beta/list_workflows.py +298 -0
- render/public_api/api/workflows_beta/update_workflow.py +212 -0
- render/public_api/api/workspaces/__init__.py +1 -0
- render/public_api/api/workspaces/list_owners.py +255 -0
- render/public_api/api/workspaces/remove_workspace_member.py +206 -0
- render/public_api/api/workspaces/retrieve_owner.py +207 -0
- render/public_api/api/workspaces/retrieve_owner_members.py +200 -0
- render/public_api/api/workspaces/update_workspace_member.py +235 -0
- render/public_api/client.py +268 -0
- render/public_api/errors.py +16 -0
- render/public_api/models/__init__.py +861 -0
- render/public_api/models/add_headers_response_201.py +72 -0
- render/public_api/models/add_or_update_artifact_source_secret_file_body.py +59 -0
- render/public_api/models/add_or_update_secret_file_body.py +59 -0
- render/public_api/models/artifact.py +145 -0
- render/public_api/models/artifact_fetch_failed.py +67 -0
- render/public_api/models/artifact_source.py +162 -0
- render/public_api/models/artifact_source_changed.py +70 -0
- render/public_api/models/artifact_source_git.py +167 -0
- render/public_api/models/artifact_source_git_region.py +12 -0
- render/public_api/models/artifact_source_git_runtime.py +14 -0
- render/public_api/models/artifact_source_image.py +78 -0
- render/public_api/models/artifact_source_patch_git.py +175 -0
- render/public_api/models/artifact_source_patch_git_region.py +12 -0
- render/public_api/models/artifact_source_patch_git_runtime.py +14 -0
- render/public_api/models/artifact_source_patch_image.py +71 -0
- render/public_api/models/artifact_source_patch_input.py +101 -0
- render/public_api/models/artifact_source_post_input.py +213 -0
- render/public_api/models/artifact_source_service_link.py +67 -0
- render/public_api/models/artifact_source_with_cursor.py +73 -0
- render/public_api/models/audit_log.py +113 -0
- render/public_api/models/audit_log_actor.py +80 -0
- render/public_api/models/audit_log_actor_type.py +10 -0
- render/public_api/models/audit_log_event.py +87 -0
- render/public_api/models/audit_log_metadata.py +49 -0
- render/public_api/models/audit_log_status.py +9 -0
- render/public_api/models/audit_log_with_cursor.py +73 -0
- render/public_api/models/auto_deploy.py +9 -0
- render/public_api/models/auto_deploy_disabled.py +79 -0
- render/public_api/models/auto_deploy_enabled.py +68 -0
- render/public_api/models/auto_deploy_trigger.py +10 -0
- render/public_api/models/autoscaling_config.py +89 -0
- render/public_api/models/autoscaling_config_changed.py +83 -0
- render/public_api/models/autoscaling_criteria.py +73 -0
- render/public_api/models/autoscaling_criteria_percentage.py +68 -0
- render/public_api/models/autoscaling_ended.py +67 -0
- render/public_api/models/autoscaling_started.py +105 -0
- render/public_api/models/background_worker_details.py +256 -0
- render/public_api/models/background_worker_details_patch.py +184 -0
- render/public_api/models/background_worker_details_post.py +266 -0
- render/public_api/models/blueprint.py +128 -0
- render/public_api/models/blueprint_detail.py +150 -0
- render/public_api/models/blueprint_patch.py +77 -0
- render/public_api/models/blueprint_with_cursor.py +74 -0
- render/public_api/models/branch_deleted.py +67 -0
- render/public_api/models/build.py +148 -0
- render/public_api/models/build_config.py +96 -0
- render/public_api/models/build_deploy_end_reason.py +122 -0
- render/public_api/models/build_deploy_end_reason_id.py +59 -0
- render/public_api/models/build_deploy_trigger.py +150 -0
- render/public_api/models/build_ended.py +91 -0
- render/public_api/models/build_filter.py +67 -0
- render/public_api/models/build_plan.py +9 -0
- render/public_api/models/build_runtime.py +14 -0
- render/public_api/models/build_started.py +73 -0
- render/public_api/models/build_status.py +13 -0
- render/public_api/models/cache.py +61 -0
- render/public_api/models/cache_profile.py +10 -0
- render/public_api/models/cidr_block_and_description.py +67 -0
- render/public_api/models/commit_ignored.py +67 -0
- render/public_api/models/commit_ref.py +59 -0
- render/public_api/models/connect_sandbox_files_operation.py +9 -0
- render/public_api/models/connect_sandbox_run_operation.py +9 -0
- render/public_api/models/create_custom_domain_body.py +59 -0
- render/public_api/models/create_deploy_body.py +130 -0
- render/public_api/models/create_deploy_body_clear_cache.py +9 -0
- render/public_api/models/create_ephemeral_shell_body.py +69 -0
- render/public_api/models/create_ephemeral_shell_response_201.py +59 -0
- render/public_api/models/create_registry_credential_body.py +93 -0
- render/public_api/models/create_version.py +70 -0
- render/public_api/models/credential_create_input.py +59 -0
- render/public_api/models/cron_job_details.py +163 -0
- render/public_api/models/cron_job_details_patch.py +138 -0
- render/public_api/models/cron_job_details_post.py +172 -0
- render/public_api/models/cron_job_run.py +123 -0
- render/public_api/models/cron_job_run_ended.py +110 -0
- render/public_api/models/cron_job_run_started.py +59 -0
- render/public_api/models/cron_job_run_status.py +11 -0
- render/public_api/models/custom_domain.py +135 -0
- render/public_api/models/custom_domain_domain_type.py +9 -0
- render/public_api/models/custom_domain_server.py +68 -0
- render/public_api/models/custom_domain_verification_status.py +9 -0
- render/public_api/models/custom_domain_with_cursor.py +73 -0
- render/public_api/models/database_role.py +9 -0
- render/public_api/models/database_status.py +18 -0
- render/public_api/models/dedicated_ip.py +146 -0
- render/public_api/models/dedicated_ip_status.py +14 -0
- render/public_api/models/dedicated_ippatch.py +81 -0
- render/public_api/models/dedicated_ippost.py +100 -0
- render/public_api/models/deploy.py +211 -0
- render/public_api/models/deploy_commit.py +86 -0
- render/public_api/models/deploy_ended.py +102 -0
- render/public_api/models/deploy_image.py +78 -0
- render/public_api/models/deploy_mode.py +9 -0
- render/public_api/models/deploy_started.py +85 -0
- render/public_api/models/deploy_status.py +18 -0
- render/public_api/models/deploy_trigger.py +17 -0
- render/public_api/models/deploy_with_cursor.py +81 -0
- render/public_api/models/disk.py +83 -0
- render/public_api/models/disk_created.py +67 -0
- render/public_api/models/disk_deleted.py +59 -0
- render/public_api/models/disk_details.py +112 -0
- render/public_api/models/disk_patch.py +77 -0
- render/public_api/models/disk_post.py +83 -0
- render/public_api/models/disk_snapshot.py +88 -0
- render/public_api/models/disk_updated.py +75 -0
- render/public_api/models/disk_with_cursor.py +73 -0
- render/public_api/models/docker_details.py +108 -0
- render/public_api/models/docker_details_patch.py +86 -0
- render/public_api/models/docker_details_post.py +86 -0
- render/public_api/models/edge_cache_disabled.py +65 -0
- render/public_api/models/edge_cache_enabled.py +65 -0
- render/public_api/models/edge_cache_purged.py +65 -0
- render/public_api/models/edge_cache_trigger.py +91 -0
- render/public_api/models/env_group.py +162 -0
- render/public_api/models/env_group_link.py +77 -0
- render/public_api/models/env_group_meta.py +126 -0
- render/public_api/models/env_group_patch_input.py +59 -0
- render/public_api/models/env_group_post_input.py +161 -0
- render/public_api/models/env_var.py +67 -0
- render/public_api/models/env_var_generate_value.py +59 -0
- render/public_api/models/env_var_key_generate_value.py +68 -0
- render/public_api/models/env_var_key_value.py +67 -0
- render/public_api/models/env_var_value.py +59 -0
- render/public_api/models/env_var_with_cursor.py +73 -0
- render/public_api/models/environment.py +152 -0
- render/public_api/models/environment_patch_input.py +112 -0
- render/public_api/models/environment_post_input.py +122 -0
- render/public_api/models/environment_resources_post_input.py +59 -0
- render/public_api/models/environment_with_cursor.py +74 -0
- render/public_api/models/error.py +79 -0
- render/public_api/models/error_code.py +10 -0
- render/public_api/models/event.py +1122 -0
- render/public_api/models/event_status.py +10 -0
- render/public_api/models/event_type.py +74 -0
- render/public_api/models/execution.py +144 -0
- render/public_api/models/execution_operation.py +11 -0
- render/public_api/models/execution_type.py +9 -0
- render/public_api/models/failure_reason.py +138 -0
- render/public_api/models/filter_application_values_collection_item.py +78 -0
- render/public_api/models/filter_application_values_collection_item_filter.py +8 -0
- render/public_api/models/filter_http_values_collection_item.py +78 -0
- render/public_api/models/filter_http_values_collection_item_filter.py +9 -0
- render/public_api/models/get_bandwidth_sources_response_200.py +75 -0
- render/public_api/models/get_bandwidth_sources_response_200_data_item.py +101 -0
- render/public_api/models/get_bandwidth_sources_response_200_data_item_labels.py +78 -0
- render/public_api/models/get_bandwidth_sources_response_200_data_item_labels_traffic_source.py +12 -0
- render/public_api/models/get_bandwidth_sources_response_200_data_item_values_item.py +68 -0
- render/public_api/models/get_cpu_aggregation_method.py +10 -0
- render/public_api/models/get_http_requests_aggregate_by.py +9 -0
- render/public_api/models/get_object_output.py +71 -0
- render/public_api/models/get_task_runs_completed_aggregate_by.py +8 -0
- render/public_api/models/get_task_runs_completed_state.py +9 -0
- render/public_api/models/header.py +83 -0
- render/public_api/models/header_input.py +76 -0
- render/public_api/models/header_with_cursor.py +73 -0
- render/public_api/models/image.py +80 -0
- render/public_api/models/image_pull_failed.py +67 -0
- render/public_api/models/initial_deploy_hook_ended.py +59 -0
- render/public_api/models/initial_deploy_hook_started.py +59 -0
- render/public_api/models/instance_count_changed.py +67 -0
- render/public_api/models/instance_type_changed.py +67 -0
- render/public_api/models/internal_routing.py +8 -0
- render/public_api/models/job.py +144 -0
- render/public_api/models/job_run_ended.py +92 -0
- render/public_api/models/job_status.py +12 -0
- render/public_api/models/job_with_cursor.py +73 -0
- render/public_api/models/key_value.py +182 -0
- render/public_api/models/key_value_available.py +44 -0
- render/public_api/models/key_value_config_restart.py +67 -0
- render/public_api/models/key_value_connection_info.py +76 -0
- render/public_api/models/key_value_detail.py +192 -0
- render/public_api/models/key_value_options.py +79 -0
- render/public_api/models/key_value_patch_input.py +138 -0
- render/public_api/models/key_value_plan.py +13 -0
- render/public_api/models/key_value_post_input.py +166 -0
- render/public_api/models/key_value_unhealthy.py +44 -0
- render/public_api/models/key_value_with_cursor.py +73 -0
- render/public_api/models/label.py +68 -0
- render/public_api/models/list_custom_domains_domain_type.py +9 -0
- render/public_api/models/list_custom_domains_verification_status.py +9 -0
- render/public_api/models/list_logs_response_200.py +102 -0
- render/public_api/models/list_logs_values_label.py +13 -0
- render/public_api/models/list_objects_response.py +92 -0
- render/public_api/models/list_postgres_suspended_item.py +9 -0
- render/public_api/models/list_postgres_users_response_200_item.py +86 -0
- render/public_api/models/list_routes_type_item.py +9 -0
- render/public_api/models/list_services_suspended_item.py +9 -0
- render/public_api/models/log.py +100 -0
- render/public_api/models/log_direction.py +9 -0
- render/public_api/models/log_label.py +70 -0
- render/public_api/models/log_label_name.py +22 -0
- render/public_api/models/log_stream_owner_update.py +80 -0
- render/public_api/models/log_stream_preview_setting.py +9 -0
- render/public_api/models/log_stream_resource_update.py +80 -0
- render/public_api/models/log_stream_setting.py +9 -0
- render/public_api/models/maintenance_ended.py +44 -0
- render/public_api/models/maintenance_mode.py +68 -0
- render/public_api/models/maintenance_mode_enabled.py +59 -0
- render/public_api/models/maintenance_mode_uri_updated.py +67 -0
- render/public_api/models/maintenance_run.py +105 -0
- render/public_api/models/maintenance_run_patch.py +69 -0
- render/public_api/models/maintenance_run_with_resource.py +114 -0
- render/public_api/models/maintenance_started.py +65 -0
- render/public_api/models/maintenance_state.py +13 -0
- render/public_api/models/maintenance_trigger.py +91 -0
- render/public_api/models/maxmemory_policy.py +15 -0
- render/public_api/models/metrics_stream.py +77 -0
- render/public_api/models/metrics_stream_input.py +86 -0
- render/public_api/models/native_environment_details.py +78 -0
- render/public_api/models/native_environment_details_patch.py +68 -0
- render/public_api/models/native_environment_details_post.py +68 -0
- render/public_api/models/notification_override.py +78 -0
- render/public_api/models/notification_override_with_cursor.py +73 -0
- render/public_api/models/notification_service_override.py +78 -0
- render/public_api/models/notification_service_override_patch.py +84 -0
- render/public_api/models/notification_setting.py +93 -0
- render/public_api/models/notification_setting_patch.py +85 -0
- render/public_api/models/notify_override.py +11 -0
- render/public_api/models/notify_preview_override.py +10 -0
- render/public_api/models/notify_setting.py +10 -0
- render/public_api/models/notify_setting_v2.py +10 -0
- render/public_api/models/object_metadata.py +77 -0
- render/public_api/models/object_with_cursor.py +73 -0
- render/public_api/models/oom_killed.py +59 -0
- render/public_api/models/otel_provider_type.py +16 -0
- render/public_api/models/outbound_ips.py +81 -0
- render/public_api/models/outbound_ips_type.py +9 -0
- render/public_api/models/owner.py +121 -0
- render/public_api/models/owner_log_stream_setting.py +86 -0
- render/public_api/models/owner_type.py +9 -0
- render/public_api/models/owner_with_cursor.py +81 -0
- render/public_api/models/paid_plan.py +13 -0
- render/public_api/models/patch_route_response_200.py +72 -0
- render/public_api/models/persistence_mode.py +10 -0
- render/public_api/models/pipeline_minutes_exhausted.py +73 -0
- render/public_api/models/plan.py +22 -0
- render/public_api/models/post_job_body.py +70 -0
- render/public_api/models/postgres.py +300 -0
- render/public_api/models/postgres_available.py +44 -0
- render/public_api/models/postgres_available_parameter_override.py +106 -0
- render/public_api/models/postgres_available_parameter_overrides_result.py +75 -0
- render/public_api/models/postgres_backup_completed.py +44 -0
- render/public_api/models/postgres_backup_failed.py +44 -0
- render/public_api/models/postgres_backup_started.py +44 -0
- render/public_api/models/postgres_cluster_leader_changed.py +59 -0
- render/public_api/models/postgres_connection_info.py +103 -0
- render/public_api/models/postgres_connection_pool_changed.py +91 -0
- render/public_api/models/postgres_connection_pool_enabled_changed.py +83 -0
- render/public_api/models/postgres_created.py +72 -0
- render/public_api/models/postgres_detail.py +336 -0
- render/public_api/models/postgres_detail_suspended.py +9 -0
- render/public_api/models/postgres_disk_size_changed.py +91 -0
- render/public_api/models/postgres_export.py +80 -0
- render/public_api/models/postgres_ha_status_changed.py +67 -0
- render/public_api/models/postgres_parameter_overrides.py +44 -0
- render/public_api/models/postgres_parameter_overrides_wrapper.py +65 -0
- render/public_api/models/postgres_patch_input.py +199 -0
- render/public_api/models/postgres_pitr_checkpoint_completed.py +44 -0
- render/public_api/models/postgres_pitr_checkpoint_failed.py +44 -0
- render/public_api/models/postgres_pitr_checkpoint_started.py +44 -0
- render/public_api/models/postgres_plans.py +35 -0
- render/public_api/models/postgres_post_input.py +254 -0
- render/public_api/models/postgres_process.py +185 -0
- render/public_api/models/postgres_processes_result.py +73 -0
- render/public_api/models/postgres_put_parameter_overrides_result.py +82 -0
- render/public_api/models/postgres_query_statistic.py +222 -0
- render/public_api/models/postgres_read_replica_stale.py +44 -0
- render/public_api/models/postgres_read_replicas_changed.py +67 -0
- render/public_api/models/postgres_replication_setup_input.py +96 -0
- render/public_api/models/postgres_restarted.py +72 -0
- render/public_api/models/postgres_size.py +96 -0
- render/public_api/models/postgres_sizes_result.py +73 -0
- render/public_api/models/postgres_suspended.py +9 -0
- render/public_api/models/postgres_table_scan.py +87 -0
- render/public_api/models/postgres_table_scans_result.py +73 -0
- render/public_api/models/postgres_top_queries_result.py +73 -0
- render/public_api/models/postgres_unavailable.py +44 -0
- render/public_api/models/postgres_upgrade_failed.py +67 -0
- render/public_api/models/postgres_upgrade_started.py +91 -0
- render/public_api/models/postgres_upgrade_succeeded.py +67 -0
- render/public_api/models/postgres_version.py +15 -0
- render/public_api/models/postgres_with_cursor.py +73 -0
- render/public_api/models/pre_deploy_ended.py +99 -0
- render/public_api/models/pre_deploy_started.py +67 -0
- render/public_api/models/preview_input.py +94 -0
- render/public_api/models/previews.py +67 -0
- render/public_api/models/previews_generation.py +10 -0
- render/public_api/models/private_service_details.py +282 -0
- render/public_api/models/private_service_details_patch.py +184 -0
- render/public_api/models/private_service_details_post.py +266 -0
- render/public_api/models/project.py +108 -0
- render/public_api/models/project_patch_input.py +60 -0
- render/public_api/models/project_post_environment_input.py +114 -0
- render/public_api/models/project_post_input.py +89 -0
- render/public_api/models/project_with_cursor.py +73 -0
- render/public_api/models/protected_status.py +9 -0
- render/public_api/models/pull_request_previews_enabled.py +9 -0
- render/public_api/models/put_object_input.py +59 -0
- render/public_api/models/put_object_output.py +79 -0
- render/public_api/models/read_replica.py +91 -0
- render/public_api/models/read_replica_input.py +83 -0
- render/public_api/models/recovery_info.py +80 -0
- render/public_api/models/recovery_info_recovery_status.py +10 -0
- render/public_api/models/recovery_input.py +114 -0
- render/public_api/models/redis.py +182 -0
- render/public_api/models/redis_connection_info.py +76 -0
- render/public_api/models/redis_detail.py +192 -0
- render/public_api/models/redis_options.py +79 -0
- render/public_api/models/redis_patch_input.py +138 -0
- render/public_api/models/redis_plan.py +13 -0
- render/public_api/models/redis_post_input.py +166 -0
- render/public_api/models/redis_with_cursor.py +73 -0
- render/public_api/models/region.py +12 -0
- render/public_api/models/registry_credential.py +95 -0
- render/public_api/models/registry_credential_registry.py +12 -0
- render/public_api/models/registry_credential_summary.py +67 -0
- render/public_api/models/render_subdomain_policy.py +9 -0
- render/public_api/models/resource.py +67 -0
- render/public_api/models/resource_log_stream_setting.py +87 -0
- render/public_api/models/resource_ref.py +77 -0
- render/public_api/models/resource_ref_type.py +17 -0
- render/public_api/models/rollback_deploy_body.py +59 -0
- render/public_api/models/route.py +93 -0
- render/public_api/models/route_patch.py +60 -0
- render/public_api/models/route_post.py +88 -0
- render/public_api/models/route_put.py +77 -0
- render/public_api/models/route_type.py +9 -0
- render/public_api/models/route_with_cursor.py +73 -0
- render/public_api/models/run_task.py +96 -0
- render/public_api/models/runtime.py +12 -0
- render/public_api/models/sandbox.py +149 -0
- render/public_api/models/sandbox_connect_request.py +62 -0
- render/public_api/models/sandbox_connect_response.py +98 -0
- render/public_api/models/sandbox_directory_listing.py +82 -0
- render/public_api/models/sandbox_exec_update_request.py +60 -0
- render/public_api/models/sandbox_exec_update_response.py +130 -0
- render/public_api/models/sandbox_file_entry.py +98 -0
- render/public_api/models/sandbox_file_entry_type.py +10 -0
- render/public_api/models/sandbox_group.py +141 -0
- render/public_api/models/sandbox_group_with_cursor.py +74 -0
- render/public_api/models/sandbox_network_policy.py +61 -0
- render/public_api/models/sandbox_network_policy_default.py +9 -0
- render/public_api/models/sandbox_plan.py +10 -0
- render/public_api/models/sandbox_post.py +138 -0
- render/public_api/models/sandbox_post_env.py +44 -0
- render/public_api/models/sandbox_status.py +13 -0
- render/public_api/models/sandbox_with_cursor.py +74 -0
- render/public_api/models/scale_service_body.py +59 -0
- render/public_api/models/schemas_build_filter.py +70 -0
- render/public_api/models/schemas_image.py +96 -0
- render/public_api/models/schemas_user.py +68 -0
- render/public_api/models/secret_file.py +67 -0
- render/public_api/models/secret_file_input.py +67 -0
- render/public_api/models/secret_file_with_cursor.py +73 -0
- render/public_api/models/server_available.py +44 -0
- render/public_api/models/server_failed.py +81 -0
- render/public_api/models/server_hardware_failure.py +44 -0
- render/public_api/models/server_port.py +69 -0
- render/public_api/models/server_port_protocol.py +9 -0
- render/public_api/models/server_restarted.py +66 -0
- render/public_api/models/service_disk.py +78 -0
- render/public_api/models/service_env.py +15 -0
- render/public_api/models/service_event.py +770 -0
- render/public_api/models/service_event_type.py +48 -0
- render/public_api/models/service_event_with_cursor.py +66 -0
- render/public_api/models/service_instance.py +69 -0
- render/public_api/models/service_patch.py +273 -0
- render/public_api/models/service_post.py +357 -0
- render/public_api/models/service_resumed.py +44 -0
- render/public_api/models/service_runtime.py +15 -0
- render/public_api/models/service_suspended.py +44 -0
- render/public_api/models/service_type.py +12 -0
- render/public_api/models/service_type_short.py +12 -0
- render/public_api/models/setup_postgres_replication_response_201.py +44 -0
- render/public_api/models/snapshot_restore_post.py +72 -0
- render/public_api/models/static_site_details.py +183 -0
- render/public_api/models/static_site_details_patch.py +147 -0
- render/public_api/models/static_site_details_post.py +189 -0
- render/public_api/models/status.py +12 -0
- render/public_api/models/stream_sandbox_logs_accept.py +8 -0
- render/public_api/models/stream_task_runs_events_accept.py +8 -0
- render/public_api/models/suspender_added.py +83 -0
- render/public_api/models/suspender_removed.py +83 -0
- render/public_api/models/suspender_type.py +14 -0
- render/public_api/models/sync.py +118 -0
- render/public_api/models/sync_state.py +12 -0
- render/public_api/models/sync_with_cursor.py +74 -0
- render/public_api/models/task.py +97 -0
- render/public_api/models/task_attempt.py +121 -0
- render/public_api/models/task_attempt_details.py +141 -0
- render/public_api/models/task_data_type_1.py +44 -0
- render/public_api/models/task_run.py +168 -0
- render/public_api/models/task_run_details.py +216 -0
- render/public_api/models/task_run_status.py +14 -0
- render/public_api/models/task_run_with_cursor.py +73 -0
- render/public_api/models/task_with_cursor.py +73 -0
- render/public_api/models/team_member.py +102 -0
- render/public_api/models/team_member_role.py +12 -0
- render/public_api/models/team_member_status.py +9 -0
- render/public_api/models/time_series.py +100 -0
- render/public_api/models/time_series_value.py +70 -0
- render/public_api/models/update_env_group_secret_file_body.py +59 -0
- render/public_api/models/update_registry_credential_body.py +85 -0
- render/public_api/models/update_workspace_member_body.py +61 -0
- render/public_api/models/user.py +67 -0
- render/public_api/models/validate_blueprint_request.py +84 -0
- render/public_api/models/validate_blueprint_response.py +105 -0
- render/public_api/models/validation_error.py +88 -0
- render/public_api/models/validation_plan_summary.py +107 -0
- render/public_api/models/web_service_details.py +385 -0
- render/public_api/models/web_service_details_patch.py +270 -0
- render/public_api/models/web_service_details_post.py +354 -0
- render/public_api/models/webhook.py +110 -0
- render/public_api/models/webhook_event.py +115 -0
- render/public_api/models/webhook_event_with_cursor.py +73 -0
- render/public_api/models/webhook_patch_input.py +98 -0
- render/public_api/models/webhook_post_input.py +102 -0
- render/public_api/models/webhook_with_cursor.py +73 -0
- render/public_api/models/workflow.py +163 -0
- render/public_api/models/workflow_create.py +169 -0
- render/public_api/models/workflow_update.py +109 -0
- render/public_api/models/workflow_version.py +95 -0
- render/public_api/models/workflow_version_status.py +13 -0
- render/public_api/models/workflow_version_with_cursor.py +73 -0
- render/public_api/models/workflow_with_cursor.py +73 -0
- render/public_api/models/zero_downtime_redeploy_ended.py +44 -0
- render/public_api/models/zero_downtime_redeploy_started.py +59 -0
- render/public_api/py.typed +1 -0
- render/public_api/types.py +54 -0
- render/py.typed +0 -0
- render/render.py +81 -0
- render/render_async.py +80 -0
- render/version.py +27 -0
- render/workflows/__init__.py +25 -0
- render/workflows/_callback_models.py +194 -0
- render/workflows/_uds_http.py +128 -0
- render/workflows/app.py +263 -0
- render/workflows/cli.py +62 -0
- render/workflows/client.py +354 -0
- render/workflows/context.py +75 -0
- render/workflows/executor.py +86 -0
- render/workflows/runner.py +174 -0
- render/workflows/task.py +348 -0
- render/workflows/tests/__init__.py +0 -0
- render/workflows/tests/test_app.py +343 -0
- render/workflows/tests/test_cli.py +134 -0
- render/workflows/tests/test_client.py +59 -0
- render/workflows/tests/test_context.py +118 -0
- render/workflows/tests/test_end_to_end.py +307 -0
- render/workflows/tests/test_executor.py +221 -0
- render/workflows/tests/test_lazy_imports.py +100 -0
- render/workflows/tests/test_registration.py +293 -0
- render/workflows/tests/test_translate_errors.py +126 -0
- render/workflows/tests/test_uds_http.py +199 -0
- render/workflows/tests/test_version.py +15 -0
- render-1.0.1.dist-info/METADATA +382 -0
- render-1.0.1.dist-info/RECORD +799 -0
- render-1.0.1.dist-info/WHEEL +4 -0
- render-1.0.1.dist-info/entry_points.txt +2 -0
- render-1.0.1.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Auto-generated sync version. Do not edit — run scripts/unasync.py instead.
|
|
2
|
+
|
|
3
|
+
"""High-level client for creating and operating Render sandboxes."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from collections.abc import Iterator, Sequence
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from render.client.errors import RenderError
|
|
12
|
+
from render.experimental.sandbox.api_sync import SyncSandboxApi
|
|
13
|
+
from render.experimental.sandbox.files import normalize_remote_path
|
|
14
|
+
from render.experimental.sandbox.types import Sandbox, SandboxExecEvent, SandboxList
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from render.public_api.client import AuthenticatedClient, Client
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SyncSandboxClient:
|
|
21
|
+
"""High-level client for creating and operating Render sandboxes."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
client: AuthenticatedClient | Client,
|
|
26
|
+
default_owner_id: str | None = None,
|
|
27
|
+
default_region: str | None = None,
|
|
28
|
+
):
|
|
29
|
+
self.client = client
|
|
30
|
+
self.api = SyncSandboxApi(client)
|
|
31
|
+
self._default_owner_id = default_owner_id
|
|
32
|
+
self._default_region = default_region
|
|
33
|
+
|
|
34
|
+
def _resolve_owner_id(self, owner_id: str | None) -> str:
|
|
35
|
+
resolved = owner_id or self._default_owner_id
|
|
36
|
+
if not resolved:
|
|
37
|
+
raise RenderError(
|
|
38
|
+
"owner_id is required. Provide it as a parameter or set the RENDER_WORKSPACE_ID environment variable."
|
|
39
|
+
)
|
|
40
|
+
return resolved
|
|
41
|
+
|
|
42
|
+
def create(
|
|
43
|
+
self,
|
|
44
|
+
*,
|
|
45
|
+
owner_id: str | None = None,
|
|
46
|
+
plan: str | None = None,
|
|
47
|
+
timeout_seconds: int | None = None,
|
|
48
|
+
network_policy: str | None = None,
|
|
49
|
+
region: str | None = None,
|
|
50
|
+
env: dict[str, str] | None = None,
|
|
51
|
+
) -> Sandbox:
|
|
52
|
+
"""Create a sandbox and return its initial snapshot.
|
|
53
|
+
|
|
54
|
+
All parameters are optional. Unspecified fields fall back to the
|
|
55
|
+
workspace defaults enforced by the API (plan starter, 7200s timeout,
|
|
56
|
+
workspace default region and network policy).
|
|
57
|
+
"""
|
|
58
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
59
|
+
resolved_region = region or self._default_region
|
|
60
|
+
return self.api.create(
|
|
61
|
+
owner_id=resolved_owner_id,
|
|
62
|
+
plan=plan,
|
|
63
|
+
timeout_seconds=timeout_seconds,
|
|
64
|
+
network_policy=network_policy,
|
|
65
|
+
region=resolved_region,
|
|
66
|
+
env=env,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def from_id(self, sandbox_id: str, *, owner_id: str | None = None) -> Sandbox:
|
|
70
|
+
"""Reconnect to an existing sandbox by id.
|
|
71
|
+
|
|
72
|
+
Raises SandboxNotFoundError if the sandbox does not exist or has been
|
|
73
|
+
terminated.
|
|
74
|
+
"""
|
|
75
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
76
|
+
return self.api.get(sandbox_id, resolved_owner_id)
|
|
77
|
+
|
|
78
|
+
def list(
|
|
79
|
+
self,
|
|
80
|
+
*,
|
|
81
|
+
owner_id: str | None = None,
|
|
82
|
+
status: str | Sequence[str] | None = None,
|
|
83
|
+
cursor: str | None = None,
|
|
84
|
+
limit: int | None = None,
|
|
85
|
+
) -> SandboxList:
|
|
86
|
+
"""List sandboxes for a workspace.
|
|
87
|
+
|
|
88
|
+
status filters by sandbox status, one or a sequence of them (each one
|
|
89
|
+
of creating, running, suspended, resuming, errored, terminated). limit
|
|
90
|
+
is capped at 100 by the API.
|
|
91
|
+
"""
|
|
92
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
93
|
+
return self.api.list(resolved_owner_id, status, cursor, limit)
|
|
94
|
+
|
|
95
|
+
def terminate(self, sandbox_id: str, *, owner_id: str | None = None) -> None:
|
|
96
|
+
"""Terminate a sandbox.
|
|
97
|
+
|
|
98
|
+
Idempotent for an already-terminated sandbox (the API returns 204).
|
|
99
|
+
Raises SandboxNotFoundError if the sandbox id was never valid.
|
|
100
|
+
"""
|
|
101
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
102
|
+
self.api.terminate(sandbox_id, resolved_owner_id)
|
|
103
|
+
|
|
104
|
+
def copy_to(
|
|
105
|
+
self,
|
|
106
|
+
sandbox_id: str,
|
|
107
|
+
local_path: str | os.PathLike[str],
|
|
108
|
+
remote_path: str,
|
|
109
|
+
*,
|
|
110
|
+
owner_id: str | None = None,
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Copy a local file or directory into the sandbox at remote_path.
|
|
113
|
+
|
|
114
|
+
A relative remote_path resolves under the sandbox's home directory and
|
|
115
|
+
an absolute one addresses the filesystem, as scp does. The path is
|
|
116
|
+
normalized before it is sent, so a trailing slash or a redundant
|
|
117
|
+
separator is accepted rather than rejected by the sandbox.
|
|
118
|
+
|
|
119
|
+
A file is uploaded as raw bytes. A directory is streamed as an archive
|
|
120
|
+
that the sandbox extracts at remote_path: names are relative to
|
|
121
|
+
local_path, symlinks are stored rather than followed, and sockets,
|
|
122
|
+
fifos and other special files are skipped. Passing one of those special
|
|
123
|
+
files as local_path raises ValueError.
|
|
124
|
+
"""
|
|
125
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
126
|
+
self.api.upload(sandbox_id, local_path, remote_path, resolved_owner_id)
|
|
127
|
+
|
|
128
|
+
def exec(
|
|
129
|
+
self,
|
|
130
|
+
sandbox_id: str,
|
|
131
|
+
command: str,
|
|
132
|
+
*,
|
|
133
|
+
owner_id: str | None = None,
|
|
134
|
+
) -> Iterator[SandboxExecEvent]:
|
|
135
|
+
"""Run a command in a sandbox and stream its output.
|
|
136
|
+
|
|
137
|
+
command is passed to ``bash -c`` in the sandbox. Yields
|
|
138
|
+
SandboxExecOutput chunks as they arrive and a final SandboxExecExit.
|
|
139
|
+
A non-zero exit code is reported via SandboxExecExit, not an exception.
|
|
140
|
+
Raises SandboxExecStreamError if the sandbox reports a terminal error.
|
|
141
|
+
"""
|
|
142
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
143
|
+
return self.api.exec_stream(sandbox_id, command, resolved_owner_id)
|
|
144
|
+
|
|
145
|
+
def copy_from(
|
|
146
|
+
self,
|
|
147
|
+
sandbox_id: str,
|
|
148
|
+
remote_path: str,
|
|
149
|
+
local_path: str | os.PathLike[str],
|
|
150
|
+
*,
|
|
151
|
+
owner_id: str | None = None,
|
|
152
|
+
) -> str:
|
|
153
|
+
"""Copy a file or directory out of a sandbox, returning the path written.
|
|
154
|
+
|
|
155
|
+
A directory is extracted under local_path. A file is written to
|
|
156
|
+
local_path, or into it under the name the sandbox suggests when
|
|
157
|
+
local_path is an existing directory. remote_path is cleaned before it
|
|
158
|
+
is sent, since the API rejects a path carrying "." , ".." or redundant
|
|
159
|
+
separators. Raises SandboxFileNotFoundError if the sandbox has no such
|
|
160
|
+
path. Raises SandboxDownloadError if the response is unsafe or the download
|
|
161
|
+
cannot be written or extracted locally. Directory extraction is not atomic
|
|
162
|
+
and may leave partial contents after a failure.
|
|
163
|
+
"""
|
|
164
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
165
|
+
return self.api.download_file(
|
|
166
|
+
sandbox_id,
|
|
167
|
+
normalize_remote_path(remote_path),
|
|
168
|
+
os.fspath(local_path),
|
|
169
|
+
resolved_owner_id,
|
|
170
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Sandbox-specific exceptions."""
|
|
2
|
+
|
|
3
|
+
from render.client.errors import ClientError, RenderError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SandboxNotFoundError(ClientError):
|
|
7
|
+
"""Raised when a sandbox does not exist or is no longer retrievable."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SandboxFileNotFoundError(ClientError):
|
|
11
|
+
"""Raised when a file operation names a path the sandbox does not have.
|
|
12
|
+
|
|
13
|
+
Distinct from SandboxNotFoundError: the sandbox is alive, the path is not.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SandboxDownloadError(RenderError):
|
|
18
|
+
"""Raised when a download cannot be written to disk safely: a truncated
|
|
19
|
+
archive, an entry that escapes the destination, or a response encoding the
|
|
20
|
+
SDK cannot decode."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SandboxExecError(RenderError):
|
|
24
|
+
"""Raised when an exec stream ends abnormally: no terminal event, an
|
|
25
|
+
unknown event type, or unparseable event data."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SandboxExecStreamError(RenderError):
|
|
29
|
+
"""Raised when an exec stream delivers a terminal error event.
|
|
30
|
+
|
|
31
|
+
Carries the in-stream status and message reported by the sandbox.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, status: int, message: str) -> None:
|
|
35
|
+
super().__init__(message)
|
|
36
|
+
self.status = status
|
|
37
|
+
self.message = message
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""Filesystem side of sandbox file transfer.
|
|
2
|
+
|
|
3
|
+
Nothing here talks to the sandbox, so the async and sync sandbox APIs both
|
|
4
|
+
import this module unchanged and unasync leaves it alone. The one pair that
|
|
5
|
+
differs between them, arun_blocking and run_blocking, is defined here for
|
|
6
|
+
the same reason: a transformed module cannot hold both.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import contextlib
|
|
13
|
+
import email.message
|
|
14
|
+
import ntpath
|
|
15
|
+
import os
|
|
16
|
+
import posixpath
|
|
17
|
+
import stat
|
|
18
|
+
import tarfile
|
|
19
|
+
import tempfile
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
from types import TracebackType
|
|
22
|
+
from typing import IO, Any, TypeVar, cast
|
|
23
|
+
|
|
24
|
+
from render.experimental.sandbox.errors import SandboxDownloadError
|
|
25
|
+
|
|
26
|
+
_T = TypeVar("_T")
|
|
27
|
+
|
|
28
|
+
_PARTIAL_PREFIX = ".render-partial-"
|
|
29
|
+
|
|
30
|
+
# Codings httpx negotiates and decodes before we see the body. Anything else
|
|
31
|
+
# arrives as-is, and passing those bytes on means writing them to disk as the
|
|
32
|
+
# file or reading them as a tar, so we refuse instead.
|
|
33
|
+
_DECODED_CODINGS = frozenset({"gzip", "x-gzip"})
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def arun_blocking(func: Callable[..., _T], *args: Any) -> _T:
|
|
37
|
+
"""Run a blocking filesystem call off the event loop."""
|
|
38
|
+
return await asyncio.to_thread(func, *args)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def run_blocking(func: Callable[..., _T], *args: Any) -> _T:
|
|
42
|
+
"""Run a blocking filesystem call. The sync client owns its own thread."""
|
|
43
|
+
return func(*args)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def normalize_remote_path(path: str) -> str:
|
|
47
|
+
"""Clean a remote path the way the sandbox API requires.
|
|
48
|
+
|
|
49
|
+
The API rejects any path that Go's path.Clean would change, and the CLI
|
|
50
|
+
cleans before it mints a token, so `render sandbox cp` and the SDK agree on
|
|
51
|
+
what a path means. An empty path raises rather than cleaning to ".", which
|
|
52
|
+
would quietly transfer the home directory, and matches what copy_to does.
|
|
53
|
+
"""
|
|
54
|
+
if not path:
|
|
55
|
+
raise ValueError("remote_path is required")
|
|
56
|
+
cleaned = posixpath.normpath(path)
|
|
57
|
+
# normpath keeps exactly two leading slashes, which POSIX reserves; Go's
|
|
58
|
+
# path.Clean collapses them, and the API compares against path.Clean.
|
|
59
|
+
if cleaned.startswith("//") and not cleaned.startswith("///"):
|
|
60
|
+
cleaned = cleaned[1:]
|
|
61
|
+
return cleaned
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def media_type(header: str) -> str:
|
|
65
|
+
"""The media type of a Content-Type header, lowercased, parameters dropped."""
|
|
66
|
+
return header.split(";", 1)[0].strip().lower()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def disposition_filename(header: str) -> str:
|
|
70
|
+
"""The filename a Content-Disposition header suggests, or "" if it has none."""
|
|
71
|
+
if not header:
|
|
72
|
+
return ""
|
|
73
|
+
message = email.message.Message()
|
|
74
|
+
message["Content-Disposition"] = header
|
|
75
|
+
name = message.get_filename()
|
|
76
|
+
return name if isinstance(name, str) else ""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def base_name(name: str) -> str:
|
|
80
|
+
"""Reduce an untrusted name to a single path component, or "" if nothing
|
|
81
|
+
usable is left.
|
|
82
|
+
|
|
83
|
+
Content-Disposition filenames come from the sandbox and nothing in the
|
|
84
|
+
parsing strips separators, so a filename of "../../x" would otherwise escape
|
|
85
|
+
the destination directory. ntpath treats both separators and a drive prefix
|
|
86
|
+
as separators on every platform, which is the strictest reading available.
|
|
87
|
+
"""
|
|
88
|
+
base = ntpath.basename(name)
|
|
89
|
+
if base in ("", ".", ".."):
|
|
90
|
+
return ""
|
|
91
|
+
return base
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def check_content_encoding(header: str) -> None:
|
|
95
|
+
"""Refuse a body that arrives under a coding httpx has not already undone.
|
|
96
|
+
|
|
97
|
+
httpx sets its own Accept-Encoding, transparently decodes a gzip response,
|
|
98
|
+
and leaves the header in place, so gzip here means the body is already
|
|
99
|
+
plain. Content-Type: application/gzip is a payload type, not an encoding,
|
|
100
|
+
and is deliberately not handled here.
|
|
101
|
+
"""
|
|
102
|
+
codings = [token.strip().lower() for token in header.split(",")]
|
|
103
|
+
codings = [coding for coding in codings if coding and coding != "identity"]
|
|
104
|
+
if not codings:
|
|
105
|
+
return
|
|
106
|
+
# Multiple codings would have to be undone in reverse order; nothing sends
|
|
107
|
+
# them, so refuse rather than guess.
|
|
108
|
+
if len(codings) > 1 or codings[0] not in _DECODED_CODINGS:
|
|
109
|
+
raise SandboxDownloadError(f"cannot decode Content-Encoding {header!r}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def resolve_file_dest(local_path: str, filename: str, remote_path: str) -> str:
|
|
113
|
+
"""Where a single downloaded file lands.
|
|
114
|
+
|
|
115
|
+
Writing into local_path when it is an existing directory mirrors cp and scp;
|
|
116
|
+
the name comes from the sandbox, so it is reduced to one path component.
|
|
117
|
+
"""
|
|
118
|
+
if not os.path.isdir(local_path):
|
|
119
|
+
return local_path
|
|
120
|
+
name = base_name(filename) or base_name(remote_path)
|
|
121
|
+
if not name:
|
|
122
|
+
raise SandboxDownloadError(
|
|
123
|
+
f"cannot name the downloaded file under {local_path}: "
|
|
124
|
+
"the sandbox suggested no usable filename"
|
|
125
|
+
)
|
|
126
|
+
return os.path.join(local_path, name)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def parent_dir(path: str) -> str:
|
|
130
|
+
"""The directory holding path, as somewhere to put a temp sibling."""
|
|
131
|
+
return os.path.dirname(os.path.abspath(path))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def prepare_extract_dir(local_path: str) -> str:
|
|
135
|
+
"""Create the extraction destination and return where its spool file goes."""
|
|
136
|
+
os.makedirs(local_path, 0o750, exist_ok=True)
|
|
137
|
+
return parent_dir(local_path)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class PartialDownload:
|
|
141
|
+
"""A temp file holding a download in flight.
|
|
142
|
+
|
|
143
|
+
Everything the sandbox sends lands here first: a single file is renamed over
|
|
144
|
+
its destination once complete, and an archive is extracted from here. An
|
|
145
|
+
interrupted transfer leaves only the temp file, never a truncated file
|
|
146
|
+
looking complete at the destination. The name is unique, so an unrelated
|
|
147
|
+
file that happens to share the prefix survives, as does a second download
|
|
148
|
+
running against the same destination.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(self, directory: str):
|
|
152
|
+
fd, self.path = tempfile.mkstemp(prefix=_PARTIAL_PREFIX, dir=directory)
|
|
153
|
+
self._file: IO[bytes] | None = os.fdopen(fd, "wb")
|
|
154
|
+
|
|
155
|
+
def __enter__(self) -> PartialDownload:
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
def __exit__(
|
|
159
|
+
self,
|
|
160
|
+
exc_type: type[BaseException] | None,
|
|
161
|
+
exc: BaseException | None,
|
|
162
|
+
tb: TracebackType | None,
|
|
163
|
+
) -> None:
|
|
164
|
+
self._close()
|
|
165
|
+
# A no-op once commit has renamed the temp file away.
|
|
166
|
+
with contextlib.suppress(OSError):
|
|
167
|
+
os.remove(self.path)
|
|
168
|
+
|
|
169
|
+
def write(self, chunk: bytes) -> None:
|
|
170
|
+
if self._file is None:
|
|
171
|
+
raise SandboxDownloadError("write to a finished download")
|
|
172
|
+
self._file.write(chunk)
|
|
173
|
+
|
|
174
|
+
def commit(self, dest: str) -> str:
|
|
175
|
+
"""Move the completed download to dest, replacing what is there."""
|
|
176
|
+
# mkstemp opens 0o600. Widen to the 0o644 a plain create would produce
|
|
177
|
+
# under a default umask, which is also what extraction writes with.
|
|
178
|
+
os.chmod(self.path, 0o644)
|
|
179
|
+
self._close()
|
|
180
|
+
os.replace(self.path, dest)
|
|
181
|
+
return dest
|
|
182
|
+
|
|
183
|
+
def extract(self, dest: str) -> None:
|
|
184
|
+
"""Extract the completed download as a tar archive under dest."""
|
|
185
|
+
self._close()
|
|
186
|
+
with open(self.path, "rb") as archive:
|
|
187
|
+
extract_tar(dest, archive)
|
|
188
|
+
|
|
189
|
+
def _close(self) -> None:
|
|
190
|
+
if self._file is not None:
|
|
191
|
+
self._file.close()
|
|
192
|
+
self._file = None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class _TerminatedReader:
|
|
196
|
+
"""Reports the underlying stream's EOF as a truncated archive.
|
|
197
|
+
|
|
198
|
+
tarfile stops reading at the end-of-archive marker, so a complete archive
|
|
199
|
+
never reaches the end of the stream and never trips this; a truncated one
|
|
200
|
+
does. Nothing else catches that: tarfile reads a stream that simply stopped
|
|
201
|
+
on a 512-byte boundary as a clean end of archive, and httpx's decompressor
|
|
202
|
+
does not check the gzip trailer, so a cut-off transfer would otherwise
|
|
203
|
+
extract part of a tree and report success.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
def __init__(self, fileobj: IO[bytes]):
|
|
207
|
+
self._fileobj = fileobj
|
|
208
|
+
|
|
209
|
+
def read(self, size: int = -1) -> bytes:
|
|
210
|
+
if size == 0:
|
|
211
|
+
return b""
|
|
212
|
+
chunk = self._fileobj.read(size)
|
|
213
|
+
if not chunk:
|
|
214
|
+
raise SandboxDownloadError(
|
|
215
|
+
"sandbox archive ended without an end-of-archive marker"
|
|
216
|
+
)
|
|
217
|
+
return chunk
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def extract_tar(dest: str, fileobj: IO[bytes]) -> None:
|
|
221
|
+
"""Extract a downloaded archive under dest.
|
|
222
|
+
|
|
223
|
+
The archive comes from the (untrusted) sandbox, so every path is resolved
|
|
224
|
+
against dest before anything is created: an entry naming its way out is
|
|
225
|
+
refused, and so is one routed through a symlink that leaves dest. A symlink
|
|
226
|
+
pointing outside dest still gets created, matching what tar does and what
|
|
227
|
+
the trees people download contain, but nothing can be written through it.
|
|
228
|
+
"""
|
|
229
|
+
os.makedirs(dest, 0o750, exist_ok=True)
|
|
230
|
+
dest_real = os.path.realpath(dest)
|
|
231
|
+
|
|
232
|
+
# Streaming mode even though the spool file is seekable: seeking would break
|
|
233
|
+
# the sequential reads _TerminatedReader relies on. The cast is because a
|
|
234
|
+
# stream-mode archive only ever calls read() on its fileobj.
|
|
235
|
+
reader = cast("IO[bytes]", _TerminatedReader(fileobj))
|
|
236
|
+
# A malformed header or a bad checksum raises TarError, which says nothing
|
|
237
|
+
# about the sandbox to a caller catching RenderError.
|
|
238
|
+
try:
|
|
239
|
+
with tarfile.open(fileobj=reader, mode="r|") as archive:
|
|
240
|
+
for member in archive:
|
|
241
|
+
parts = _entry_parts(member.name)
|
|
242
|
+
if not parts:
|
|
243
|
+
continue
|
|
244
|
+
# Masking to 0o777 drops setuid/setgid, which an archive from
|
|
245
|
+
# the sandbox has no business setting locally; the or'd owner
|
|
246
|
+
# bits keep the tree writable enough to finish extracting into.
|
|
247
|
+
if member.isdir():
|
|
248
|
+
_make_dirs(dest, dest_real, parts, member.mode & 0o777 | 0o700)
|
|
249
|
+
elif member.isreg():
|
|
250
|
+
target = _make_parents(dest, dest_real, parts)
|
|
251
|
+
_write_file(target, member.mode & 0o777 | 0o600, archive, member)
|
|
252
|
+
elif member.issym():
|
|
253
|
+
target = _make_parents(dest, dest_real, parts)
|
|
254
|
+
_clear_target(target, member.name)
|
|
255
|
+
os.symlink(member.linkname, target)
|
|
256
|
+
elif member.islnk():
|
|
257
|
+
target = _make_parents(dest, dest_real, parts)
|
|
258
|
+
source = _link_source(dest, dest_real, member.linkname)
|
|
259
|
+
_clear_target(target, member.name)
|
|
260
|
+
os.link(source, target)
|
|
261
|
+
# Devices, fifos, and other special entries are skipped.
|
|
262
|
+
except tarfile.TarError as exc:
|
|
263
|
+
raise SandboxDownloadError(f"sandbox archive is malformed: {exc}") from exc
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _entry_parts(name: str) -> list[str]:
|
|
267
|
+
"""Split a tar entry name into path components under the destination.
|
|
268
|
+
|
|
269
|
+
Returns an empty list for the archive's own root entry, and raises for a
|
|
270
|
+
name that points anywhere but under the destination.
|
|
271
|
+
"""
|
|
272
|
+
clean = posixpath.normpath(name)
|
|
273
|
+
if clean in (".", "/", ""):
|
|
274
|
+
return []
|
|
275
|
+
escapes = (
|
|
276
|
+
posixpath.isabs(clean)
|
|
277
|
+
or bool(ntpath.splitdrive(clean)[0])
|
|
278
|
+
or clean == ".."
|
|
279
|
+
or clean.startswith("../")
|
|
280
|
+
)
|
|
281
|
+
if escapes:
|
|
282
|
+
raise SandboxDownloadError(f"archive entry {name!r} escapes the destination")
|
|
283
|
+
return clean.split("/")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _make_dirs(dest: str, dest_real: str, parts: list[str], mode: int) -> str:
|
|
287
|
+
"""Create each directory in parts under dest, checking as it descends.
|
|
288
|
+
|
|
289
|
+
Creating the tree one component at a time is what keeps a symlink planted by
|
|
290
|
+
an earlier entry from redirecting the rest of the extraction: a component
|
|
291
|
+
that already exists as a link out of dest fails the check before anything is
|
|
292
|
+
written through it.
|
|
293
|
+
"""
|
|
294
|
+
current = dest
|
|
295
|
+
for index, part in enumerate(parts):
|
|
296
|
+
current = os.path.join(current, part)
|
|
297
|
+
with contextlib.suppress(FileExistsError):
|
|
298
|
+
os.mkdir(current, mode if index == len(parts) - 1 else 0o750)
|
|
299
|
+
# Something that is not a directory sitting in the path is the archive
|
|
300
|
+
# and the destination disagreeing about what a name is. Say so, rather
|
|
301
|
+
# than letting the next write fail with a bare ENOTDIR.
|
|
302
|
+
if not os.path.isdir(current):
|
|
303
|
+
raise SandboxDownloadError(
|
|
304
|
+
f"archive entry {'/'.join(parts)!r} needs a directory at "
|
|
305
|
+
f"{current}, which is not one"
|
|
306
|
+
)
|
|
307
|
+
real = os.path.realpath(current)
|
|
308
|
+
if real != dest_real and not real.startswith(dest_real + os.sep):
|
|
309
|
+
raise SandboxDownloadError(
|
|
310
|
+
f"archive entry {'/'.join(parts)!r} escapes the destination"
|
|
311
|
+
)
|
|
312
|
+
return current
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _make_parents(dest: str, dest_real: str, parts: list[str]) -> str:
|
|
316
|
+
"""Create the directories leading to the last component and return its path."""
|
|
317
|
+
parent = _make_dirs(dest, dest_real, parts[:-1], 0o750)
|
|
318
|
+
return os.path.join(parent, parts[-1])
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _write_file(
|
|
322
|
+
target: str, mode: int, archive: tarfile.TarFile, member: tarfile.TarInfo
|
|
323
|
+
) -> None:
|
|
324
|
+
source = archive.extractfile(member)
|
|
325
|
+
if source is None:
|
|
326
|
+
return
|
|
327
|
+
# O_NOFOLLOW refuses a symlink sitting at target but opens a FIFO normally,
|
|
328
|
+
# and opening one blocks until a writer appears, which may be never. Clear
|
|
329
|
+
# anything that is not a regular file, as the symlink branch already does.
|
|
330
|
+
existing = _lstat(target)
|
|
331
|
+
if existing is not None and not stat.S_ISREG(existing.st_mode):
|
|
332
|
+
_clear_target(target, member.name)
|
|
333
|
+
flags = os.O_CREAT | os.O_WRONLY | os.O_TRUNC | os.O_NOFOLLOW
|
|
334
|
+
with os.fdopen(os.open(target, flags, mode), "wb") as out:
|
|
335
|
+
while True:
|
|
336
|
+
chunk = source.read(64 * 1024)
|
|
337
|
+
if not chunk:
|
|
338
|
+
break
|
|
339
|
+
out.write(chunk)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _link_source(dest: str, dest_real: str, linkname: str) -> str:
|
|
343
|
+
"""Resolve a hard link's target, which the archive names relative to itself."""
|
|
344
|
+
parts = _entry_parts(linkname)
|
|
345
|
+
if not parts:
|
|
346
|
+
raise SandboxDownloadError(f"archive hard link to {linkname!r} is not a file")
|
|
347
|
+
source = os.path.join(dest, *parts)
|
|
348
|
+
real = os.path.realpath(source)
|
|
349
|
+
if not real.startswith(dest_real + os.sep):
|
|
350
|
+
raise SandboxDownloadError(
|
|
351
|
+
f"archive hard link to {linkname!r} escapes the destination"
|
|
352
|
+
)
|
|
353
|
+
return source
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _lstat(path: str) -> os.stat_result | None:
|
|
357
|
+
"""lstat path, or None when nothing is there."""
|
|
358
|
+
try:
|
|
359
|
+
return os.lstat(path)
|
|
360
|
+
except FileNotFoundError:
|
|
361
|
+
return None
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _clear_target(target: str, name: str) -> None:
|
|
365
|
+
"""Remove whatever sits at target so an archive entry can take its place.
|
|
366
|
+
|
|
367
|
+
A directory is refused rather than emptied: a download quietly deleting a
|
|
368
|
+
local tree is a surprise nobody asks for. Every other failure surfaces,
|
|
369
|
+
since swallowing it here only produces a stranger error at the next call.
|
|
370
|
+
"""
|
|
371
|
+
existing = _lstat(target)
|
|
372
|
+
if existing is None:
|
|
373
|
+
return
|
|
374
|
+
if stat.S_ISDIR(existing.st_mode):
|
|
375
|
+
raise SandboxDownloadError(
|
|
376
|
+
f"archive entry {name!r} would replace the directory at {target}"
|
|
377
|
+
)
|
|
378
|
+
os.remove(target)
|
|
File without changes
|