From 0d86632ab387191f01754dc64743ee2c6ad19e1c Mon Sep 17 00:00:00 2001 From: Vedran Jukic Date: Sun, 12 Jul 2026 21:02:19 +0000 Subject: [PATCH] feat(volumes): add hotmount and blockmount volume types Sync clients with the API volume-types refactor: regenerate all API clients (TS, Go, Python, Python-async, Ruby, Java) from the updated openapi-specs/api.json, and add the hand-written SDK/CLI/example support. Spec additions: VolumeType, HotmountRegion, BlockmountConflict, CreateVolumeMountToken, VolumeMountTokenDto; region/size/type fields on Volume/CreateVolume/SandboxVolume/Region; and the /volumes/hotmount-regions, /volumes/blockmount-regions and /volumes/{volumeId}/mount-token endpoints. SDKs: Volume/VolumeType surface, Sandbox.mountVolume (hotmount, on-the-fly token bootstrap) and Sandbox.pullVolumes (blockmount explicit pull) across all languages; CLI volume create/list/info; new blockmount and hotmount TypeScript examples. --- api-client-go/.openapi-generator/FILES | 5 + api-client-go/api/openapi.yaml | 458 ++++++++- api-client-go/api_volumes.go | 370 +++++++ api-client-go/model_blockmount_conflict.go | 306 ++++++ api-client-go/model_create_volume.go | 80 ++ .../model_create_volume_mount_token.go | 161 +++ api-client-go/model_hotmount_region.go | 230 +++++ api-client-go/model_region.go | 32 +- api-client-go/model_sandbox_volume.go | 342 +++++++ api-client-go/model_volume_dto.go | 263 ++++- api-client-go/model_volume_mount_token_dto.go | 368 +++++++ api-client-go/model_volume_type.go | 117 +++ api-client-java/.openapi-generator/FILES | 10 + .../main/java/io/daytona/api/client/JSON.java | 4 + .../io/daytona/api/client/api/VolumesApi.java | 397 ++++++++ .../api/client/model/BlockmountConflict.java | 406 ++++++++ .../api/client/model/CreateVolume.java | 66 +- .../client/model/CreateVolumeMountToken.java | 341 +++++++ .../api/client/model/HotmountRegion.java | 348 +++++++ .../io/daytona/api/client/model/Region.java | 34 +- .../api/client/model/SandboxVolume.java | 264 ++++- .../daytona/api/client/model/VolumeDto.java | 197 +++- .../api/client/model/VolumeMountTokenDto.java | 476 +++++++++ .../daytona/api/client/model/VolumeType.java | 82 ++ .../api/client/api/VolumesApiTest.java | 42 + .../client/model/BlockmountConflictTest.java | 80 ++ .../model/CreateVolumeMountTokenTest.java | 48 + .../api/client/model/CreateVolumeTest.java | 17 + .../api/client/model/HotmountRegionTest.java | 64 ++ .../daytona/api/client/model/RegionTest.java | 8 + .../api/client/model/SandboxVolumeTest.java | 74 ++ .../api/client/model/VolumeDtoTest.java | 53 + .../client/model/VolumeMountTokenDtoTest.java | 97 ++ .../api/client/model/VolumeTypeTest.java | 32 + .../.openapi-generator/FILES | 5 + .../daytona_api_client_async/__init__.py | 15 + .../api/volumes_api.py | 962 ++++++++++++++++-- .../models/__init__.py | 10 + .../models/blockmount_conflict.py | 111 ++ .../models/create_volume.py | 13 +- .../models/create_volume_mount_token.py | 103 ++ .../models/hotmount_region.py | 107 ++ .../daytona_api_client_async/models/region.py | 8 +- .../models/sandbox_volume.py | 27 +- .../models/volume_dto.py | 52 +- .../models/volume_mount_token_dto.py | 120 +++ .../models/volume_type.py | 44 + api-client-python/.openapi-generator/FILES | 5 + .../daytona_api_client/__init__.py | 15 + .../daytona_api_client/api/volumes_api.py | 962 ++++++++++++++++-- .../daytona_api_client/models/__init__.py | 10 + .../models/blockmount_conflict.py | 111 ++ .../models/create_volume.py | 13 +- .../models/create_volume_mount_token.py | 103 ++ .../models/hotmount_region.py | 107 ++ .../daytona_api_client/models/region.py | 8 +- .../models/sandbox_volume.py | 27 +- .../daytona_api_client/models/volume_dto.py | 52 +- .../models/volume_mount_token_dto.py | 120 +++ .../daytona_api_client/models/volume_type.py | 44 + api-client-ruby/.openapi-generator/FILES | 5 + api-client-ruby/lib/daytona_api_client.rb | 5 + .../lib/daytona_api_client/api/volumes_api.rb | 187 ++++ .../models/blockmount_conflict.rb | 239 +++++ .../models/create_volume.rb | 50 +- .../models/create_volume_mount_token.rb | 184 ++++ .../models/hotmount_region.rb | 219 ++++ .../lib/daytona_api_client/models/region.rb | 37 +- .../models/sandbox_volume.rb | 120 ++- .../daytona_api_client/models/volume_dto.rb | 86 +- .../models/volume_mount_token_dto.rb | 311 ++++++ .../daytona_api_client/models/volume_type.rb | 42 + api-client/src/.openapi-generator/FILES | 5 + api-client/src/api/volumes-api.ts | 243 +++++ api-client/src/models/blockmount-conflict.ts | 39 + .../src/models/create-volume-mount-token.ts | 32 + api-client/src/models/create-volume.ts | 13 + api-client/src/models/hotmount-region.ts | 31 + api-client/src/models/index.ts | 5 + api-client/src/models/region.ts | 4 + api-client/src/models/sandbox-volume.ts | 41 + api-client/src/models/volume-dto.ts | 30 + .../src/models/volume-mount-token-dto.ts | 47 + api-client/src/models/volume-type.ts | 31 + cli/cmd/volume/create.go | 26 +- cli/views/volume/info.go | 10 + cli/views/volume/list.go | 12 +- examples/typescript/blockmount/index.ts | 162 +++ examples/typescript/hotmount/index.ts | 100 ++ openapi-specs/api.json | 395 ++++++- sdk-go/pkg/daytona/sandbox.go | 151 +++ sdk-go/pkg/daytona/volume.go | 115 ++- sdk-go/pkg/types/types.go | 86 +- .../src/main/java/io/daytona/sdk/Sandbox.java | 129 +++ .../java/io/daytona/sdk/VolumeService.java | 82 +- .../java/io/daytona/sdk/model/Volume.java | 64 ++ .../daytona/sdk/model/VolumePullResult.java | 155 +++ sdk-python/src/daytona/__init__.py | 10 +- sdk-python/src/daytona/_async/daytona.py | 3 + sdk-python/src/daytona/_async/sandbox.py | 88 ++ sdk-python/src/daytona/_async/volume.py | 95 +- sdk-python/src/daytona/_sync/daytona.py | 3 + sdk-python/src/daytona/_sync/sandbox.py | 88 ++ sdk-python/src/daytona/_sync/volume.py | 93 +- sdk-python/src/daytona/common/volume.py | 79 +- sdk-ruby/lib/daytona/daytona.rb | 3 +- sdk-ruby/lib/daytona/sandbox.rb | 104 +- sdk-ruby/lib/daytona/volume.rb | 8 + sdk-ruby/lib/daytona/volume_service.rb | 43 +- sdk-typescript/src/Daytona.ts | 6 +- sdk-typescript/src/Sandbox.ts | 118 +++ sdk-typescript/src/Volume.ts | 121 ++- sdk-typescript/src/index.ts | 9 + 113 files changed, 13142 insertions(+), 248 deletions(-) create mode 100644 api-client-go/model_blockmount_conflict.go create mode 100644 api-client-go/model_create_volume_mount_token.go create mode 100644 api-client-go/model_hotmount_region.go create mode 100644 api-client-go/model_volume_mount_token_dto.go create mode 100644 api-client-go/model_volume_type.go create mode 100644 api-client-java/src/main/java/io/daytona/api/client/model/BlockmountConflict.java create mode 100644 api-client-java/src/main/java/io/daytona/api/client/model/CreateVolumeMountToken.java create mode 100644 api-client-java/src/main/java/io/daytona/api/client/model/HotmountRegion.java create mode 100644 api-client-java/src/main/java/io/daytona/api/client/model/VolumeMountTokenDto.java create mode 100644 api-client-java/src/main/java/io/daytona/api/client/model/VolumeType.java create mode 100644 api-client-java/src/test/java/io/daytona/api/client/model/BlockmountConflictTest.java create mode 100644 api-client-java/src/test/java/io/daytona/api/client/model/CreateVolumeMountTokenTest.java create mode 100644 api-client-java/src/test/java/io/daytona/api/client/model/HotmountRegionTest.java create mode 100644 api-client-java/src/test/java/io/daytona/api/client/model/VolumeMountTokenDtoTest.java create mode 100644 api-client-java/src/test/java/io/daytona/api/client/model/VolumeTypeTest.java create mode 100644 api-client-python-async/daytona_api_client_async/models/blockmount_conflict.py create mode 100644 api-client-python-async/daytona_api_client_async/models/create_volume_mount_token.py create mode 100644 api-client-python-async/daytona_api_client_async/models/hotmount_region.py create mode 100644 api-client-python-async/daytona_api_client_async/models/volume_mount_token_dto.py create mode 100644 api-client-python-async/daytona_api_client_async/models/volume_type.py create mode 100644 api-client-python/daytona_api_client/models/blockmount_conflict.py create mode 100644 api-client-python/daytona_api_client/models/create_volume_mount_token.py create mode 100644 api-client-python/daytona_api_client/models/hotmount_region.py create mode 100644 api-client-python/daytona_api_client/models/volume_mount_token_dto.py create mode 100644 api-client-python/daytona_api_client/models/volume_type.py create mode 100644 api-client-ruby/lib/daytona_api_client/models/blockmount_conflict.rb create mode 100644 api-client-ruby/lib/daytona_api_client/models/create_volume_mount_token.rb create mode 100644 api-client-ruby/lib/daytona_api_client/models/hotmount_region.rb create mode 100644 api-client-ruby/lib/daytona_api_client/models/volume_mount_token_dto.rb create mode 100644 api-client-ruby/lib/daytona_api_client/models/volume_type.rb create mode 100644 api-client/src/models/blockmount-conflict.ts create mode 100644 api-client/src/models/create-volume-mount-token.ts create mode 100644 api-client/src/models/hotmount-region.ts create mode 100644 api-client/src/models/volume-mount-token-dto.ts create mode 100644 api-client/src/models/volume-type.ts create mode 100644 examples/typescript/blockmount/index.ts create mode 100644 examples/typescript/hotmount/index.ts create mode 100644 sdk-java/src/main/java/io/daytona/sdk/model/VolumePullResult.java diff --git a/api-client-go/.openapi-generator/FILES b/api-client-go/.openapi-generator/FILES index e3a48463b..43622df49 100644 --- a/api-client-go/.openapi-generator/FILES +++ b/api-client-go/.openapi-generator/FILES @@ -32,6 +32,7 @@ model_api_key_list.go model_api_key_response.go model_audit_log.go model_available_sandbox_class.go +model_blockmount_conflict.go model_build_info.go model_command.go model_completion_context.go @@ -61,6 +62,7 @@ model_create_session_request.go model_create_snapshot.go model_create_user.go model_create_volume.go +model_create_volume_mount_token.go model_date_filter.go model_daytona_configuration.go model_display_info_response.go @@ -85,6 +87,7 @@ model_gpu_type.go model_health_controller_check_200_response.go model_health_controller_check_200_response_info_value.go model_health_controller_check_503_response.go +model_hotmount_region.go model_int_filter.go model_job.go model_job_status.go @@ -213,7 +216,9 @@ model_user.go model_user_home_dir_response.go model_user_public_key.go model_volume_dto.go +model_volume_mount_token_dto.go model_volume_state.go +model_volume_type.go model_webhook_app_portal_access.go model_webhook_event.go model_webhook_initialization_status.go diff --git a/api-client-go/api/openapi.yaml b/api-client-go/api/openapi.yaml index 8985a7801..031e7e590 100644 --- a/api-client-go/api/openapi.yaml +++ b/api-client-go/api/openapi.yaml @@ -7583,6 +7583,66 @@ paths: summary: Create a new volume tags: - volumes + /volumes/hotmount-regions: + get: + operationId: listHotmountRegions + parameters: + - description: Use with JWT to specify the organization ID + explode: false + in: header + name: X-Daytona-Organization-ID + required: false + schema: + type: string + style: simple + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/HotmountRegion" + type: array + description: List of active hotmount regions selectable at volume creation + security: + - bearer: [] + - oauth2: + - openid + - profile + - email + summary: List available hotmount regions + tags: + - volumes + /volumes/blockmount-regions: + get: + operationId: listBlockmountRegions + parameters: + - description: Use with JWT to specify the organization ID + explode: false + in: header + name: X-Daytona-Organization-ID + required: false + schema: + type: string + style: simple + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Region" + type: array + description: List of regions that support blockmount volumes + security: + - bearer: [] + - oauth2: + - openid + - profile + - email + summary: List regions where blockmount volumes can be created + tags: + - volumes /volumes/{volumeId}: delete: operationId: deleteVolume @@ -7652,6 +7712,48 @@ paths: summary: Get volume details tags: - volumes + /volumes/{volumeId}/mount-token: + post: + operationId: createVolumeMountToken + parameters: + - description: Use with JWT to specify the organization ID + explode: false + in: header + name: X-Daytona-Organization-ID + required: false + schema: + type: string + style: simple + - description: ID of the volume + explode: false + in: path + name: volumeId + required: true + schema: + type: string + style: simple + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateVolumeMountToken" + required: false + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/VolumeMountTokenDto" + description: The mount token has been successfully created. + security: + - bearer: [] + - oauth2: + - openid + - profile + - email + summary: Create a mount token for a hotmount volume + tags: + - volumes /volumes/by-name/{name}: get: operationId: getVolumeByName @@ -11285,6 +11387,7 @@ components: snapshotManagerUrl: http://snapshot-manager.example.com regionType: shared proxyUrl: https://proxy.example.com + blockmountEnabled: false name: us-east-1 sshGatewayUrl: http://ssh-gateway.example.com id: "123456789012" @@ -11331,7 +11434,12 @@ components: example: http://snapshot-manager.example.com nullable: true type: string + blockmountEnabled: + description: Whether blockmount volumes are supported in this region + example: false + type: boolean required: + - blockmountEnabled - createdAt - id - name @@ -11827,11 +11935,29 @@ components: - items - nextCursor type: object + VolumeType: + description: The type of the volume. Resolved from the referenced volume on + sandbox create; the runner uses it to choose how to mount the volume. Absent + values are treated as legacy. + enum: + - legacy + - hotmount + - blockmount + type: string SandboxVolume: example: + organizationId: org_123 + volumeType: legacy mountPath: /data + s3Endpoint: s3Endpoint + s3Region: s3Region + sizeInGb: 10 volumeId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 subpath: users/alice + s3PathStyle: true + region: us + s3Prefix: s3Prefix + s3Bucket: s3Bucket properties: volumeId: description: The ID or name of the volume. Resolved to the volume ID on @@ -11848,6 +11974,53 @@ components: \ is mounted." example: users/alice type: string + volumeType: + allOf: + - $ref: "#/components/schemas/VolumeType" + description: The type of the volume. Resolved from the referenced volume + on sandbox create; the runner uses it to choose how to mount the volume. + Absent values are treated as legacy. + example: legacy + organizationId: + description: The organization that owns the volume. Forwarded to the runner + to isolate the S3 prefix. Set only for blockmount volumes. + example: org_123 + type: string + sizeInGb: + description: "The logical size of the volume in gigabytes, used by the runner\ + \ as the per-sandbox scratch quota. Set only for blockmount volumes." + example: 10 + type: number + region: + description: The region the blockmount volume's data lives in. Forwarded + to the runner so it can fetch the region's store credentials over its + authenticated channel. Set only for blockmount volumes. + example: us + type: string + s3Endpoint: + description: "The S3 endpoint of the CAS store the blockmount volume's data\ + \ lives in, resolved from the volume's region. Forwarded to the runner\ + \ so cross-region attaches reach the right bucket. Omitted when the volume's\ + \ region has no store configured (runner falls back to its env store).\ + \ Credentials are never sent here — the runner fetches them by region.\ + \ Set only for blockmount volumes." + type: string + s3Region: + description: The S3 region of the CAS store the blockmount volume's data + lives in. Set only for blockmount volumes. + type: string + s3Bucket: + description: The S3 bucket of the CAS store the blockmount volume's data + lives in. Set only for blockmount volumes. + type: string + s3Prefix: + description: The S3 key prefix of the CAS store the blockmount volume's + data lives in. Set only for blockmount volumes. + type: string + s3PathStyle: + description: Whether the CAS store uses path-style S3 addressing. Set only + for blockmount volumes. + type: boolean required: - mountPath - volumeId @@ -11912,12 +12085,30 @@ components: desiredState: destroyed networkAllowList: "192.168.1.0/16,10.0.0.0/24" volumes: - - mountPath: /data + - organizationId: org_123 + volumeType: legacy + mountPath: /data + s3Endpoint: s3Endpoint + s3Region: s3Region + sizeInGb: 10 volumeId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 subpath: users/alice - - mountPath: /data + s3PathStyle: true + region: us + s3Prefix: s3Prefix + s3Bucket: s3Bucket + - organizationId: org_123 + volumeType: legacy + mountPath: /data + s3Endpoint: s3Endpoint + s3Region: s3Region + sizeInGb: 10 volumeId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 subpath: users/alice + s3PathStyle: true + region: us + s3Prefix: s3Prefix + s3Bucket: s3Bucket cpu: 2 recoverable: true env: @@ -12152,12 +12343,30 @@ components: desiredState: destroyed networkAllowList: "192.168.1.0/16,10.0.0.0/24" volumes: - - mountPath: /data + - organizationId: org_123 + volumeType: legacy + mountPath: /data + s3Endpoint: s3Endpoint + s3Region: s3Region + sizeInGb: 10 volumeId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 subpath: users/alice - - mountPath: /data + s3PathStyle: true + region: us + s3Prefix: s3Prefix + s3Bucket: s3Bucket + - organizationId: org_123 + volumeType: legacy + mountPath: /data + s3Endpoint: s3Endpoint + s3Region: s3Region + sizeInGb: 10 volumeId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 subpath: users/alice + s3PathStyle: true + region: us + s3Prefix: s3Prefix + s3Bucket: s3Bucket cpu: 2 recoverable: true env: @@ -12196,12 +12405,30 @@ components: desiredState: destroyed networkAllowList: "192.168.1.0/16,10.0.0.0/24" volumes: - - mountPath: /data + - organizationId: org_123 + volumeType: legacy + mountPath: /data + s3Endpoint: s3Endpoint + s3Region: s3Region + sizeInGb: 10 volumeId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 subpath: users/alice - - mountPath: /data + s3PathStyle: true + region: us + s3Prefix: s3Prefix + s3Bucket: s3Bucket + - organizationId: org_123 + volumeType: legacy + mountPath: /data + s3Endpoint: s3Endpoint + s3Region: s3Region + sizeInGb: 10 volumeId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 subpath: users/alice + s3PathStyle: true + region: us + s3Prefix: s3Prefix + s3Bucket: s3Bucket cpu: 2 recoverable: true env: @@ -12263,12 +12490,30 @@ components: buildInfo: "" networkAllowList: "192.168.1.0/16,10.0.0.0/24" volumes: - - mountPath: /data + - organizationId: org_123 + volumeType: legacy + mountPath: /data + s3Endpoint: s3Endpoint + s3Region: s3Region + sizeInGb: 10 volumeId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 subpath: users/alice - - mountPath: /data + s3PathStyle: true + region: us + s3Prefix: s3Prefix + s3Bucket: s3Bucket + - organizationId: org_123 + volumeType: legacy + mountPath: /data + s3Endpoint: s3Endpoint + s3Region: s3Region + sizeInGb: 10 volumeId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 subpath: users/alice + s3PathStyle: true + region: us + s3Prefix: s3Prefix + s3Bucket: s3Bucket cpu: 2 env: NODE_ENV: production @@ -15112,6 +15357,41 @@ components: required: - enabled type: object + BlockmountConflict: + example: + path: path + reason: mtime + winner: ours + oursSha: oursSha + theirsSha: theirsSha + properties: + path: + description: The path (relative to the volume root) that was concurrently + modified + type: string + winner: + description: "Which side won the merge: \"ours\" (the committing writer)\ + \ or \"theirs\" (the state already in latest)" + example: ours + type: string + reason: + description: "Why the winner won: \"mtime\" (newer change), \"tie\" (equal\ + \ mtimes, committer won), \"modify-over-delete\", or \"type\"" + example: mtime + type: string + oursSha: + description: "Content hash of the committing writer’s version, when both\ + \ sides were files" + type: string + theirsSha: + description: "Content hash of the concurrent version found in latest, when\ + \ both sides were files" + type: string + required: + - path + - reason + - winner + type: object VolumeState: description: Volume state enum: @@ -15125,13 +15405,29 @@ components: type: string VolumeDto: example: + shared: false + type: legacy + lastManifestId: lastManifestId organizationId: 123e4567-e89b-12d3-a456-426614174000 createdAt: 2023-01-01T00:00:00.000Z lastUsedAt: 2023-01-01T00:00:00.000Z + sizeInGb: 10 errorReason: Error processing volume name: my-volume + conflicts: + - path: path + reason: mtime + winner: ours + oursSha: oursSha + theirsSha: theirsSha + - path: path + reason: mtime + winner: ours + oursSha: oursSha + theirsSha: theirsSha id: vol-12345678 state: ready + region: us updatedAt: 2023-01-01T00:00:00.000Z properties: id: @@ -15146,6 +15442,46 @@ components: description: Organization ID example: 123e4567-e89b-12d3-a456-426614174000 type: string + type: + allOf: + - $ref: "#/components/schemas/VolumeType" + description: Volume type + example: legacy + sizeInGb: + description: The per-sandbox scratch quota in GB. Set only for blockmount + volumes. + example: 10 + nullable: true + type: number + region: + description: "The region the volume's data lives in. For blockmount volumes\ + \ this selects the region-local CAS store (a performance/placement knob\ + \ — sandboxes in any region can attach it, colocation is just faster).\ + \ For hotmount volumes this is the hotmount deployment region. Set for\ + \ blockmount and hotmount volumes." + example: us + nullable: true + type: string + shared: + description: "The hotmount sharing mode (false = single-writer write-back,\ + \ true = multi-writer synchronous). Set only for hotmount volumes." + example: false + nullable: true + type: boolean + lastManifestId: + description: "The id of the most recent committed manifest, read-through\ + \ from the reconciliation store. Set only for blockmount volumes that\ + \ have been committed at least once." + nullable: true + type: string + conflicts: + description: Conflicts recorded on the latest manifest — concurrent same-path + modifications the store resolved (last-change-wins). Read-through from + the store. Set only for blockmount volumes. + items: + $ref: "#/components/schemas/BlockmountConflict" + nullable: true + type: array state: allOf: - $ref: "#/components/schemas/VolumeState" @@ -15176,17 +15512,123 @@ components: - name - organizationId - state + - type - updatedAt type: object CreateVolume: example: name: name + type: legacy + region: us properties: name: type: string + type: + allOf: + - $ref: "#/components/schemas/VolumeType" + default: legacy + description: The type of the volume. Defaults to legacy. + example: legacy + region: + description: "The region to create the volume in. For blockmount volumes\ + \ it selects the region-local CAS store the volume's data lives in — a\ + \ performance/placement knob, not an attach restriction, so sandboxes\ + \ in any region can attach the volume (colocation is just faster). Optional\ + \ for blockmount: when omitted it defaults to the organization's default\ + \ region (or the first region that offers blockmount). For hotmount volumes\ + \ it selects the hotmount deployment region and defaults to an active\ + \ region. Not allowed for legacy volumes. The volume's region is fixed\ + \ for its lifetime." + example: us + type: string required: - name type: object + HotmountRegion: + example: + geo: us + label: "US (OCI, Ashburn)" + region: oci-us + properties: + region: + description: Stable region id + example: oci-us + type: string + label: + description: User-facing region name + example: "US (OCI, Ashburn)" + type: string + geo: + description: Geo hint used for default region selection + example: us + type: string + required: + - geo + - label + - region + type: object + CreateVolumeMountToken: + example: + mode: rw + properties: + mode: + default: rw + description: The access mode for the mount. Defaults to rw. + enum: + - rw + - ro + example: rw + type: string + type: object + VolumeMountTokenDto: + example: + gatewayHttp: https://hotmount-gw-oci-us.trydaytona.com:443 + gatewayGrpc: hotmount-gw-oci-us.trydaytona.com:18443 + region: oci-us + binariesUrl: https://hotmount-binaries-446539620565.s3.amazonaws.com + version: v0.4.65 + expiresAt: 2023-01-01T00:00:00.000Z + token: BASE64_MACAROON + properties: + token: + description: The short-lived macaroon token the in-sandbox agent uses to + mount the volume + example: BASE64_MACAROON + type: string + expiresAt: + description: The token expiration timestamp + example: 2023-01-01T00:00:00.000Z + type: string + region: + description: The hotmount region the volume lives in + example: oci-us + type: string + gatewayGrpc: + description: The gateway gRPC endpoint (SEAWEED_GATEWAY_GRPC) + example: hotmount-gw-oci-us.trydaytona.com:18443 + type: string + gatewayHttp: + description: The gateway HTTPS endpoint (SEAWEED_GATEWAY_HTTP) + example: https://hotmount-gw-oci-us.trydaytona.com:443 + type: string + binariesUrl: + description: The binaries bucket base URL used to bootstrap the mount (SEAWEED_BINARIES_URL) + example: https://hotmount-binaries-446539620565.s3.amazonaws.com + type: string + version: + description: "The pinned client binary version to use (SEAWEED_VERSION),\ + \ when the region pins one" + example: v0.4.65 + nullable: true + type: string + required: + - binariesUrl + - expiresAt + - gatewayGrpc + - gatewayHttp + - region + - token + type: object JobStatus: enum: - PENDING diff --git a/api-client-go/api_volumes.go b/api-client-go/api_volumes.go index e2cd1401f..4fc7e50d5 100644 --- a/api-client-go/api_volumes.go +++ b/api-client-go/api_volumes.go @@ -35,6 +35,19 @@ type VolumesAPI interface { // @return VolumeDto CreateVolumeExecute(r VolumesAPICreateVolumeRequest) (*VolumeDto, *http.Response, error) + /* + CreateVolumeMountToken Create a mount token for a hotmount volume + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @param volumeId ID of the volume + @return VolumesAPICreateVolumeMountTokenRequest + */ + CreateVolumeMountToken(ctx context.Context, volumeId string) VolumesAPICreateVolumeMountTokenRequest + + // CreateVolumeMountTokenExecute executes the request + // @return VolumeMountTokenDto + CreateVolumeMountTokenExecute(r VolumesAPICreateVolumeMountTokenRequest) (*VolumeMountTokenDto, *http.Response, error) + /* DeleteVolume Delete volume @@ -73,6 +86,30 @@ type VolumesAPI interface { // @return VolumeDto GetVolumeByNameExecute(r VolumesAPIGetVolumeByNameRequest) (*VolumeDto, *http.Response, error) + /* + ListBlockmountRegions List regions where blockmount volumes can be created + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @return VolumesAPIListBlockmountRegionsRequest + */ + ListBlockmountRegions(ctx context.Context) VolumesAPIListBlockmountRegionsRequest + + // ListBlockmountRegionsExecute executes the request + // @return []Region + ListBlockmountRegionsExecute(r VolumesAPIListBlockmountRegionsRequest) ([]Region, *http.Response, error) + + /* + ListHotmountRegions List available hotmount regions + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @return VolumesAPIListHotmountRegionsRequest + */ + ListHotmountRegions(ctx context.Context) VolumesAPIListHotmountRegionsRequest + + // ListHotmountRegionsExecute executes the request + // @return []HotmountRegion + ListHotmountRegionsExecute(r VolumesAPIListHotmountRegionsRequest) ([]HotmountRegion, *http.Response, error) + /* ListVolumes List all volumes @@ -207,6 +244,125 @@ func (a *VolumesAPIService) CreateVolumeExecute(r VolumesAPICreateVolumeRequest) return localVarReturnValue, localVarHTTPResponse, nil } +type VolumesAPICreateVolumeMountTokenRequest struct { + ctx context.Context + ApiService VolumesAPI + volumeId string + xDaytonaOrganizationID *string + createVolumeMountToken *CreateVolumeMountToken +} + +// Use with JWT to specify the organization ID +func (r VolumesAPICreateVolumeMountTokenRequest) XDaytonaOrganizationID(xDaytonaOrganizationID string) VolumesAPICreateVolumeMountTokenRequest { + r.xDaytonaOrganizationID = &xDaytonaOrganizationID + return r +} + +func (r VolumesAPICreateVolumeMountTokenRequest) CreateVolumeMountToken(createVolumeMountToken CreateVolumeMountToken) VolumesAPICreateVolumeMountTokenRequest { + r.createVolumeMountToken = &createVolumeMountToken + return r +} + +func (r VolumesAPICreateVolumeMountTokenRequest) Execute() (*VolumeMountTokenDto, *http.Response, error) { + return r.ApiService.CreateVolumeMountTokenExecute(r) +} + +/* +CreateVolumeMountToken Create a mount token for a hotmount volume + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @param volumeId ID of the volume + @return VolumesAPICreateVolumeMountTokenRequest +*/ +func (a *VolumesAPIService) CreateVolumeMountToken(ctx context.Context, volumeId string) VolumesAPICreateVolumeMountTokenRequest { + return VolumesAPICreateVolumeMountTokenRequest{ + ApiService: a, + ctx: ctx, + volumeId: volumeId, + } +} + +// Execute executes the request +// @return VolumeMountTokenDto +func (a *VolumesAPIService) CreateVolumeMountTokenExecute(r VolumesAPICreateVolumeMountTokenRequest) (*VolumeMountTokenDto, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodPost + localVarPostBody interface{} + formFiles []formFile + localVarReturnValue *VolumeMountTokenDto + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "VolumesAPIService.CreateVolumeMountToken") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/volumes/{volumeId}/mount-token" + localVarPath = strings.Replace(localVarPath, "{"+"volumeId"+"}", url.PathEscape(parameterValueToString(r.volumeId, "volumeId")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{"application/json"} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + if r.xDaytonaOrganizationID != nil { + parameterAddToHeaderOrQuery(localVarHeaderParams, "X-Daytona-Organization-ID", r.xDaytonaOrganizationID, "simple", "") + } + // body params + localVarPostBody = r.createVolumeMountToken + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + type VolumesAPIDeleteVolumeRequest struct { ctx context.Context ApiService VolumesAPI @@ -529,6 +685,220 @@ func (a *VolumesAPIService) GetVolumeByNameExecute(r VolumesAPIGetVolumeByNameRe return localVarReturnValue, localVarHTTPResponse, nil } +type VolumesAPIListBlockmountRegionsRequest struct { + ctx context.Context + ApiService VolumesAPI + xDaytonaOrganizationID *string +} + +// Use with JWT to specify the organization ID +func (r VolumesAPIListBlockmountRegionsRequest) XDaytonaOrganizationID(xDaytonaOrganizationID string) VolumesAPIListBlockmountRegionsRequest { + r.xDaytonaOrganizationID = &xDaytonaOrganizationID + return r +} + +func (r VolumesAPIListBlockmountRegionsRequest) Execute() ([]Region, *http.Response, error) { + return r.ApiService.ListBlockmountRegionsExecute(r) +} + +/* +ListBlockmountRegions List regions where blockmount volumes can be created + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @return VolumesAPIListBlockmountRegionsRequest +*/ +func (a *VolumesAPIService) ListBlockmountRegions(ctx context.Context) VolumesAPIListBlockmountRegionsRequest { + return VolumesAPIListBlockmountRegionsRequest{ + ApiService: a, + ctx: ctx, + } +} + +// Execute executes the request +// @return []Region +func (a *VolumesAPIService) ListBlockmountRegionsExecute(r VolumesAPIListBlockmountRegionsRequest) ([]Region, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + formFiles []formFile + localVarReturnValue []Region + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "VolumesAPIService.ListBlockmountRegions") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/volumes/blockmount-regions" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + if r.xDaytonaOrganizationID != nil { + parameterAddToHeaderOrQuery(localVarHeaderParams, "X-Daytona-Organization-ID", r.xDaytonaOrganizationID, "simple", "") + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + +type VolumesAPIListHotmountRegionsRequest struct { + ctx context.Context + ApiService VolumesAPI + xDaytonaOrganizationID *string +} + +// Use with JWT to specify the organization ID +func (r VolumesAPIListHotmountRegionsRequest) XDaytonaOrganizationID(xDaytonaOrganizationID string) VolumesAPIListHotmountRegionsRequest { + r.xDaytonaOrganizationID = &xDaytonaOrganizationID + return r +} + +func (r VolumesAPIListHotmountRegionsRequest) Execute() ([]HotmountRegion, *http.Response, error) { + return r.ApiService.ListHotmountRegionsExecute(r) +} + +/* +ListHotmountRegions List available hotmount regions + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @return VolumesAPIListHotmountRegionsRequest +*/ +func (a *VolumesAPIService) ListHotmountRegions(ctx context.Context) VolumesAPIListHotmountRegionsRequest { + return VolumesAPIListHotmountRegionsRequest{ + ApiService: a, + ctx: ctx, + } +} + +// Execute executes the request +// @return []HotmountRegion +func (a *VolumesAPIService) ListHotmountRegionsExecute(r VolumesAPIListHotmountRegionsRequest) ([]HotmountRegion, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + formFiles []formFile + localVarReturnValue []HotmountRegion + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "VolumesAPIService.ListHotmountRegions") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/volumes/hotmount-regions" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + if r.xDaytonaOrganizationID != nil { + parameterAddToHeaderOrQuery(localVarHeaderParams, "X-Daytona-Organization-ID", r.xDaytonaOrganizationID, "simple", "") + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + type VolumesAPIListVolumesRequest struct { ctx context.Context ApiService VolumesAPI diff --git a/api-client-go/model_blockmount_conflict.go b/api-client-go/model_blockmount_conflict.go new file mode 100644 index 000000000..681580abb --- /dev/null +++ b/api-client-go/model_blockmount_conflict.go @@ -0,0 +1,306 @@ +/* +Daytona + +Daytona AI platform API Docs + +API version: 1.0 +Contact: support@daytona.com +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package apiclient + +import ( + "encoding/json" + "fmt" +) + +// checks if the BlockmountConflict type satisfies the MappedNullable interface at compile time +var _ MappedNullable = &BlockmountConflict{} + +// BlockmountConflict struct for BlockmountConflict +type BlockmountConflict struct { + // The path (relative to the volume root) that was concurrently modified + Path string `json:"path"` + // Which side won the merge: \"ours\" (the committing writer) or \"theirs\" (the state already in latest) + Winner string `json:"winner"` + // Why the winner won: \"mtime\" (newer change), \"tie\" (equal mtimes, committer won), \"modify-over-delete\", or \"type\" + Reason string `json:"reason"` + // Content hash of the committing writer’s version, when both sides were files + OursSha *string `json:"oursSha,omitempty"` + // Content hash of the concurrent version found in latest, when both sides were files + TheirsSha *string `json:"theirsSha,omitempty"` + AdditionalProperties map[string]interface{} +} + +type _BlockmountConflict BlockmountConflict + +// NewBlockmountConflict instantiates a new BlockmountConflict object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewBlockmountConflict(path string, winner string, reason string) *BlockmountConflict { + this := BlockmountConflict{} + this.Path = path + this.Winner = winner + this.Reason = reason + return &this +} + +// NewBlockmountConflictWithDefaults instantiates a new BlockmountConflict object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewBlockmountConflictWithDefaults() *BlockmountConflict { + this := BlockmountConflict{} + return &this +} + +// GetPath returns the Path field value +func (o *BlockmountConflict) GetPath() string { + if o == nil { + var ret string + return ret + } + + return o.Path +} + +// GetPathOk returns a tuple with the Path field value +// and a boolean to check if the value has been set. +func (o *BlockmountConflict) GetPathOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Path, true +} + +// SetPath sets field value +func (o *BlockmountConflict) SetPath(v string) { + o.Path = v +} + +// GetWinner returns the Winner field value +func (o *BlockmountConflict) GetWinner() string { + if o == nil { + var ret string + return ret + } + + return o.Winner +} + +// GetWinnerOk returns a tuple with the Winner field value +// and a boolean to check if the value has been set. +func (o *BlockmountConflict) GetWinnerOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Winner, true +} + +// SetWinner sets field value +func (o *BlockmountConflict) SetWinner(v string) { + o.Winner = v +} + +// GetReason returns the Reason field value +func (o *BlockmountConflict) GetReason() string { + if o == nil { + var ret string + return ret + } + + return o.Reason +} + +// GetReasonOk returns a tuple with the Reason field value +// and a boolean to check if the value has been set. +func (o *BlockmountConflict) GetReasonOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Reason, true +} + +// SetReason sets field value +func (o *BlockmountConflict) SetReason(v string) { + o.Reason = v +} + +// GetOursSha returns the OursSha field value if set, zero value otherwise. +func (o *BlockmountConflict) GetOursSha() string { + if o == nil || IsNil(o.OursSha) { + var ret string + return ret + } + return *o.OursSha +} + +// GetOursShaOk returns a tuple with the OursSha field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *BlockmountConflict) GetOursShaOk() (*string, bool) { + if o == nil || IsNil(o.OursSha) { + return nil, false + } + return o.OursSha, true +} + +// HasOursSha returns a boolean if a field has been set. +func (o *BlockmountConflict) HasOursSha() bool { + if o != nil && !IsNil(o.OursSha) { + return true + } + + return false +} + +// SetOursSha gets a reference to the given string and assigns it to the OursSha field. +func (o *BlockmountConflict) SetOursSha(v string) { + o.OursSha = &v +} + +// GetTheirsSha returns the TheirsSha field value if set, zero value otherwise. +func (o *BlockmountConflict) GetTheirsSha() string { + if o == nil || IsNil(o.TheirsSha) { + var ret string + return ret + } + return *o.TheirsSha +} + +// GetTheirsShaOk returns a tuple with the TheirsSha field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *BlockmountConflict) GetTheirsShaOk() (*string, bool) { + if o == nil || IsNil(o.TheirsSha) { + return nil, false + } + return o.TheirsSha, true +} + +// HasTheirsSha returns a boolean if a field has been set. +func (o *BlockmountConflict) HasTheirsSha() bool { + if o != nil && !IsNil(o.TheirsSha) { + return true + } + + return false +} + +// SetTheirsSha gets a reference to the given string and assigns it to the TheirsSha field. +func (o *BlockmountConflict) SetTheirsSha(v string) { + o.TheirsSha = &v +} + +func (o BlockmountConflict) MarshalJSON() ([]byte, error) { + toSerialize,err := o.ToMap() + if err != nil { + return []byte{}, err + } + return json.Marshal(toSerialize) +} + +func (o BlockmountConflict) ToMap() (map[string]interface{}, error) { + toSerialize := map[string]interface{}{} + toSerialize["path"] = o.Path + toSerialize["winner"] = o.Winner + toSerialize["reason"] = o.Reason + if !IsNil(o.OursSha) { + toSerialize["oursSha"] = o.OursSha + } + if !IsNil(o.TheirsSha) { + toSerialize["theirsSha"] = o.TheirsSha + } + + for key, value := range o.AdditionalProperties { + toSerialize[key] = value + } + + return toSerialize, nil +} + +func (o *BlockmountConflict) UnmarshalJSON(data []byte) (err error) { + // This validates that all required properties are included in the JSON object + // by unmarshalling the object into a generic map with string keys and checking + // that every required field exists as a key in the generic map. + requiredProperties := []string{ + "path", + "winner", + "reason", + } + + allProperties := make(map[string]interface{}) + + err = json.Unmarshal(data, &allProperties) + + if err != nil { + return err; + } + + for _, requiredProperty := range(requiredProperties) { + if _, exists := allProperties[requiredProperty]; !exists { + return fmt.Errorf("no value given for required property %v", requiredProperty) + } + } + + varBlockmountConflict := _BlockmountConflict{} + + err = json.Unmarshal(data, &varBlockmountConflict) + + if err != nil { + return err + } + + *o = BlockmountConflict(varBlockmountConflict) + + additionalProperties := make(map[string]interface{}) + + if err = json.Unmarshal(data, &additionalProperties); err == nil { + delete(additionalProperties, "path") + delete(additionalProperties, "winner") + delete(additionalProperties, "reason") + delete(additionalProperties, "oursSha") + delete(additionalProperties, "theirsSha") + o.AdditionalProperties = additionalProperties + } + + return err +} + +type NullableBlockmountConflict struct { + value *BlockmountConflict + isSet bool +} + +func (v NullableBlockmountConflict) Get() *BlockmountConflict { + return v.value +} + +func (v *NullableBlockmountConflict) Set(val *BlockmountConflict) { + v.value = val + v.isSet = true +} + +func (v NullableBlockmountConflict) IsSet() bool { + return v.isSet +} + +func (v *NullableBlockmountConflict) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableBlockmountConflict(val *BlockmountConflict) *NullableBlockmountConflict { + return &NullableBlockmountConflict{value: val, isSet: true} +} + +func (v NullableBlockmountConflict) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableBlockmountConflict) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} + + diff --git a/api-client-go/model_create_volume.go b/api-client-go/model_create_volume.go index 7588995ad..c4722060e 100644 --- a/api-client-go/model_create_volume.go +++ b/api-client-go/model_create_volume.go @@ -22,6 +22,10 @@ var _ MappedNullable = &CreateVolume{} // CreateVolume struct for CreateVolume type CreateVolume struct { Name string `json:"name"` + // The type of the volume. Defaults to legacy. + Type *VolumeType `json:"type,omitempty"` + // The region to create the volume in. For blockmount volumes it selects the region-local CAS store the volume's data lives in — a performance/placement knob, not an attach restriction, so sandboxes in any region can attach the volume (colocation is just faster). Optional for blockmount: when omitted it defaults to the organization's default region (or the first region that offers blockmount). For hotmount volumes it selects the hotmount deployment region and defaults to an active region. Not allowed for legacy volumes. The volume's region is fixed for its lifetime. + Region *string `json:"region,omitempty"` AdditionalProperties map[string]interface{} } @@ -34,6 +38,8 @@ type _CreateVolume CreateVolume func NewCreateVolume(name string) *CreateVolume { this := CreateVolume{} this.Name = name + var type_ VolumeType = VOLUMETYPE_LEGACY + this.Type = &type_ return &this } @@ -42,6 +48,8 @@ func NewCreateVolume(name string) *CreateVolume { // but it doesn't guarantee that properties required by API are set func NewCreateVolumeWithDefaults() *CreateVolume { this := CreateVolume{} + var type_ VolumeType = VOLUMETYPE_LEGACY + this.Type = &type_ return &this } @@ -69,6 +77,70 @@ func (o *CreateVolume) SetName(v string) { o.Name = v } +// GetType returns the Type field value if set, zero value otherwise. +func (o *CreateVolume) GetType() VolumeType { + if o == nil || IsNil(o.Type) { + var ret VolumeType + return ret + } + return *o.Type +} + +// GetTypeOk returns a tuple with the Type field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CreateVolume) GetTypeOk() (*VolumeType, bool) { + if o == nil || IsNil(o.Type) { + return nil, false + } + return o.Type, true +} + +// HasType returns a boolean if a field has been set. +func (o *CreateVolume) HasType() bool { + if o != nil && !IsNil(o.Type) { + return true + } + + return false +} + +// SetType gets a reference to the given VolumeType and assigns it to the Type field. +func (o *CreateVolume) SetType(v VolumeType) { + o.Type = &v +} + +// GetRegion returns the Region field value if set, zero value otherwise. +func (o *CreateVolume) GetRegion() string { + if o == nil || IsNil(o.Region) { + var ret string + return ret + } + return *o.Region +} + +// GetRegionOk returns a tuple with the Region field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CreateVolume) GetRegionOk() (*string, bool) { + if o == nil || IsNil(o.Region) { + return nil, false + } + return o.Region, true +} + +// HasRegion returns a boolean if a field has been set. +func (o *CreateVolume) HasRegion() bool { + if o != nil && !IsNil(o.Region) { + return true + } + + return false +} + +// SetRegion gets a reference to the given string and assigns it to the Region field. +func (o *CreateVolume) SetRegion(v string) { + o.Region = &v +} + func (o CreateVolume) MarshalJSON() ([]byte, error) { toSerialize,err := o.ToMap() if err != nil { @@ -80,6 +152,12 @@ func (o CreateVolume) MarshalJSON() ([]byte, error) { func (o CreateVolume) ToMap() (map[string]interface{}, error) { toSerialize := map[string]interface{}{} toSerialize["name"] = o.Name + if !IsNil(o.Type) { + toSerialize["type"] = o.Type + } + if !IsNil(o.Region) { + toSerialize["region"] = o.Region + } for key, value := range o.AdditionalProperties { toSerialize[key] = value @@ -124,6 +202,8 @@ func (o *CreateVolume) UnmarshalJSON(data []byte) (err error) { if err = json.Unmarshal(data, &additionalProperties); err == nil { delete(additionalProperties, "name") + delete(additionalProperties, "type") + delete(additionalProperties, "region") o.AdditionalProperties = additionalProperties } diff --git a/api-client-go/model_create_volume_mount_token.go b/api-client-go/model_create_volume_mount_token.go new file mode 100644 index 000000000..6ff435856 --- /dev/null +++ b/api-client-go/model_create_volume_mount_token.go @@ -0,0 +1,161 @@ +/* +Daytona + +Daytona AI platform API Docs + +API version: 1.0 +Contact: support@daytona.com +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package apiclient + +import ( + "encoding/json" +) + +// checks if the CreateVolumeMountToken type satisfies the MappedNullable interface at compile time +var _ MappedNullable = &CreateVolumeMountToken{} + +// CreateVolumeMountToken struct for CreateVolumeMountToken +type CreateVolumeMountToken struct { + // The access mode for the mount. Defaults to rw. + Mode *string `json:"mode,omitempty"` + AdditionalProperties map[string]interface{} +} + +type _CreateVolumeMountToken CreateVolumeMountToken + +// NewCreateVolumeMountToken instantiates a new CreateVolumeMountToken object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewCreateVolumeMountToken() *CreateVolumeMountToken { + this := CreateVolumeMountToken{} + var mode string = "rw" + this.Mode = &mode + return &this +} + +// NewCreateVolumeMountTokenWithDefaults instantiates a new CreateVolumeMountToken object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewCreateVolumeMountTokenWithDefaults() *CreateVolumeMountToken { + this := CreateVolumeMountToken{} + var mode string = "rw" + this.Mode = &mode + return &this +} + +// GetMode returns the Mode field value if set, zero value otherwise. +func (o *CreateVolumeMountToken) GetMode() string { + if o == nil || IsNil(o.Mode) { + var ret string + return ret + } + return *o.Mode +} + +// GetModeOk returns a tuple with the Mode field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CreateVolumeMountToken) GetModeOk() (*string, bool) { + if o == nil || IsNil(o.Mode) { + return nil, false + } + return o.Mode, true +} + +// HasMode returns a boolean if a field has been set. +func (o *CreateVolumeMountToken) HasMode() bool { + if o != nil && !IsNil(o.Mode) { + return true + } + + return false +} + +// SetMode gets a reference to the given string and assigns it to the Mode field. +func (o *CreateVolumeMountToken) SetMode(v string) { + o.Mode = &v +} + +func (o CreateVolumeMountToken) MarshalJSON() ([]byte, error) { + toSerialize,err := o.ToMap() + if err != nil { + return []byte{}, err + } + return json.Marshal(toSerialize) +} + +func (o CreateVolumeMountToken) ToMap() (map[string]interface{}, error) { + toSerialize := map[string]interface{}{} + if !IsNil(o.Mode) { + toSerialize["mode"] = o.Mode + } + + for key, value := range o.AdditionalProperties { + toSerialize[key] = value + } + + return toSerialize, nil +} + +func (o *CreateVolumeMountToken) UnmarshalJSON(data []byte) (err error) { + varCreateVolumeMountToken := _CreateVolumeMountToken{} + + err = json.Unmarshal(data, &varCreateVolumeMountToken) + + if err != nil { + return err + } + + *o = CreateVolumeMountToken(varCreateVolumeMountToken) + + additionalProperties := make(map[string]interface{}) + + if err = json.Unmarshal(data, &additionalProperties); err == nil { + delete(additionalProperties, "mode") + o.AdditionalProperties = additionalProperties + } + + return err +} + +type NullableCreateVolumeMountToken struct { + value *CreateVolumeMountToken + isSet bool +} + +func (v NullableCreateVolumeMountToken) Get() *CreateVolumeMountToken { + return v.value +} + +func (v *NullableCreateVolumeMountToken) Set(val *CreateVolumeMountToken) { + v.value = val + v.isSet = true +} + +func (v NullableCreateVolumeMountToken) IsSet() bool { + return v.isSet +} + +func (v *NullableCreateVolumeMountToken) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableCreateVolumeMountToken(val *CreateVolumeMountToken) *NullableCreateVolumeMountToken { + return &NullableCreateVolumeMountToken{value: val, isSet: true} +} + +func (v NullableCreateVolumeMountToken) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableCreateVolumeMountToken) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} + + diff --git a/api-client-go/model_hotmount_region.go b/api-client-go/model_hotmount_region.go new file mode 100644 index 000000000..5c49b1a54 --- /dev/null +++ b/api-client-go/model_hotmount_region.go @@ -0,0 +1,230 @@ +/* +Daytona + +Daytona AI platform API Docs + +API version: 1.0 +Contact: support@daytona.com +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package apiclient + +import ( + "encoding/json" + "fmt" +) + +// checks if the HotmountRegion type satisfies the MappedNullable interface at compile time +var _ MappedNullable = &HotmountRegion{} + +// HotmountRegion struct for HotmountRegion +type HotmountRegion struct { + // Stable region id + Region string `json:"region"` + // User-facing region name + Label string `json:"label"` + // Geo hint used for default region selection + Geo string `json:"geo"` + AdditionalProperties map[string]interface{} +} + +type _HotmountRegion HotmountRegion + +// NewHotmountRegion instantiates a new HotmountRegion object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewHotmountRegion(region string, label string, geo string) *HotmountRegion { + this := HotmountRegion{} + this.Region = region + this.Label = label + this.Geo = geo + return &this +} + +// NewHotmountRegionWithDefaults instantiates a new HotmountRegion object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewHotmountRegionWithDefaults() *HotmountRegion { + this := HotmountRegion{} + return &this +} + +// GetRegion returns the Region field value +func (o *HotmountRegion) GetRegion() string { + if o == nil { + var ret string + return ret + } + + return o.Region +} + +// GetRegionOk returns a tuple with the Region field value +// and a boolean to check if the value has been set. +func (o *HotmountRegion) GetRegionOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Region, true +} + +// SetRegion sets field value +func (o *HotmountRegion) SetRegion(v string) { + o.Region = v +} + +// GetLabel returns the Label field value +func (o *HotmountRegion) GetLabel() string { + if o == nil { + var ret string + return ret + } + + return o.Label +} + +// GetLabelOk returns a tuple with the Label field value +// and a boolean to check if the value has been set. +func (o *HotmountRegion) GetLabelOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Label, true +} + +// SetLabel sets field value +func (o *HotmountRegion) SetLabel(v string) { + o.Label = v +} + +// GetGeo returns the Geo field value +func (o *HotmountRegion) GetGeo() string { + if o == nil { + var ret string + return ret + } + + return o.Geo +} + +// GetGeoOk returns a tuple with the Geo field value +// and a boolean to check if the value has been set. +func (o *HotmountRegion) GetGeoOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Geo, true +} + +// SetGeo sets field value +func (o *HotmountRegion) SetGeo(v string) { + o.Geo = v +} + +func (o HotmountRegion) MarshalJSON() ([]byte, error) { + toSerialize,err := o.ToMap() + if err != nil { + return []byte{}, err + } + return json.Marshal(toSerialize) +} + +func (o HotmountRegion) ToMap() (map[string]interface{}, error) { + toSerialize := map[string]interface{}{} + toSerialize["region"] = o.Region + toSerialize["label"] = o.Label + toSerialize["geo"] = o.Geo + + for key, value := range o.AdditionalProperties { + toSerialize[key] = value + } + + return toSerialize, nil +} + +func (o *HotmountRegion) UnmarshalJSON(data []byte) (err error) { + // This validates that all required properties are included in the JSON object + // by unmarshalling the object into a generic map with string keys and checking + // that every required field exists as a key in the generic map. + requiredProperties := []string{ + "region", + "label", + "geo", + } + + allProperties := make(map[string]interface{}) + + err = json.Unmarshal(data, &allProperties) + + if err != nil { + return err; + } + + for _, requiredProperty := range(requiredProperties) { + if _, exists := allProperties[requiredProperty]; !exists { + return fmt.Errorf("no value given for required property %v", requiredProperty) + } + } + + varHotmountRegion := _HotmountRegion{} + + err = json.Unmarshal(data, &varHotmountRegion) + + if err != nil { + return err + } + + *o = HotmountRegion(varHotmountRegion) + + additionalProperties := make(map[string]interface{}) + + if err = json.Unmarshal(data, &additionalProperties); err == nil { + delete(additionalProperties, "region") + delete(additionalProperties, "label") + delete(additionalProperties, "geo") + o.AdditionalProperties = additionalProperties + } + + return err +} + +type NullableHotmountRegion struct { + value *HotmountRegion + isSet bool +} + +func (v NullableHotmountRegion) Get() *HotmountRegion { + return v.value +} + +func (v *NullableHotmountRegion) Set(val *HotmountRegion) { + v.value = val + v.isSet = true +} + +func (v NullableHotmountRegion) IsSet() bool { + return v.isSet +} + +func (v *NullableHotmountRegion) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableHotmountRegion(val *HotmountRegion) *NullableHotmountRegion { + return &NullableHotmountRegion{value: val, isSet: true} +} + +func (v NullableHotmountRegion) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableHotmountRegion) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} + + diff --git a/api-client-go/model_region.go b/api-client-go/model_region.go index d9e49ad59..80ae8e2f2 100644 --- a/api-client-go/model_region.go +++ b/api-client-go/model_region.go @@ -39,6 +39,8 @@ type Region struct { SshGatewayUrl NullableString `json:"sshGatewayUrl,omitempty"` // Snapshot Manager URL for the region SnapshotManagerUrl NullableString `json:"snapshotManagerUrl,omitempty"` + // Whether blockmount volumes are supported in this region + BlockmountEnabled bool `json:"blockmountEnabled"` AdditionalProperties map[string]interface{} } @@ -48,13 +50,14 @@ type _Region Region // This constructor will assign default values to properties that have it defined, // and makes sure properties required by API are set, but the set of arguments // will change when the set of required properties is changed -func NewRegion(id string, name string, regionType RegionType, createdAt string, updatedAt string) *Region { +func NewRegion(id string, name string, regionType RegionType, createdAt string, updatedAt string, blockmountEnabled bool) *Region { this := Region{} this.Id = id this.Name = name this.RegionType = regionType this.CreatedAt = createdAt this.UpdatedAt = updatedAt + this.BlockmountEnabled = blockmountEnabled return &this } @@ -354,6 +357,30 @@ func (o *Region) UnsetSnapshotManagerUrl() { o.SnapshotManagerUrl.Unset() } +// GetBlockmountEnabled returns the BlockmountEnabled field value +func (o *Region) GetBlockmountEnabled() bool { + if o == nil { + var ret bool + return ret + } + + return o.BlockmountEnabled +} + +// GetBlockmountEnabledOk returns a tuple with the BlockmountEnabled field value +// and a boolean to check if the value has been set. +func (o *Region) GetBlockmountEnabledOk() (*bool, bool) { + if o == nil { + return nil, false + } + return &o.BlockmountEnabled, true +} + +// SetBlockmountEnabled sets field value +func (o *Region) SetBlockmountEnabled(v bool) { + o.BlockmountEnabled = v +} + func (o Region) MarshalJSON() ([]byte, error) { toSerialize,err := o.ToMap() if err != nil { @@ -381,6 +408,7 @@ func (o Region) ToMap() (map[string]interface{}, error) { if o.SnapshotManagerUrl.IsSet() { toSerialize["snapshotManagerUrl"] = o.SnapshotManagerUrl.Get() } + toSerialize["blockmountEnabled"] = o.BlockmountEnabled for key, value := range o.AdditionalProperties { toSerialize[key] = value @@ -399,6 +427,7 @@ func (o *Region) UnmarshalJSON(data []byte) (err error) { "regionType", "createdAt", "updatedAt", + "blockmountEnabled", } allProperties := make(map[string]interface{}) @@ -437,6 +466,7 @@ func (o *Region) UnmarshalJSON(data []byte) (err error) { delete(additionalProperties, "proxyUrl") delete(additionalProperties, "sshGatewayUrl") delete(additionalProperties, "snapshotManagerUrl") + delete(additionalProperties, "blockmountEnabled") o.AdditionalProperties = additionalProperties } diff --git a/api-client-go/model_sandbox_volume.go b/api-client-go/model_sandbox_volume.go index 9efe93c57..83be95f5a 100644 --- a/api-client-go/model_sandbox_volume.go +++ b/api-client-go/model_sandbox_volume.go @@ -27,6 +27,24 @@ type SandboxVolume struct { MountPath string `json:"mountPath"` // Optional subpath within the volume to mount. When specified, only this S3 prefix will be accessible. When omitted, the entire volume is mounted. Subpath *string `json:"subpath,omitempty"` + // The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy. + VolumeType *VolumeType `json:"volumeType,omitempty"` + // The organization that owns the volume. Forwarded to the runner to isolate the S3 prefix. Set only for blockmount volumes. + OrganizationId *string `json:"organizationId,omitempty"` + // The logical size of the volume in gigabytes, used by the runner as the per-sandbox scratch quota. Set only for blockmount volumes. + SizeInGb *float32 `json:"sizeInGb,omitempty"` + // The region the blockmount volume's data lives in. Forwarded to the runner so it can fetch the region's store credentials over its authenticated channel. Set only for blockmount volumes. + Region *string `json:"region,omitempty"` + // The S3 endpoint of the CAS store the blockmount volume's data lives in, resolved from the volume's region. Forwarded to the runner so cross-region attaches reach the right bucket. Omitted when the volume's region has no store configured (runner falls back to its env store). Credentials are never sent here — the runner fetches them by region. Set only for blockmount volumes. + S3Endpoint *string `json:"s3Endpoint,omitempty"` + // The S3 region of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes. + S3Region *string `json:"s3Region,omitempty"` + // The S3 bucket of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes. + S3Bucket *string `json:"s3Bucket,omitempty"` + // The S3 key prefix of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes. + S3Prefix *string `json:"s3Prefix,omitempty"` + // Whether the CAS store uses path-style S3 addressing. Set only for blockmount volumes. + S3PathStyle *bool `json:"s3PathStyle,omitempty"` AdditionalProperties map[string]interface{} } @@ -131,6 +149,294 @@ func (o *SandboxVolume) SetSubpath(v string) { o.Subpath = &v } +// GetVolumeType returns the VolumeType field value if set, zero value otherwise. +func (o *SandboxVolume) GetVolumeType() VolumeType { + if o == nil || IsNil(o.VolumeType) { + var ret VolumeType + return ret + } + return *o.VolumeType +} + +// GetVolumeTypeOk returns a tuple with the VolumeType field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SandboxVolume) GetVolumeTypeOk() (*VolumeType, bool) { + if o == nil || IsNil(o.VolumeType) { + return nil, false + } + return o.VolumeType, true +} + +// HasVolumeType returns a boolean if a field has been set. +func (o *SandboxVolume) HasVolumeType() bool { + if o != nil && !IsNil(o.VolumeType) { + return true + } + + return false +} + +// SetVolumeType gets a reference to the given VolumeType and assigns it to the VolumeType field. +func (o *SandboxVolume) SetVolumeType(v VolumeType) { + o.VolumeType = &v +} + +// GetOrganizationId returns the OrganizationId field value if set, zero value otherwise. +func (o *SandboxVolume) GetOrganizationId() string { + if o == nil || IsNil(o.OrganizationId) { + var ret string + return ret + } + return *o.OrganizationId +} + +// GetOrganizationIdOk returns a tuple with the OrganizationId field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SandboxVolume) GetOrganizationIdOk() (*string, bool) { + if o == nil || IsNil(o.OrganizationId) { + return nil, false + } + return o.OrganizationId, true +} + +// HasOrganizationId returns a boolean if a field has been set. +func (o *SandboxVolume) HasOrganizationId() bool { + if o != nil && !IsNil(o.OrganizationId) { + return true + } + + return false +} + +// SetOrganizationId gets a reference to the given string and assigns it to the OrganizationId field. +func (o *SandboxVolume) SetOrganizationId(v string) { + o.OrganizationId = &v +} + +// GetSizeInGb returns the SizeInGb field value if set, zero value otherwise. +func (o *SandboxVolume) GetSizeInGb() float32 { + if o == nil || IsNil(o.SizeInGb) { + var ret float32 + return ret + } + return *o.SizeInGb +} + +// GetSizeInGbOk returns a tuple with the SizeInGb field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SandboxVolume) GetSizeInGbOk() (*float32, bool) { + if o == nil || IsNil(o.SizeInGb) { + return nil, false + } + return o.SizeInGb, true +} + +// HasSizeInGb returns a boolean if a field has been set. +func (o *SandboxVolume) HasSizeInGb() bool { + if o != nil && !IsNil(o.SizeInGb) { + return true + } + + return false +} + +// SetSizeInGb gets a reference to the given float32 and assigns it to the SizeInGb field. +func (o *SandboxVolume) SetSizeInGb(v float32) { + o.SizeInGb = &v +} + +// GetRegion returns the Region field value if set, zero value otherwise. +func (o *SandboxVolume) GetRegion() string { + if o == nil || IsNil(o.Region) { + var ret string + return ret + } + return *o.Region +} + +// GetRegionOk returns a tuple with the Region field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SandboxVolume) GetRegionOk() (*string, bool) { + if o == nil || IsNil(o.Region) { + return nil, false + } + return o.Region, true +} + +// HasRegion returns a boolean if a field has been set. +func (o *SandboxVolume) HasRegion() bool { + if o != nil && !IsNil(o.Region) { + return true + } + + return false +} + +// SetRegion gets a reference to the given string and assigns it to the Region field. +func (o *SandboxVolume) SetRegion(v string) { + o.Region = &v +} + +// GetS3Endpoint returns the S3Endpoint field value if set, zero value otherwise. +func (o *SandboxVolume) GetS3Endpoint() string { + if o == nil || IsNil(o.S3Endpoint) { + var ret string + return ret + } + return *o.S3Endpoint +} + +// GetS3EndpointOk returns a tuple with the S3Endpoint field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SandboxVolume) GetS3EndpointOk() (*string, bool) { + if o == nil || IsNil(o.S3Endpoint) { + return nil, false + } + return o.S3Endpoint, true +} + +// HasS3Endpoint returns a boolean if a field has been set. +func (o *SandboxVolume) HasS3Endpoint() bool { + if o != nil && !IsNil(o.S3Endpoint) { + return true + } + + return false +} + +// SetS3Endpoint gets a reference to the given string and assigns it to the S3Endpoint field. +func (o *SandboxVolume) SetS3Endpoint(v string) { + o.S3Endpoint = &v +} + +// GetS3Region returns the S3Region field value if set, zero value otherwise. +func (o *SandboxVolume) GetS3Region() string { + if o == nil || IsNil(o.S3Region) { + var ret string + return ret + } + return *o.S3Region +} + +// GetS3RegionOk returns a tuple with the S3Region field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SandboxVolume) GetS3RegionOk() (*string, bool) { + if o == nil || IsNil(o.S3Region) { + return nil, false + } + return o.S3Region, true +} + +// HasS3Region returns a boolean if a field has been set. +func (o *SandboxVolume) HasS3Region() bool { + if o != nil && !IsNil(o.S3Region) { + return true + } + + return false +} + +// SetS3Region gets a reference to the given string and assigns it to the S3Region field. +func (o *SandboxVolume) SetS3Region(v string) { + o.S3Region = &v +} + +// GetS3Bucket returns the S3Bucket field value if set, zero value otherwise. +func (o *SandboxVolume) GetS3Bucket() string { + if o == nil || IsNil(o.S3Bucket) { + var ret string + return ret + } + return *o.S3Bucket +} + +// GetS3BucketOk returns a tuple with the S3Bucket field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SandboxVolume) GetS3BucketOk() (*string, bool) { + if o == nil || IsNil(o.S3Bucket) { + return nil, false + } + return o.S3Bucket, true +} + +// HasS3Bucket returns a boolean if a field has been set. +func (o *SandboxVolume) HasS3Bucket() bool { + if o != nil && !IsNil(o.S3Bucket) { + return true + } + + return false +} + +// SetS3Bucket gets a reference to the given string and assigns it to the S3Bucket field. +func (o *SandboxVolume) SetS3Bucket(v string) { + o.S3Bucket = &v +} + +// GetS3Prefix returns the S3Prefix field value if set, zero value otherwise. +func (o *SandboxVolume) GetS3Prefix() string { + if o == nil || IsNil(o.S3Prefix) { + var ret string + return ret + } + return *o.S3Prefix +} + +// GetS3PrefixOk returns a tuple with the S3Prefix field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SandboxVolume) GetS3PrefixOk() (*string, bool) { + if o == nil || IsNil(o.S3Prefix) { + return nil, false + } + return o.S3Prefix, true +} + +// HasS3Prefix returns a boolean if a field has been set. +func (o *SandboxVolume) HasS3Prefix() bool { + if o != nil && !IsNil(o.S3Prefix) { + return true + } + + return false +} + +// SetS3Prefix gets a reference to the given string and assigns it to the S3Prefix field. +func (o *SandboxVolume) SetS3Prefix(v string) { + o.S3Prefix = &v +} + +// GetS3PathStyle returns the S3PathStyle field value if set, zero value otherwise. +func (o *SandboxVolume) GetS3PathStyle() bool { + if o == nil || IsNil(o.S3PathStyle) { + var ret bool + return ret + } + return *o.S3PathStyle +} + +// GetS3PathStyleOk returns a tuple with the S3PathStyle field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *SandboxVolume) GetS3PathStyleOk() (*bool, bool) { + if o == nil || IsNil(o.S3PathStyle) { + return nil, false + } + return o.S3PathStyle, true +} + +// HasS3PathStyle returns a boolean if a field has been set. +func (o *SandboxVolume) HasS3PathStyle() bool { + if o != nil && !IsNil(o.S3PathStyle) { + return true + } + + return false +} + +// SetS3PathStyle gets a reference to the given bool and assigns it to the S3PathStyle field. +func (o *SandboxVolume) SetS3PathStyle(v bool) { + o.S3PathStyle = &v +} + func (o SandboxVolume) MarshalJSON() ([]byte, error) { toSerialize,err := o.ToMap() if err != nil { @@ -146,6 +452,33 @@ func (o SandboxVolume) ToMap() (map[string]interface{}, error) { if !IsNil(o.Subpath) { toSerialize["subpath"] = o.Subpath } + if !IsNil(o.VolumeType) { + toSerialize["volumeType"] = o.VolumeType + } + if !IsNil(o.OrganizationId) { + toSerialize["organizationId"] = o.OrganizationId + } + if !IsNil(o.SizeInGb) { + toSerialize["sizeInGb"] = o.SizeInGb + } + if !IsNil(o.Region) { + toSerialize["region"] = o.Region + } + if !IsNil(o.S3Endpoint) { + toSerialize["s3Endpoint"] = o.S3Endpoint + } + if !IsNil(o.S3Region) { + toSerialize["s3Region"] = o.S3Region + } + if !IsNil(o.S3Bucket) { + toSerialize["s3Bucket"] = o.S3Bucket + } + if !IsNil(o.S3Prefix) { + toSerialize["s3Prefix"] = o.S3Prefix + } + if !IsNil(o.S3PathStyle) { + toSerialize["s3PathStyle"] = o.S3PathStyle + } for key, value := range o.AdditionalProperties { toSerialize[key] = value @@ -193,6 +526,15 @@ func (o *SandboxVolume) UnmarshalJSON(data []byte) (err error) { delete(additionalProperties, "volumeId") delete(additionalProperties, "mountPath") delete(additionalProperties, "subpath") + delete(additionalProperties, "volumeType") + delete(additionalProperties, "organizationId") + delete(additionalProperties, "sizeInGb") + delete(additionalProperties, "region") + delete(additionalProperties, "s3Endpoint") + delete(additionalProperties, "s3Region") + delete(additionalProperties, "s3Bucket") + delete(additionalProperties, "s3Prefix") + delete(additionalProperties, "s3PathStyle") o.AdditionalProperties = additionalProperties } diff --git a/api-client-go/model_volume_dto.go b/api-client-go/model_volume_dto.go index 0ee0b6f2b..cbdcc17f5 100644 --- a/api-client-go/model_volume_dto.go +++ b/api-client-go/model_volume_dto.go @@ -27,6 +27,18 @@ type VolumeDto struct { Name string `json:"name"` // Organization ID OrganizationId string `json:"organizationId"` + // Volume type + Type VolumeType `json:"type"` + // The per-sandbox scratch quota in GB. Set only for blockmount volumes. + SizeInGb NullableFloat32 `json:"sizeInGb,omitempty"` + // The region the volume's data lives in. For blockmount volumes this selects the region-local CAS store (a performance/placement knob — sandboxes in any region can attach it, colocation is just faster). For hotmount volumes this is the hotmount deployment region. Set for blockmount and hotmount volumes. + Region NullableString `json:"region,omitempty"` + // The hotmount sharing mode (false = single-writer write-back, true = multi-writer synchronous). Set only for hotmount volumes. + Shared NullableBool `json:"shared,omitempty"` + // The id of the most recent committed manifest, read-through from the reconciliation store. Set only for blockmount volumes that have been committed at least once. + LastManifestId NullableString `json:"lastManifestId,omitempty"` + // Conflicts recorded on the latest manifest — concurrent same-path modifications the store resolved (last-change-wins). Read-through from the store. Set only for blockmount volumes. + Conflicts []BlockmountConflict `json:"conflicts,omitempty"` // Volume state State VolumeState `json:"state"` // Creation timestamp @@ -46,11 +58,12 @@ type _VolumeDto VolumeDto // This constructor will assign default values to properties that have it defined, // and makes sure properties required by API are set, but the set of arguments // will change when the set of required properties is changed -func NewVolumeDto(id string, name string, organizationId string, state VolumeState, createdAt string, updatedAt string, errorReason NullableString) *VolumeDto { +func NewVolumeDto(id string, name string, organizationId string, type_ VolumeType, state VolumeState, createdAt string, updatedAt string, errorReason NullableString) *VolumeDto { this := VolumeDto{} this.Id = id this.Name = name this.OrganizationId = organizationId + this.Type = type_ this.State = state this.CreatedAt = createdAt this.UpdatedAt = updatedAt @@ -138,6 +151,231 @@ func (o *VolumeDto) SetOrganizationId(v string) { o.OrganizationId = v } +// GetType returns the Type field value +func (o *VolumeDto) GetType() VolumeType { + if o == nil { + var ret VolumeType + return ret + } + + return o.Type +} + +// GetTypeOk returns a tuple with the Type field value +// and a boolean to check if the value has been set. +func (o *VolumeDto) GetTypeOk() (*VolumeType, bool) { + if o == nil { + return nil, false + } + return &o.Type, true +} + +// SetType sets field value +func (o *VolumeDto) SetType(v VolumeType) { + o.Type = v +} + +// GetSizeInGb returns the SizeInGb field value if set, zero value otherwise (both if not set or set to explicit null). +func (o *VolumeDto) GetSizeInGb() float32 { + if o == nil || IsNil(o.SizeInGb.Get()) { + var ret float32 + return ret + } + return *o.SizeInGb.Get() +} + +// GetSizeInGbOk returns a tuple with the SizeInGb field value if set, nil otherwise +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *VolumeDto) GetSizeInGbOk() (*float32, bool) { + if o == nil { + return nil, false + } + return o.SizeInGb.Get(), o.SizeInGb.IsSet() +} + +// HasSizeInGb returns a boolean if a field has been set. +func (o *VolumeDto) HasSizeInGb() bool { + if o != nil && o.SizeInGb.IsSet() { + return true + } + + return false +} + +// SetSizeInGb gets a reference to the given NullableFloat32 and assigns it to the SizeInGb field. +func (o *VolumeDto) SetSizeInGb(v float32) { + o.SizeInGb.Set(&v) +} +// SetSizeInGbNil sets the value for SizeInGb to be an explicit nil +func (o *VolumeDto) SetSizeInGbNil() { + o.SizeInGb.Set(nil) +} + +// UnsetSizeInGb ensures that no value is present for SizeInGb, not even an explicit nil +func (o *VolumeDto) UnsetSizeInGb() { + o.SizeInGb.Unset() +} + +// GetRegion returns the Region field value if set, zero value otherwise (both if not set or set to explicit null). +func (o *VolumeDto) GetRegion() string { + if o == nil || IsNil(o.Region.Get()) { + var ret string + return ret + } + return *o.Region.Get() +} + +// GetRegionOk returns a tuple with the Region field value if set, nil otherwise +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *VolumeDto) GetRegionOk() (*string, bool) { + if o == nil { + return nil, false + } + return o.Region.Get(), o.Region.IsSet() +} + +// HasRegion returns a boolean if a field has been set. +func (o *VolumeDto) HasRegion() bool { + if o != nil && o.Region.IsSet() { + return true + } + + return false +} + +// SetRegion gets a reference to the given NullableString and assigns it to the Region field. +func (o *VolumeDto) SetRegion(v string) { + o.Region.Set(&v) +} +// SetRegionNil sets the value for Region to be an explicit nil +func (o *VolumeDto) SetRegionNil() { + o.Region.Set(nil) +} + +// UnsetRegion ensures that no value is present for Region, not even an explicit nil +func (o *VolumeDto) UnsetRegion() { + o.Region.Unset() +} + +// GetShared returns the Shared field value if set, zero value otherwise (both if not set or set to explicit null). +func (o *VolumeDto) GetShared() bool { + if o == nil || IsNil(o.Shared.Get()) { + var ret bool + return ret + } + return *o.Shared.Get() +} + +// GetSharedOk returns a tuple with the Shared field value if set, nil otherwise +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *VolumeDto) GetSharedOk() (*bool, bool) { + if o == nil { + return nil, false + } + return o.Shared.Get(), o.Shared.IsSet() +} + +// HasShared returns a boolean if a field has been set. +func (o *VolumeDto) HasShared() bool { + if o != nil && o.Shared.IsSet() { + return true + } + + return false +} + +// SetShared gets a reference to the given NullableBool and assigns it to the Shared field. +func (o *VolumeDto) SetShared(v bool) { + o.Shared.Set(&v) +} +// SetSharedNil sets the value for Shared to be an explicit nil +func (o *VolumeDto) SetSharedNil() { + o.Shared.Set(nil) +} + +// UnsetShared ensures that no value is present for Shared, not even an explicit nil +func (o *VolumeDto) UnsetShared() { + o.Shared.Unset() +} + +// GetLastManifestId returns the LastManifestId field value if set, zero value otherwise (both if not set or set to explicit null). +func (o *VolumeDto) GetLastManifestId() string { + if o == nil || IsNil(o.LastManifestId.Get()) { + var ret string + return ret + } + return *o.LastManifestId.Get() +} + +// GetLastManifestIdOk returns a tuple with the LastManifestId field value if set, nil otherwise +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *VolumeDto) GetLastManifestIdOk() (*string, bool) { + if o == nil { + return nil, false + } + return o.LastManifestId.Get(), o.LastManifestId.IsSet() +} + +// HasLastManifestId returns a boolean if a field has been set. +func (o *VolumeDto) HasLastManifestId() bool { + if o != nil && o.LastManifestId.IsSet() { + return true + } + + return false +} + +// SetLastManifestId gets a reference to the given NullableString and assigns it to the LastManifestId field. +func (o *VolumeDto) SetLastManifestId(v string) { + o.LastManifestId.Set(&v) +} +// SetLastManifestIdNil sets the value for LastManifestId to be an explicit nil +func (o *VolumeDto) SetLastManifestIdNil() { + o.LastManifestId.Set(nil) +} + +// UnsetLastManifestId ensures that no value is present for LastManifestId, not even an explicit nil +func (o *VolumeDto) UnsetLastManifestId() { + o.LastManifestId.Unset() +} + +// GetConflicts returns the Conflicts field value if set, zero value otherwise (both if not set or set to explicit null). +func (o *VolumeDto) GetConflicts() []BlockmountConflict { + if o == nil { + var ret []BlockmountConflict + return ret + } + return o.Conflicts +} + +// GetConflictsOk returns a tuple with the Conflicts field value if set, nil otherwise +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *VolumeDto) GetConflictsOk() ([]BlockmountConflict, bool) { + if o == nil || IsNil(o.Conflicts) { + return nil, false + } + return o.Conflicts, true +} + +// HasConflicts returns a boolean if a field has been set. +func (o *VolumeDto) HasConflicts() bool { + if o != nil && !IsNil(o.Conflicts) { + return true + } + + return false +} + +// SetConflicts gets a reference to the given []BlockmountConflict and assigns it to the Conflicts field. +func (o *VolumeDto) SetConflicts(v []BlockmountConflict) { + o.Conflicts = v +} + // GetState returns the State field value func (o *VolumeDto) GetState() VolumeState { if o == nil { @@ -291,6 +529,22 @@ func (o VolumeDto) ToMap() (map[string]interface{}, error) { toSerialize["id"] = o.Id toSerialize["name"] = o.Name toSerialize["organizationId"] = o.OrganizationId + toSerialize["type"] = o.Type + if o.SizeInGb.IsSet() { + toSerialize["sizeInGb"] = o.SizeInGb.Get() + } + if o.Region.IsSet() { + toSerialize["region"] = o.Region.Get() + } + if o.Shared.IsSet() { + toSerialize["shared"] = o.Shared.Get() + } + if o.LastManifestId.IsSet() { + toSerialize["lastManifestId"] = o.LastManifestId.Get() + } + if o.Conflicts != nil { + toSerialize["conflicts"] = o.Conflicts + } toSerialize["state"] = o.State toSerialize["createdAt"] = o.CreatedAt toSerialize["updatedAt"] = o.UpdatedAt @@ -314,6 +568,7 @@ func (o *VolumeDto) UnmarshalJSON(data []byte) (err error) { "id", "name", "organizationId", + "type", "state", "createdAt", "updatedAt", @@ -350,6 +605,12 @@ func (o *VolumeDto) UnmarshalJSON(data []byte) (err error) { delete(additionalProperties, "id") delete(additionalProperties, "name") delete(additionalProperties, "organizationId") + delete(additionalProperties, "type") + delete(additionalProperties, "sizeInGb") + delete(additionalProperties, "region") + delete(additionalProperties, "shared") + delete(additionalProperties, "lastManifestId") + delete(additionalProperties, "conflicts") delete(additionalProperties, "state") delete(additionalProperties, "createdAt") delete(additionalProperties, "updatedAt") diff --git a/api-client-go/model_volume_mount_token_dto.go b/api-client-go/model_volume_mount_token_dto.go new file mode 100644 index 000000000..0285b5c30 --- /dev/null +++ b/api-client-go/model_volume_mount_token_dto.go @@ -0,0 +1,368 @@ +/* +Daytona + +Daytona AI platform API Docs + +API version: 1.0 +Contact: support@daytona.com +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package apiclient + +import ( + "encoding/json" + "fmt" +) + +// checks if the VolumeMountTokenDto type satisfies the MappedNullable interface at compile time +var _ MappedNullable = &VolumeMountTokenDto{} + +// VolumeMountTokenDto struct for VolumeMountTokenDto +type VolumeMountTokenDto struct { + // The short-lived macaroon token the in-sandbox agent uses to mount the volume + Token string `json:"token"` + // The token expiration timestamp + ExpiresAt string `json:"expiresAt"` + // The hotmount region the volume lives in + Region string `json:"region"` + // The gateway gRPC endpoint (SEAWEED_GATEWAY_GRPC) + GatewayGrpc string `json:"gatewayGrpc"` + // The gateway HTTPS endpoint (SEAWEED_GATEWAY_HTTP) + GatewayHttp string `json:"gatewayHttp"` + // The binaries bucket base URL used to bootstrap the mount (SEAWEED_BINARIES_URL) + BinariesUrl string `json:"binariesUrl"` + // The pinned client binary version to use (SEAWEED_VERSION), when the region pins one + Version NullableString `json:"version,omitempty"` + AdditionalProperties map[string]interface{} +} + +type _VolumeMountTokenDto VolumeMountTokenDto + +// NewVolumeMountTokenDto instantiates a new VolumeMountTokenDto object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewVolumeMountTokenDto(token string, expiresAt string, region string, gatewayGrpc string, gatewayHttp string, binariesUrl string) *VolumeMountTokenDto { + this := VolumeMountTokenDto{} + this.Token = token + this.ExpiresAt = expiresAt + this.Region = region + this.GatewayGrpc = gatewayGrpc + this.GatewayHttp = gatewayHttp + this.BinariesUrl = binariesUrl + return &this +} + +// NewVolumeMountTokenDtoWithDefaults instantiates a new VolumeMountTokenDto object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewVolumeMountTokenDtoWithDefaults() *VolumeMountTokenDto { + this := VolumeMountTokenDto{} + return &this +} + +// GetToken returns the Token field value +func (o *VolumeMountTokenDto) GetToken() string { + if o == nil { + var ret string + return ret + } + + return o.Token +} + +// GetTokenOk returns a tuple with the Token field value +// and a boolean to check if the value has been set. +func (o *VolumeMountTokenDto) GetTokenOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Token, true +} + +// SetToken sets field value +func (o *VolumeMountTokenDto) SetToken(v string) { + o.Token = v +} + +// GetExpiresAt returns the ExpiresAt field value +func (o *VolumeMountTokenDto) GetExpiresAt() string { + if o == nil { + var ret string + return ret + } + + return o.ExpiresAt +} + +// GetExpiresAtOk returns a tuple with the ExpiresAt field value +// and a boolean to check if the value has been set. +func (o *VolumeMountTokenDto) GetExpiresAtOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.ExpiresAt, true +} + +// SetExpiresAt sets field value +func (o *VolumeMountTokenDto) SetExpiresAt(v string) { + o.ExpiresAt = v +} + +// GetRegion returns the Region field value +func (o *VolumeMountTokenDto) GetRegion() string { + if o == nil { + var ret string + return ret + } + + return o.Region +} + +// GetRegionOk returns a tuple with the Region field value +// and a boolean to check if the value has been set. +func (o *VolumeMountTokenDto) GetRegionOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Region, true +} + +// SetRegion sets field value +func (o *VolumeMountTokenDto) SetRegion(v string) { + o.Region = v +} + +// GetGatewayGrpc returns the GatewayGrpc field value +func (o *VolumeMountTokenDto) GetGatewayGrpc() string { + if o == nil { + var ret string + return ret + } + + return o.GatewayGrpc +} + +// GetGatewayGrpcOk returns a tuple with the GatewayGrpc field value +// and a boolean to check if the value has been set. +func (o *VolumeMountTokenDto) GetGatewayGrpcOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.GatewayGrpc, true +} + +// SetGatewayGrpc sets field value +func (o *VolumeMountTokenDto) SetGatewayGrpc(v string) { + o.GatewayGrpc = v +} + +// GetGatewayHttp returns the GatewayHttp field value +func (o *VolumeMountTokenDto) GetGatewayHttp() string { + if o == nil { + var ret string + return ret + } + + return o.GatewayHttp +} + +// GetGatewayHttpOk returns a tuple with the GatewayHttp field value +// and a boolean to check if the value has been set. +func (o *VolumeMountTokenDto) GetGatewayHttpOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.GatewayHttp, true +} + +// SetGatewayHttp sets field value +func (o *VolumeMountTokenDto) SetGatewayHttp(v string) { + o.GatewayHttp = v +} + +// GetBinariesUrl returns the BinariesUrl field value +func (o *VolumeMountTokenDto) GetBinariesUrl() string { + if o == nil { + var ret string + return ret + } + + return o.BinariesUrl +} + +// GetBinariesUrlOk returns a tuple with the BinariesUrl field value +// and a boolean to check if the value has been set. +func (o *VolumeMountTokenDto) GetBinariesUrlOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.BinariesUrl, true +} + +// SetBinariesUrl sets field value +func (o *VolumeMountTokenDto) SetBinariesUrl(v string) { + o.BinariesUrl = v +} + +// GetVersion returns the Version field value if set, zero value otherwise (both if not set or set to explicit null). +func (o *VolumeMountTokenDto) GetVersion() string { + if o == nil || IsNil(o.Version.Get()) { + var ret string + return ret + } + return *o.Version.Get() +} + +// GetVersionOk returns a tuple with the Version field value if set, nil otherwise +// and a boolean to check if the value has been set. +// NOTE: If the value is an explicit nil, `nil, true` will be returned +func (o *VolumeMountTokenDto) GetVersionOk() (*string, bool) { + if o == nil { + return nil, false + } + return o.Version.Get(), o.Version.IsSet() +} + +// HasVersion returns a boolean if a field has been set. +func (o *VolumeMountTokenDto) HasVersion() bool { + if o != nil && o.Version.IsSet() { + return true + } + + return false +} + +// SetVersion gets a reference to the given NullableString and assigns it to the Version field. +func (o *VolumeMountTokenDto) SetVersion(v string) { + o.Version.Set(&v) +} +// SetVersionNil sets the value for Version to be an explicit nil +func (o *VolumeMountTokenDto) SetVersionNil() { + o.Version.Set(nil) +} + +// UnsetVersion ensures that no value is present for Version, not even an explicit nil +func (o *VolumeMountTokenDto) UnsetVersion() { + o.Version.Unset() +} + +func (o VolumeMountTokenDto) MarshalJSON() ([]byte, error) { + toSerialize,err := o.ToMap() + if err != nil { + return []byte{}, err + } + return json.Marshal(toSerialize) +} + +func (o VolumeMountTokenDto) ToMap() (map[string]interface{}, error) { + toSerialize := map[string]interface{}{} + toSerialize["token"] = o.Token + toSerialize["expiresAt"] = o.ExpiresAt + toSerialize["region"] = o.Region + toSerialize["gatewayGrpc"] = o.GatewayGrpc + toSerialize["gatewayHttp"] = o.GatewayHttp + toSerialize["binariesUrl"] = o.BinariesUrl + if o.Version.IsSet() { + toSerialize["version"] = o.Version.Get() + } + + for key, value := range o.AdditionalProperties { + toSerialize[key] = value + } + + return toSerialize, nil +} + +func (o *VolumeMountTokenDto) UnmarshalJSON(data []byte) (err error) { + // This validates that all required properties are included in the JSON object + // by unmarshalling the object into a generic map with string keys and checking + // that every required field exists as a key in the generic map. + requiredProperties := []string{ + "token", + "expiresAt", + "region", + "gatewayGrpc", + "gatewayHttp", + "binariesUrl", + } + + allProperties := make(map[string]interface{}) + + err = json.Unmarshal(data, &allProperties) + + if err != nil { + return err; + } + + for _, requiredProperty := range(requiredProperties) { + if _, exists := allProperties[requiredProperty]; !exists { + return fmt.Errorf("no value given for required property %v", requiredProperty) + } + } + + varVolumeMountTokenDto := _VolumeMountTokenDto{} + + err = json.Unmarshal(data, &varVolumeMountTokenDto) + + if err != nil { + return err + } + + *o = VolumeMountTokenDto(varVolumeMountTokenDto) + + additionalProperties := make(map[string]interface{}) + + if err = json.Unmarshal(data, &additionalProperties); err == nil { + delete(additionalProperties, "token") + delete(additionalProperties, "expiresAt") + delete(additionalProperties, "region") + delete(additionalProperties, "gatewayGrpc") + delete(additionalProperties, "gatewayHttp") + delete(additionalProperties, "binariesUrl") + delete(additionalProperties, "version") + o.AdditionalProperties = additionalProperties + } + + return err +} + +type NullableVolumeMountTokenDto struct { + value *VolumeMountTokenDto + isSet bool +} + +func (v NullableVolumeMountTokenDto) Get() *VolumeMountTokenDto { + return v.value +} + +func (v *NullableVolumeMountTokenDto) Set(val *VolumeMountTokenDto) { + v.value = val + v.isSet = true +} + +func (v NullableVolumeMountTokenDto) IsSet() bool { + return v.isSet +} + +func (v *NullableVolumeMountTokenDto) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableVolumeMountTokenDto(val *VolumeMountTokenDto) *NullableVolumeMountTokenDto { + return &NullableVolumeMountTokenDto{value: val, isSet: true} +} + +func (v NullableVolumeMountTokenDto) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableVolumeMountTokenDto) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} + + diff --git a/api-client-go/model_volume_type.go b/api-client-go/model_volume_type.go new file mode 100644 index 000000000..0b47b16a6 --- /dev/null +++ b/api-client-go/model_volume_type.go @@ -0,0 +1,117 @@ +/* +Daytona + +Daytona AI platform API Docs + +API version: 1.0 +Contact: support@daytona.com +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package apiclient + +import ( + "encoding/json" +) + +// VolumeType The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy. +type VolumeType string + +// List of VolumeType +const ( + VOLUMETYPE_LEGACY VolumeType = "legacy" + VOLUMETYPE_HOTMOUNT VolumeType = "hotmount" + VOLUMETYPE_BLOCKMOUNT VolumeType = "blockmount" + VOLUMETYPE_UNKNOWN_DEFAULT_OPEN_API VolumeType = "11184809" +) + +// All allowed values of VolumeType enum +var AllowedVolumeTypeEnumValues = []VolumeType{ + "legacy", + "hotmount", + "blockmount", + "11184809", +} + +func (v *VolumeType) UnmarshalJSON(src []byte) error { + var value string + err := json.Unmarshal(src, &value) + if err != nil { + return err + } + enumTypeValue := VolumeType(value) + for _, existing := range AllowedVolumeTypeEnumValues { + if existing == enumTypeValue { + *v = enumTypeValue + return nil + } + } + + *v = VOLUMETYPE_UNKNOWN_DEFAULT_OPEN_API + return nil +} + +// NewVolumeTypeFromValue returns a pointer to a valid VolumeType +// for the value passed as argument, or an error if the value passed is not allowed by the enum +func NewVolumeTypeFromValue(v string) (*VolumeType, error) { + ev := VolumeType(v) + if ev.IsValid() { + return &ev, nil + } else { + enumValue := VOLUMETYPE_UNKNOWN_DEFAULT_OPEN_API + return &enumValue, nil + } +} + +// IsValid return true if the value is valid for the enum, false otherwise +func (v VolumeType) IsValid() bool { + for _, existing := range AllowedVolumeTypeEnumValues { + if existing == v { + return true + } + } + return false +} + +// Ptr returns reference to VolumeType value +func (v VolumeType) Ptr() *VolumeType { + return &v +} + +type NullableVolumeType struct { + value *VolumeType + isSet bool +} + +func (v NullableVolumeType) Get() *VolumeType { + return v.value +} + +func (v *NullableVolumeType) Set(val *VolumeType) { + v.value = val + v.isSet = true +} + +func (v NullableVolumeType) IsSet() bool { + return v.isSet +} + +func (v *NullableVolumeType) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableVolumeType(val *VolumeType) *NullableVolumeType { + return &NullableVolumeType{value: val, isSet: true} +} + +func (v NullableVolumeType) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableVolumeType) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} + diff --git a/api-client-java/.openapi-generator/FILES b/api-client-java/.openapi-generator/FILES index 46e17f686..668d9cacd 100644 --- a/api-client-java/.openapi-generator/FILES +++ b/api-client-java/.openapi-generator/FILES @@ -50,6 +50,7 @@ src/main/java/io/daytona/api/client/model/ApiKeyList.java src/main/java/io/daytona/api/client/model/ApiKeyResponse.java src/main/java/io/daytona/api/client/model/AuditLog.java src/main/java/io/daytona/api/client/model/AvailableSandboxClass.java +src/main/java/io/daytona/api/client/model/BlockmountConflict.java src/main/java/io/daytona/api/client/model/BuildInfo.java src/main/java/io/daytona/api/client/model/Command.java src/main/java/io/daytona/api/client/model/CompletionContext.java @@ -79,6 +80,7 @@ src/main/java/io/daytona/api/client/model/CreateSessionRequest.java src/main/java/io/daytona/api/client/model/CreateSnapshot.java src/main/java/io/daytona/api/client/model/CreateUser.java src/main/java/io/daytona/api/client/model/CreateVolume.java +src/main/java/io/daytona/api/client/model/CreateVolumeMountToken.java src/main/java/io/daytona/api/client/model/DateFilter.java src/main/java/io/daytona/api/client/model/DaytonaConfiguration.java src/main/java/io/daytona/api/client/model/DisplayInfoResponse.java @@ -103,6 +105,7 @@ src/main/java/io/daytona/api/client/model/GpuType.java src/main/java/io/daytona/api/client/model/HealthControllerCheck200Response.java src/main/java/io/daytona/api/client/model/HealthControllerCheck200ResponseInfoValue.java src/main/java/io/daytona/api/client/model/HealthControllerCheck503Response.java +src/main/java/io/daytona/api/client/model/HotmountRegion.java src/main/java/io/daytona/api/client/model/IntFilter.java src/main/java/io/daytona/api/client/model/Job.java src/main/java/io/daytona/api/client/model/JobStatus.java @@ -231,7 +234,9 @@ src/main/java/io/daytona/api/client/model/User.java src/main/java/io/daytona/api/client/model/UserHomeDirResponse.java src/main/java/io/daytona/api/client/model/UserPublicKey.java src/main/java/io/daytona/api/client/model/VolumeDto.java +src/main/java/io/daytona/api/client/model/VolumeMountTokenDto.java src/main/java/io/daytona/api/client/model/VolumeState.java +src/main/java/io/daytona/api/client/model/VolumeType.java src/main/java/io/daytona/api/client/model/WebhookAppPortalAccess.java src/main/java/io/daytona/api/client/model/WebhookEvent.java src/main/java/io/daytona/api/client/model/WebhookInitializationStatus.java @@ -265,6 +270,7 @@ src/test/java/io/daytona/api/client/model/ApiKeyListTest.java src/test/java/io/daytona/api/client/model/ApiKeyResponseTest.java src/test/java/io/daytona/api/client/model/AuditLogTest.java src/test/java/io/daytona/api/client/model/AvailableSandboxClassTest.java +src/test/java/io/daytona/api/client/model/BlockmountConflictTest.java src/test/java/io/daytona/api/client/model/BuildInfoTest.java src/test/java/io/daytona/api/client/model/CommandTest.java src/test/java/io/daytona/api/client/model/CompletionContextTest.java @@ -293,6 +299,7 @@ src/test/java/io/daytona/api/client/model/CreateSecretTest.java src/test/java/io/daytona/api/client/model/CreateSessionRequestTest.java src/test/java/io/daytona/api/client/model/CreateSnapshotTest.java src/test/java/io/daytona/api/client/model/CreateUserTest.java +src/test/java/io/daytona/api/client/model/CreateVolumeMountTokenTest.java src/test/java/io/daytona/api/client/model/CreateVolumeTest.java src/test/java/io/daytona/api/client/model/DateFilterTest.java src/test/java/io/daytona/api/client/model/DaytonaConfigurationTest.java @@ -318,6 +325,7 @@ src/test/java/io/daytona/api/client/model/GpuTypeTest.java src/test/java/io/daytona/api/client/model/HealthControllerCheck200ResponseInfoValueTest.java src/test/java/io/daytona/api/client/model/HealthControllerCheck200ResponseTest.java src/test/java/io/daytona/api/client/model/HealthControllerCheck503ResponseTest.java +src/test/java/io/daytona/api/client/model/HotmountRegionTest.java src/test/java/io/daytona/api/client/model/IntFilterTest.java src/test/java/io/daytona/api/client/model/JobStatusTest.java src/test/java/io/daytona/api/client/model/JobTest.java @@ -446,7 +454,9 @@ src/test/java/io/daytona/api/client/model/UserHomeDirResponseTest.java src/test/java/io/daytona/api/client/model/UserPublicKeyTest.java src/test/java/io/daytona/api/client/model/UserTest.java src/test/java/io/daytona/api/client/model/VolumeDtoTest.java +src/test/java/io/daytona/api/client/model/VolumeMountTokenDtoTest.java src/test/java/io/daytona/api/client/model/VolumeStateTest.java +src/test/java/io/daytona/api/client/model/VolumeTypeTest.java src/test/java/io/daytona/api/client/model/WebhookAppPortalAccessTest.java src/test/java/io/daytona/api/client/model/WebhookEventTest.java src/test/java/io/daytona/api/client/model/WebhookInitializationStatusTest.java diff --git a/api-client-java/src/main/java/io/daytona/api/client/JSON.java b/api-client-java/src/main/java/io/daytona/api/client/JSON.java index 71fb7c765..7a4a669bf 100644 --- a/api-client-java/src/main/java/io/daytona/api/client/JSON.java +++ b/api-client-java/src/main/java/io/daytona/api/client/JSON.java @@ -105,6 +105,7 @@ private static Class getClassByDiscriminator(Map classByDiscriminatorValue, Stri gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.ApiKeyResponse.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.AuditLog.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.AvailableSandboxClass.CustomTypeAdapterFactory()); + gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.BlockmountConflict.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.BuildInfo.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.Command.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.CompletionContext.CustomTypeAdapterFactory()); @@ -134,6 +135,7 @@ private static Class getClassByDiscriminator(Map classByDiscriminatorValue, Stri gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.CreateSnapshot.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.CreateUser.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.CreateVolume.CustomTypeAdapterFactory()); + gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.CreateVolumeMountToken.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.DateFilter.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.DaytonaConfiguration.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.DisplayInfoResponse.CustomTypeAdapterFactory()); @@ -157,6 +159,7 @@ private static Class getClassByDiscriminator(Map classByDiscriminatorValue, Stri gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.HealthControllerCheck200Response.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.HealthControllerCheck200ResponseInfoValue.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.HealthControllerCheck503Response.CustomTypeAdapterFactory()); + gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.HotmountRegion.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.IntFilter.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.Job.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.KeyboardHotkeyRequest.CustomTypeAdapterFactory()); @@ -274,6 +277,7 @@ private static Class getClassByDiscriminator(Map classByDiscriminatorValue, Stri gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.UserHomeDirResponse.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.UserPublicKey.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.VolumeDto.CustomTypeAdapterFactory()); + gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.VolumeMountTokenDto.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.WebhookAppPortalAccess.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.WebhookInitializationStatus.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new io.daytona.api.client.model.WindowsResponse.CustomTypeAdapterFactory()); diff --git a/api-client-java/src/main/java/io/daytona/api/client/api/VolumesApi.java b/api-client-java/src/main/java/io/daytona/api/client/api/VolumesApi.java index b3fcaba7a..48b22ddc2 100644 --- a/api-client-java/src/main/java/io/daytona/api/client/api/VolumesApi.java +++ b/api-client-java/src/main/java/io/daytona/api/client/api/VolumesApi.java @@ -28,7 +28,11 @@ import io.daytona.api.client.model.CreateVolume; +import io.daytona.api.client.model.CreateVolumeMountToken; +import io.daytona.api.client.model.HotmountRegion; +import io.daytona.api.client.model.Region; import io.daytona.api.client.model.VolumeDto; +import io.daytona.api.client.model.VolumeMountTokenDto; import java.lang.reflect.Type; import java.util.ArrayList; @@ -209,6 +213,147 @@ public okhttp3.Call createVolumeAsync(@javax.annotation.Nonnull CreateVolume cre localVarApiClient.executeAsync(localVarCall, localVarReturnType, _callback); return localVarCall; } + /** + * Build call for createVolumeMountToken + * @param volumeId ID of the volume (required) + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @param createVolumeMountToken (optional) + * @param _callback Callback for upload/download progress + * @return Call to execute + * @throws ApiException If fail to serialize the request body object + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 The mount token has been successfully created. -
+ */ + public okhttp3.Call createVolumeMountTokenCall(@javax.annotation.Nonnull String volumeId, @javax.annotation.Nullable String xDaytonaOrganizationID, @javax.annotation.Nullable CreateVolumeMountToken createVolumeMountToken, final ApiCallback _callback) throws ApiException { + String basePath = null; + // Operation Servers + String[] localBasePaths = new String[] { }; + + // Determine Base Path to Use + if (localCustomBaseUrl != null){ + basePath = localCustomBaseUrl; + } else if ( localBasePaths.length > 0 ) { + basePath = localBasePaths[localHostIndex]; + } else { + basePath = null; + } + + Object localVarPostBody = createVolumeMountToken; + + // create path and map variables + String localVarPath = "/volumes/{volumeId}/mount-token" + .replace("{" + "volumeId" + "}", localVarApiClient.escapeString(volumeId.toString())); + + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); + + final String[] localVarAccepts = { + "application/json" + }; + final String localVarAccept = localVarApiClient.selectHeaderAccept(localVarAccepts); + if (localVarAccept != null) { + localVarHeaderParams.put("Accept", localVarAccept); + } + + final String[] localVarContentTypes = { + "application/json" + }; + final String localVarContentType = localVarApiClient.selectHeaderContentType(localVarContentTypes); + if (localVarContentType != null) { + localVarHeaderParams.put("Content-Type", localVarContentType); + } + + if (xDaytonaOrganizationID != null) { + localVarHeaderParams.put("X-Daytona-Organization-ID", localVarApiClient.parameterToString(xDaytonaOrganizationID)); + } + + + String[] localVarAuthNames = new String[] { "bearer", "oauth2" }; + return localVarApiClient.buildCall(basePath, localVarPath, "POST", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAuthNames, _callback); + } + + @SuppressWarnings("rawtypes") + private okhttp3.Call createVolumeMountTokenValidateBeforeCall(@javax.annotation.Nonnull String volumeId, @javax.annotation.Nullable String xDaytonaOrganizationID, @javax.annotation.Nullable CreateVolumeMountToken createVolumeMountToken, final ApiCallback _callback) throws ApiException { + // verify the required parameter 'volumeId' is set + if (volumeId == null) { + throw new ApiException("Missing the required parameter 'volumeId' when calling createVolumeMountToken(Async)"); + } + + return createVolumeMountTokenCall(volumeId, xDaytonaOrganizationID, createVolumeMountToken, _callback); + + } + + /** + * Create a mount token for a hotmount volume + * + * @param volumeId ID of the volume (required) + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @param createVolumeMountToken (optional) + * @return VolumeMountTokenDto + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 The mount token has been successfully created. -
+ */ + public VolumeMountTokenDto createVolumeMountToken(@javax.annotation.Nonnull String volumeId, @javax.annotation.Nullable String xDaytonaOrganizationID, @javax.annotation.Nullable CreateVolumeMountToken createVolumeMountToken) throws ApiException { + ApiResponse localVarResp = createVolumeMountTokenWithHttpInfo(volumeId, xDaytonaOrganizationID, createVolumeMountToken); + return localVarResp.getData(); + } + + /** + * Create a mount token for a hotmount volume + * + * @param volumeId ID of the volume (required) + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @param createVolumeMountToken (optional) + * @return ApiResponse<VolumeMountTokenDto> + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 The mount token has been successfully created. -
+ */ + public ApiResponse createVolumeMountTokenWithHttpInfo(@javax.annotation.Nonnull String volumeId, @javax.annotation.Nullable String xDaytonaOrganizationID, @javax.annotation.Nullable CreateVolumeMountToken createVolumeMountToken) throws ApiException { + okhttp3.Call localVarCall = createVolumeMountTokenValidateBeforeCall(volumeId, xDaytonaOrganizationID, createVolumeMountToken, null); + Type localVarReturnType = new TypeToken(){}.getType(); + return localVarApiClient.execute(localVarCall, localVarReturnType); + } + + /** + * Create a mount token for a hotmount volume (asynchronously) + * + * @param volumeId ID of the volume (required) + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @param createVolumeMountToken (optional) + * @param _callback The callback to be executed when the API call finishes + * @return The request call + * @throws ApiException If fail to process the API call, e.g. serializing the request body object + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 The mount token has been successfully created. -
+ */ + public okhttp3.Call createVolumeMountTokenAsync(@javax.annotation.Nonnull String volumeId, @javax.annotation.Nullable String xDaytonaOrganizationID, @javax.annotation.Nullable CreateVolumeMountToken createVolumeMountToken, final ApiCallback _callback) throws ApiException { + + okhttp3.Call localVarCall = createVolumeMountTokenValidateBeforeCall(volumeId, xDaytonaOrganizationID, createVolumeMountToken, _callback); + Type localVarReturnType = new TypeToken(){}.getType(); + localVarApiClient.executeAsync(localVarCall, localVarReturnType, _callback); + return localVarCall; + } /** * Build call for deleteVolume * @param volumeId ID of the volume (required) @@ -616,6 +761,258 @@ public okhttp3.Call getVolumeByNameAsync(@javax.annotation.Nonnull String name, localVarApiClient.executeAsync(localVarCall, localVarReturnType, _callback); return localVarCall; } + /** + * Build call for listBlockmountRegions + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @param _callback Callback for upload/download progress + * @return Call to execute + * @throws ApiException If fail to serialize the request body object + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 List of regions that support blockmount volumes -
+ */ + public okhttp3.Call listBlockmountRegionsCall(@javax.annotation.Nullable String xDaytonaOrganizationID, final ApiCallback _callback) throws ApiException { + String basePath = null; + // Operation Servers + String[] localBasePaths = new String[] { }; + + // Determine Base Path to Use + if (localCustomBaseUrl != null){ + basePath = localCustomBaseUrl; + } else if ( localBasePaths.length > 0 ) { + basePath = localBasePaths[localHostIndex]; + } else { + basePath = null; + } + + Object localVarPostBody = null; + + // create path and map variables + String localVarPath = "/volumes/blockmount-regions"; + + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); + + final String[] localVarAccepts = { + "application/json" + }; + final String localVarAccept = localVarApiClient.selectHeaderAccept(localVarAccepts); + if (localVarAccept != null) { + localVarHeaderParams.put("Accept", localVarAccept); + } + + final String[] localVarContentTypes = { + }; + final String localVarContentType = localVarApiClient.selectHeaderContentType(localVarContentTypes); + if (localVarContentType != null) { + localVarHeaderParams.put("Content-Type", localVarContentType); + } + + if (xDaytonaOrganizationID != null) { + localVarHeaderParams.put("X-Daytona-Organization-ID", localVarApiClient.parameterToString(xDaytonaOrganizationID)); + } + + + String[] localVarAuthNames = new String[] { "bearer", "oauth2" }; + return localVarApiClient.buildCall(basePath, localVarPath, "GET", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAuthNames, _callback); + } + + @SuppressWarnings("rawtypes") + private okhttp3.Call listBlockmountRegionsValidateBeforeCall(@javax.annotation.Nullable String xDaytonaOrganizationID, final ApiCallback _callback) throws ApiException { + return listBlockmountRegionsCall(xDaytonaOrganizationID, _callback); + + } + + /** + * List regions where blockmount volumes can be created + * + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @return List<Region> + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 List of regions that support blockmount volumes -
+ */ + public List listBlockmountRegions(@javax.annotation.Nullable String xDaytonaOrganizationID) throws ApiException { + ApiResponse> localVarResp = listBlockmountRegionsWithHttpInfo(xDaytonaOrganizationID); + return localVarResp.getData(); + } + + /** + * List regions where blockmount volumes can be created + * + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @return ApiResponse<List<Region>> + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 List of regions that support blockmount volumes -
+ */ + public ApiResponse> listBlockmountRegionsWithHttpInfo(@javax.annotation.Nullable String xDaytonaOrganizationID) throws ApiException { + okhttp3.Call localVarCall = listBlockmountRegionsValidateBeforeCall(xDaytonaOrganizationID, null); + Type localVarReturnType = new TypeToken>(){}.getType(); + return localVarApiClient.execute(localVarCall, localVarReturnType); + } + + /** + * List regions where blockmount volumes can be created (asynchronously) + * + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @param _callback The callback to be executed when the API call finishes + * @return The request call + * @throws ApiException If fail to process the API call, e.g. serializing the request body object + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 List of regions that support blockmount volumes -
+ */ + public okhttp3.Call listBlockmountRegionsAsync(@javax.annotation.Nullable String xDaytonaOrganizationID, final ApiCallback> _callback) throws ApiException { + + okhttp3.Call localVarCall = listBlockmountRegionsValidateBeforeCall(xDaytonaOrganizationID, _callback); + Type localVarReturnType = new TypeToken>(){}.getType(); + localVarApiClient.executeAsync(localVarCall, localVarReturnType, _callback); + return localVarCall; + } + /** + * Build call for listHotmountRegions + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @param _callback Callback for upload/download progress + * @return Call to execute + * @throws ApiException If fail to serialize the request body object + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 List of active hotmount regions selectable at volume creation -
+ */ + public okhttp3.Call listHotmountRegionsCall(@javax.annotation.Nullable String xDaytonaOrganizationID, final ApiCallback _callback) throws ApiException { + String basePath = null; + // Operation Servers + String[] localBasePaths = new String[] { }; + + // Determine Base Path to Use + if (localCustomBaseUrl != null){ + basePath = localCustomBaseUrl; + } else if ( localBasePaths.length > 0 ) { + basePath = localBasePaths[localHostIndex]; + } else { + basePath = null; + } + + Object localVarPostBody = null; + + // create path and map variables + String localVarPath = "/volumes/hotmount-regions"; + + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); + + final String[] localVarAccepts = { + "application/json" + }; + final String localVarAccept = localVarApiClient.selectHeaderAccept(localVarAccepts); + if (localVarAccept != null) { + localVarHeaderParams.put("Accept", localVarAccept); + } + + final String[] localVarContentTypes = { + }; + final String localVarContentType = localVarApiClient.selectHeaderContentType(localVarContentTypes); + if (localVarContentType != null) { + localVarHeaderParams.put("Content-Type", localVarContentType); + } + + if (xDaytonaOrganizationID != null) { + localVarHeaderParams.put("X-Daytona-Organization-ID", localVarApiClient.parameterToString(xDaytonaOrganizationID)); + } + + + String[] localVarAuthNames = new String[] { "bearer", "oauth2" }; + return localVarApiClient.buildCall(basePath, localVarPath, "GET", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAuthNames, _callback); + } + + @SuppressWarnings("rawtypes") + private okhttp3.Call listHotmountRegionsValidateBeforeCall(@javax.annotation.Nullable String xDaytonaOrganizationID, final ApiCallback _callback) throws ApiException { + return listHotmountRegionsCall(xDaytonaOrganizationID, _callback); + + } + + /** + * List available hotmount regions + * + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @return List<HotmountRegion> + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 List of active hotmount regions selectable at volume creation -
+ */ + public List listHotmountRegions(@javax.annotation.Nullable String xDaytonaOrganizationID) throws ApiException { + ApiResponse> localVarResp = listHotmountRegionsWithHttpInfo(xDaytonaOrganizationID); + return localVarResp.getData(); + } + + /** + * List available hotmount regions + * + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @return ApiResponse<List<HotmountRegion>> + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 List of active hotmount regions selectable at volume creation -
+ */ + public ApiResponse> listHotmountRegionsWithHttpInfo(@javax.annotation.Nullable String xDaytonaOrganizationID) throws ApiException { + okhttp3.Call localVarCall = listHotmountRegionsValidateBeforeCall(xDaytonaOrganizationID, null); + Type localVarReturnType = new TypeToken>(){}.getType(); + return localVarApiClient.execute(localVarCall, localVarReturnType); + } + + /** + * List available hotmount regions (asynchronously) + * + * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) + * @param _callback The callback to be executed when the API call finishes + * @return The request call + * @throws ApiException If fail to process the API call, e.g. serializing the request body object + * @http.response.details + + + + +
Response Details
Status Code Description Response Headers
200 List of active hotmount regions selectable at volume creation -
+ */ + public okhttp3.Call listHotmountRegionsAsync(@javax.annotation.Nullable String xDaytonaOrganizationID, final ApiCallback> _callback) throws ApiException { + + okhttp3.Call localVarCall = listHotmountRegionsValidateBeforeCall(xDaytonaOrganizationID, _callback); + Type localVarReturnType = new TypeToken>(){}.getType(); + localVarApiClient.executeAsync(localVarCall, localVarReturnType, _callback); + return localVarCall; + } /** * Build call for listVolumes * @param xDaytonaOrganizationID Use with JWT to specify the organization ID (optional) diff --git a/api-client-java/src/main/java/io/daytona/api/client/model/BlockmountConflict.java b/api-client-java/src/main/java/io/daytona/api/client/model/BlockmountConflict.java new file mode 100644 index 000000000..1ecd68e73 --- /dev/null +++ b/api-client-java/src/main/java/io/daytona/api/client/model/BlockmountConflict.java @@ -0,0 +1,406 @@ +/* + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.daytona.api.client.model; + +import java.util.Objects; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.Arrays; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.daytona.api.client.JSON; + +/** + * BlockmountConflict + */ +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.21.0") +public class BlockmountConflict { + public static final String SERIALIZED_NAME_PATH = "path"; + @SerializedName(SERIALIZED_NAME_PATH) + @javax.annotation.Nonnull + private String path; + + public static final String SERIALIZED_NAME_WINNER = "winner"; + @SerializedName(SERIALIZED_NAME_WINNER) + @javax.annotation.Nonnull + private String winner; + + public static final String SERIALIZED_NAME_REASON = "reason"; + @SerializedName(SERIALIZED_NAME_REASON) + @javax.annotation.Nonnull + private String reason; + + public static final String SERIALIZED_NAME_OURS_SHA = "oursSha"; + @SerializedName(SERIALIZED_NAME_OURS_SHA) + @javax.annotation.Nullable + private String oursSha; + + public static final String SERIALIZED_NAME_THEIRS_SHA = "theirsSha"; + @SerializedName(SERIALIZED_NAME_THEIRS_SHA) + @javax.annotation.Nullable + private String theirsSha; + + public BlockmountConflict() { + } + + public BlockmountConflict path(@javax.annotation.Nonnull String path) { + this.path = path; + return this; + } + + /** + * The path (relative to the volume root) that was concurrently modified + * @return path + */ + @javax.annotation.Nonnull + public String getPath() { + return path; + } + + public void setPath(@javax.annotation.Nonnull String path) { + this.path = path; + } + + + public BlockmountConflict winner(@javax.annotation.Nonnull String winner) { + this.winner = winner; + return this; + } + + /** + * Which side won the merge: \"ours\" (the committing writer) or \"theirs\" (the state already in latest) + * @return winner + */ + @javax.annotation.Nonnull + public String getWinner() { + return winner; + } + + public void setWinner(@javax.annotation.Nonnull String winner) { + this.winner = winner; + } + + + public BlockmountConflict reason(@javax.annotation.Nonnull String reason) { + this.reason = reason; + return this; + } + + /** + * Why the winner won: \"mtime\" (newer change), \"tie\" (equal mtimes, committer won), \"modify-over-delete\", or \"type\" + * @return reason + */ + @javax.annotation.Nonnull + public String getReason() { + return reason; + } + + public void setReason(@javax.annotation.Nonnull String reason) { + this.reason = reason; + } + + + public BlockmountConflict oursSha(@javax.annotation.Nullable String oursSha) { + this.oursSha = oursSha; + return this; + } + + /** + * Content hash of the committing writer’s version, when both sides were files + * @return oursSha + */ + @javax.annotation.Nullable + public String getOursSha() { + return oursSha; + } + + public void setOursSha(@javax.annotation.Nullable String oursSha) { + this.oursSha = oursSha; + } + + + public BlockmountConflict theirsSha(@javax.annotation.Nullable String theirsSha) { + this.theirsSha = theirsSha; + return this; + } + + /** + * Content hash of the concurrent version found in latest, when both sides were files + * @return theirsSha + */ + @javax.annotation.Nullable + public String getTheirsSha() { + return theirsSha; + } + + public void setTheirsSha(@javax.annotation.Nullable String theirsSha) { + this.theirsSha = theirsSha; + } + + /** + * A container for additional, undeclared properties. + * This is a holder for any undeclared properties as specified with + * the 'additionalProperties' keyword in the OAS document. + */ + private Map additionalProperties; + + /** + * Set the additional (undeclared) property with the specified name and value. + * If the property does not already exist, create it otherwise replace it. + * + * @param key name of the property + * @param value value of the property + * @return the BlockmountConflict instance itself + */ + public BlockmountConflict putAdditionalProperty(String key, Object value) { + if (this.additionalProperties == null) { + this.additionalProperties = new HashMap(); + } + this.additionalProperties.put(key, value); + return this; + } + + /** + * Return the additional (undeclared) property. + * + * @return a map of objects + */ + public Map getAdditionalProperties() { + return additionalProperties; + } + + /** + * Return the additional (undeclared) property with the specified name. + * + * @param key name of the property + * @return an object + */ + public Object getAdditionalProperty(String key) { + if (this.additionalProperties == null) { + return null; + } + return this.additionalProperties.get(key); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BlockmountConflict blockmountConflict = (BlockmountConflict) o; + return Objects.equals(this.path, blockmountConflict.path) && + Objects.equals(this.winner, blockmountConflict.winner) && + Objects.equals(this.reason, blockmountConflict.reason) && + Objects.equals(this.oursSha, blockmountConflict.oursSha) && + Objects.equals(this.theirsSha, blockmountConflict.theirsSha)&& + Objects.equals(this.additionalProperties, blockmountConflict.additionalProperties); + } + + @Override + public int hashCode() { + return Objects.hash(path, winner, reason, oursSha, theirsSha, additionalProperties); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class BlockmountConflict {\n"); + sb.append(" path: ").append(toIndentedString(path)).append("\n"); + sb.append(" winner: ").append(toIndentedString(winner)).append("\n"); + sb.append(" reason: ").append(toIndentedString(reason)).append("\n"); + sb.append(" oursSha: ").append(toIndentedString(oursSha)).append("\n"); + sb.append(" theirsSha: ").append(toIndentedString(theirsSha)).append("\n"); + sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + return o == null ? "null" : o.toString().replace("\n", "\n "); + } + + + public static HashSet openapiFields; + public static HashSet openapiRequiredFields; + + static { + // a set of all properties/fields (JSON key names) + openapiFields = new HashSet(Arrays.asList("path", "winner", "reason", "oursSha", "theirsSha")); + + // a set of required properties/fields (JSON key names) + openapiRequiredFields = new HashSet(Arrays.asList("path", "winner", "reason")); + } + + /** + * Validates the JSON Element and throws an exception if issues found + * + * @param jsonElement JSON Element + * @throws IOException if the JSON Element is invalid with respect to BlockmountConflict + */ + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + if (jsonElement == null) { + if (!BlockmountConflict.openapiRequiredFields.isEmpty()) { // has required fields but JSON element is null + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The required field(s) %s in BlockmountConflict is not found in the empty JSON string", BlockmountConflict.openapiRequiredFields.toString())); + } + } + + // check to make sure all required properties/fields are present in the JSON string + for (String requiredField : BlockmountConflict.openapiRequiredFields) { + if (jsonElement.getAsJsonObject().get(requiredField) == null) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The required field `%s` is not found in the JSON string: %s", requiredField, jsonElement.toString())); + } + } + JsonObject jsonObj = jsonElement.getAsJsonObject(); + if (!jsonObj.get("path").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `path` to be a primitive type in the JSON string but got `%s`", jsonObj.get("path").toString())); + } + if (!jsonObj.get("winner").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `winner` to be a primitive type in the JSON string but got `%s`", jsonObj.get("winner").toString())); + } + if (!jsonObj.get("reason").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `reason` to be a primitive type in the JSON string but got `%s`", jsonObj.get("reason").toString())); + } + if ((jsonObj.get("oursSha") != null && !jsonObj.get("oursSha").isJsonNull()) && !jsonObj.get("oursSha").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `oursSha` to be a primitive type in the JSON string but got `%s`", jsonObj.get("oursSha").toString())); + } + if ((jsonObj.get("theirsSha") != null && !jsonObj.get("theirsSha").isJsonNull()) && !jsonObj.get("theirsSha").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `theirsSha` to be a primitive type in the JSON string but got `%s`", jsonObj.get("theirsSha").toString())); + } + } + + public static class CustomTypeAdapterFactory implements TypeAdapterFactory { + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (!BlockmountConflict.class.isAssignableFrom(type.getRawType())) { + return null; // this class only serializes 'BlockmountConflict' and its subtypes + } + final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); + final TypeAdapter thisAdapter + = gson.getDelegateAdapter(this, TypeToken.get(BlockmountConflict.class)); + + return (TypeAdapter) new TypeAdapter() { + @Override + public void write(JsonWriter out, BlockmountConflict value) throws IOException { + JsonObject obj = thisAdapter.toJsonTree(value).getAsJsonObject(); + obj.remove("additionalProperties"); + // serialize additional properties + if (value.getAdditionalProperties() != null) { + for (Map.Entry entry : value.getAdditionalProperties().entrySet()) { + if (entry.getValue() instanceof String) + obj.addProperty(entry.getKey(), (String) entry.getValue()); + else if (entry.getValue() instanceof Number) + obj.addProperty(entry.getKey(), (Number) entry.getValue()); + else if (entry.getValue() instanceof Boolean) + obj.addProperty(entry.getKey(), (Boolean) entry.getValue()); + else if (entry.getValue() instanceof Character) + obj.addProperty(entry.getKey(), (Character) entry.getValue()); + else { + JsonElement jsonElement = gson.toJsonTree(entry.getValue()); + if (jsonElement.isJsonArray()) { + obj.add(entry.getKey(), jsonElement.getAsJsonArray()); + } else { + obj.add(entry.getKey(), jsonElement.getAsJsonObject()); + } + } + } + } + elementAdapter.write(out, obj); + } + + @Override + public BlockmountConflict read(JsonReader in) throws IOException { + JsonElement jsonElement = elementAdapter.read(in); + validateJsonElement(jsonElement); + JsonObject jsonObj = jsonElement.getAsJsonObject(); + // store additional fields in the deserialized instance + BlockmountConflict instance = thisAdapter.fromJsonTree(jsonObj); + for (Map.Entry entry : jsonObj.entrySet()) { + if (!openapiFields.contains(entry.getKey())) { + if (entry.getValue().isJsonPrimitive()) { // primitive type + if (entry.getValue().getAsJsonPrimitive().isString()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsString()); + else if (entry.getValue().getAsJsonPrimitive().isNumber()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsNumber()); + else if (entry.getValue().getAsJsonPrimitive().isBoolean()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsBoolean()); + else + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The field `%s` has unknown primitive type. Value: %s", entry.getKey(), entry.getValue().toString())); + } else if (entry.getValue().isJsonArray()) { + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), List.class)); + } else { // JSON object + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), HashMap.class)); + } + } + } + return instance; + } + + }.nullSafe(); + } + } + + /** + * Create an instance of BlockmountConflict given an JSON string + * + * @param jsonString JSON string + * @return An instance of BlockmountConflict + * @throws IOException if the JSON string is invalid with respect to BlockmountConflict + */ + public static BlockmountConflict fromJson(String jsonString) throws IOException { + return JSON.getGson().fromJson(jsonString, BlockmountConflict.class); + } + + /** + * Convert an instance of BlockmountConflict to an JSON string + * + * @return JSON string + */ + public String toJson() { + return JSON.getGson().toJson(this); + } +} + diff --git a/api-client-java/src/main/java/io/daytona/api/client/model/CreateVolume.java b/api-client-java/src/main/java/io/daytona/api/client/model/CreateVolume.java index 99706b6ab..6139ffb56 100644 --- a/api-client-java/src/main/java/io/daytona/api/client/model/CreateVolume.java +++ b/api-client-java/src/main/java/io/daytona/api/client/model/CreateVolume.java @@ -19,6 +19,7 @@ import com.google.gson.annotations.SerializedName; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import io.daytona.api.client.model.VolumeType; import java.io.IOException; import java.util.Arrays; @@ -55,6 +56,16 @@ public class CreateVolume { @javax.annotation.Nonnull private String name; + public static final String SERIALIZED_NAME_TYPE = "type"; + @SerializedName(SERIALIZED_NAME_TYPE) + @javax.annotation.Nullable + private VolumeType type = VolumeType.LEGACY; + + public static final String SERIALIZED_NAME_REGION = "region"; + @SerializedName(SERIALIZED_NAME_REGION) + @javax.annotation.Nullable + private String region; + public CreateVolume() { } @@ -76,6 +87,44 @@ public void setName(@javax.annotation.Nonnull String name) { this.name = name; } + + public CreateVolume type(@javax.annotation.Nullable VolumeType type) { + this.type = type; + return this; + } + + /** + * The type of the volume. Defaults to legacy. + * @return type + */ + @javax.annotation.Nullable + public VolumeType getType() { + return type; + } + + public void setType(@javax.annotation.Nullable VolumeType type) { + this.type = type; + } + + + public CreateVolume region(@javax.annotation.Nullable String region) { + this.region = region; + return this; + } + + /** + * The region to create the volume in. For blockmount volumes it selects the region-local CAS store the volume's data lives in — a performance/placement knob, not an attach restriction, so sandboxes in any region can attach the volume (colocation is just faster). Optional for blockmount: when omitted it defaults to the organization's default region (or the first region that offers blockmount). For hotmount volumes it selects the hotmount deployment region and defaults to an active region. Not allowed for legacy volumes. The volume's region is fixed for its lifetime. + * @return region + */ + @javax.annotation.Nullable + public String getRegion() { + return region; + } + + public void setRegion(@javax.annotation.Nullable String region) { + this.region = region; + } + /** * A container for additional, undeclared properties. * This is a holder for any undeclared properties as specified with @@ -131,13 +180,15 @@ public boolean equals(Object o) { return false; } CreateVolume createVolume = (CreateVolume) o; - return Objects.equals(this.name, createVolume.name)&& + return Objects.equals(this.name, createVolume.name) && + Objects.equals(this.type, createVolume.type) && + Objects.equals(this.region, createVolume.region)&& Objects.equals(this.additionalProperties, createVolume.additionalProperties); } @Override public int hashCode() { - return Objects.hash(name, additionalProperties); + return Objects.hash(name, type, region, additionalProperties); } @Override @@ -145,6 +196,8 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class CreateVolume {\n"); sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" region: ").append(toIndentedString(region)).append("\n"); sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); sb.append("}"); return sb.toString(); @@ -164,7 +217,7 @@ private String toIndentedString(Object o) { static { // a set of all properties/fields (JSON key names) - openapiFields = new HashSet(Arrays.asList("name")); + openapiFields = new HashSet(Arrays.asList("name", "type", "region")); // a set of required properties/fields (JSON key names) openapiRequiredFields = new HashSet(Arrays.asList("name")); @@ -193,6 +246,13 @@ public static void validateJsonElement(JsonElement jsonElement) throws IOExcepti if (!jsonObj.get("name").isJsonPrimitive()) { throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `name` to be a primitive type in the JSON string but got `%s`", jsonObj.get("name").toString())); } + // validate the optional field `type` + if (jsonObj.get("type") != null && !jsonObj.get("type").isJsonNull()) { + VolumeType.validateJsonElement(jsonObj.get("type")); + } + if ((jsonObj.get("region") != null && !jsonObj.get("region").isJsonNull()) && !jsonObj.get("region").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `region` to be a primitive type in the JSON string but got `%s`", jsonObj.get("region").toString())); + } } public static class CustomTypeAdapterFactory implements TypeAdapterFactory { diff --git a/api-client-java/src/main/java/io/daytona/api/client/model/CreateVolumeMountToken.java b/api-client-java/src/main/java/io/daytona/api/client/model/CreateVolumeMountToken.java new file mode 100644 index 000000000..b9b85df41 --- /dev/null +++ b/api-client-java/src/main/java/io/daytona/api/client/model/CreateVolumeMountToken.java @@ -0,0 +1,341 @@ +/* + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.daytona.api.client.model; + +import java.util.Objects; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.Arrays; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.daytona.api.client.JSON; + +/** + * CreateVolumeMountToken + */ +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.21.0") +public class CreateVolumeMountToken { + /** + * The access mode for the mount. Defaults to rw. + */ + @JsonAdapter(ModeEnum.Adapter.class) + public enum ModeEnum { + RW("rw"), + + RO("ro"), + + UNKNOWN_DEFAULT_OPEN_API("unknown_default_open_api"); + + private String value; + + ModeEnum(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static ModeEnum fromValue(String value) { + for (ModeEnum b : ModeEnum.values()) { + if (b.value.equals(value)) { + return b; + } + } + return UNKNOWN_DEFAULT_OPEN_API; + } + + public static class Adapter extends TypeAdapter { + @Override + public void write(final JsonWriter jsonWriter, final ModeEnum enumeration) throws IOException { + jsonWriter.value(enumeration.getValue()); + } + + @Override + public ModeEnum read(final JsonReader jsonReader) throws IOException { + String value = jsonReader.nextString(); + return ModeEnum.fromValue(value); + } + } + + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + String value = jsonElement.getAsString(); + ModeEnum.fromValue(value); + } + } + + public static final String SERIALIZED_NAME_MODE = "mode"; + @SerializedName(SERIALIZED_NAME_MODE) + @javax.annotation.Nullable + private ModeEnum mode = ModeEnum.RW; + + public CreateVolumeMountToken() { + } + + public CreateVolumeMountToken mode(@javax.annotation.Nullable ModeEnum mode) { + this.mode = mode; + return this; + } + + /** + * The access mode for the mount. Defaults to rw. + * @return mode + */ + @javax.annotation.Nullable + public ModeEnum getMode() { + return mode; + } + + public void setMode(@javax.annotation.Nullable ModeEnum mode) { + this.mode = mode; + } + + /** + * A container for additional, undeclared properties. + * This is a holder for any undeclared properties as specified with + * the 'additionalProperties' keyword in the OAS document. + */ + private Map additionalProperties; + + /** + * Set the additional (undeclared) property with the specified name and value. + * If the property does not already exist, create it otherwise replace it. + * + * @param key name of the property + * @param value value of the property + * @return the CreateVolumeMountToken instance itself + */ + public CreateVolumeMountToken putAdditionalProperty(String key, Object value) { + if (this.additionalProperties == null) { + this.additionalProperties = new HashMap(); + } + this.additionalProperties.put(key, value); + return this; + } + + /** + * Return the additional (undeclared) property. + * + * @return a map of objects + */ + public Map getAdditionalProperties() { + return additionalProperties; + } + + /** + * Return the additional (undeclared) property with the specified name. + * + * @param key name of the property + * @return an object + */ + public Object getAdditionalProperty(String key) { + if (this.additionalProperties == null) { + return null; + } + return this.additionalProperties.get(key); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CreateVolumeMountToken createVolumeMountToken = (CreateVolumeMountToken) o; + return Objects.equals(this.mode, createVolumeMountToken.mode)&& + Objects.equals(this.additionalProperties, createVolumeMountToken.additionalProperties); + } + + @Override + public int hashCode() { + return Objects.hash(mode, additionalProperties); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class CreateVolumeMountToken {\n"); + sb.append(" mode: ").append(toIndentedString(mode)).append("\n"); + sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + return o == null ? "null" : o.toString().replace("\n", "\n "); + } + + + public static HashSet openapiFields; + public static HashSet openapiRequiredFields; + + static { + // a set of all properties/fields (JSON key names) + openapiFields = new HashSet(Arrays.asList("mode")); + + // a set of required properties/fields (JSON key names) + openapiRequiredFields = new HashSet(0); + } + + /** + * Validates the JSON Element and throws an exception if issues found + * + * @param jsonElement JSON Element + * @throws IOException if the JSON Element is invalid with respect to CreateVolumeMountToken + */ + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + if (jsonElement == null) { + if (!CreateVolumeMountToken.openapiRequiredFields.isEmpty()) { // has required fields but JSON element is null + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The required field(s) %s in CreateVolumeMountToken is not found in the empty JSON string", CreateVolumeMountToken.openapiRequiredFields.toString())); + } + } + JsonObject jsonObj = jsonElement.getAsJsonObject(); + if ((jsonObj.get("mode") != null && !jsonObj.get("mode").isJsonNull()) && !jsonObj.get("mode").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `mode` to be a primitive type in the JSON string but got `%s`", jsonObj.get("mode").toString())); + } + // validate the optional field `mode` + if (jsonObj.get("mode") != null && !jsonObj.get("mode").isJsonNull()) { + ModeEnum.validateJsonElement(jsonObj.get("mode")); + } + } + + public static class CustomTypeAdapterFactory implements TypeAdapterFactory { + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (!CreateVolumeMountToken.class.isAssignableFrom(type.getRawType())) { + return null; // this class only serializes 'CreateVolumeMountToken' and its subtypes + } + final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); + final TypeAdapter thisAdapter + = gson.getDelegateAdapter(this, TypeToken.get(CreateVolumeMountToken.class)); + + return (TypeAdapter) new TypeAdapter() { + @Override + public void write(JsonWriter out, CreateVolumeMountToken value) throws IOException { + JsonObject obj = thisAdapter.toJsonTree(value).getAsJsonObject(); + obj.remove("additionalProperties"); + // serialize additional properties + if (value.getAdditionalProperties() != null) { + for (Map.Entry entry : value.getAdditionalProperties().entrySet()) { + if (entry.getValue() instanceof String) + obj.addProperty(entry.getKey(), (String) entry.getValue()); + else if (entry.getValue() instanceof Number) + obj.addProperty(entry.getKey(), (Number) entry.getValue()); + else if (entry.getValue() instanceof Boolean) + obj.addProperty(entry.getKey(), (Boolean) entry.getValue()); + else if (entry.getValue() instanceof Character) + obj.addProperty(entry.getKey(), (Character) entry.getValue()); + else { + JsonElement jsonElement = gson.toJsonTree(entry.getValue()); + if (jsonElement.isJsonArray()) { + obj.add(entry.getKey(), jsonElement.getAsJsonArray()); + } else { + obj.add(entry.getKey(), jsonElement.getAsJsonObject()); + } + } + } + } + elementAdapter.write(out, obj); + } + + @Override + public CreateVolumeMountToken read(JsonReader in) throws IOException { + JsonElement jsonElement = elementAdapter.read(in); + validateJsonElement(jsonElement); + JsonObject jsonObj = jsonElement.getAsJsonObject(); + // store additional fields in the deserialized instance + CreateVolumeMountToken instance = thisAdapter.fromJsonTree(jsonObj); + for (Map.Entry entry : jsonObj.entrySet()) { + if (!openapiFields.contains(entry.getKey())) { + if (entry.getValue().isJsonPrimitive()) { // primitive type + if (entry.getValue().getAsJsonPrimitive().isString()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsString()); + else if (entry.getValue().getAsJsonPrimitive().isNumber()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsNumber()); + else if (entry.getValue().getAsJsonPrimitive().isBoolean()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsBoolean()); + else + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The field `%s` has unknown primitive type. Value: %s", entry.getKey(), entry.getValue().toString())); + } else if (entry.getValue().isJsonArray()) { + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), List.class)); + } else { // JSON object + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), HashMap.class)); + } + } + } + return instance; + } + + }.nullSafe(); + } + } + + /** + * Create an instance of CreateVolumeMountToken given an JSON string + * + * @param jsonString JSON string + * @return An instance of CreateVolumeMountToken + * @throws IOException if the JSON string is invalid with respect to CreateVolumeMountToken + */ + public static CreateVolumeMountToken fromJson(String jsonString) throws IOException { + return JSON.getGson().fromJson(jsonString, CreateVolumeMountToken.class); + } + + /** + * Convert an instance of CreateVolumeMountToken to an JSON string + * + * @return JSON string + */ + public String toJson() { + return JSON.getGson().toJson(this); + } +} + diff --git a/api-client-java/src/main/java/io/daytona/api/client/model/HotmountRegion.java b/api-client-java/src/main/java/io/daytona/api/client/model/HotmountRegion.java new file mode 100644 index 000000000..01da9fe6d --- /dev/null +++ b/api-client-java/src/main/java/io/daytona/api/client/model/HotmountRegion.java @@ -0,0 +1,348 @@ +/* + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.daytona.api.client.model; + +import java.util.Objects; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.Arrays; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.daytona.api.client.JSON; + +/** + * HotmountRegion + */ +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.21.0") +public class HotmountRegion { + public static final String SERIALIZED_NAME_REGION = "region"; + @SerializedName(SERIALIZED_NAME_REGION) + @javax.annotation.Nonnull + private String region; + + public static final String SERIALIZED_NAME_LABEL = "label"; + @SerializedName(SERIALIZED_NAME_LABEL) + @javax.annotation.Nonnull + private String label; + + public static final String SERIALIZED_NAME_GEO = "geo"; + @SerializedName(SERIALIZED_NAME_GEO) + @javax.annotation.Nonnull + private String geo; + + public HotmountRegion() { + } + + public HotmountRegion region(@javax.annotation.Nonnull String region) { + this.region = region; + return this; + } + + /** + * Stable region id + * @return region + */ + @javax.annotation.Nonnull + public String getRegion() { + return region; + } + + public void setRegion(@javax.annotation.Nonnull String region) { + this.region = region; + } + + + public HotmountRegion label(@javax.annotation.Nonnull String label) { + this.label = label; + return this; + } + + /** + * User-facing region name + * @return label + */ + @javax.annotation.Nonnull + public String getLabel() { + return label; + } + + public void setLabel(@javax.annotation.Nonnull String label) { + this.label = label; + } + + + public HotmountRegion geo(@javax.annotation.Nonnull String geo) { + this.geo = geo; + return this; + } + + /** + * Geo hint used for default region selection + * @return geo + */ + @javax.annotation.Nonnull + public String getGeo() { + return geo; + } + + public void setGeo(@javax.annotation.Nonnull String geo) { + this.geo = geo; + } + + /** + * A container for additional, undeclared properties. + * This is a holder for any undeclared properties as specified with + * the 'additionalProperties' keyword in the OAS document. + */ + private Map additionalProperties; + + /** + * Set the additional (undeclared) property with the specified name and value. + * If the property does not already exist, create it otherwise replace it. + * + * @param key name of the property + * @param value value of the property + * @return the HotmountRegion instance itself + */ + public HotmountRegion putAdditionalProperty(String key, Object value) { + if (this.additionalProperties == null) { + this.additionalProperties = new HashMap(); + } + this.additionalProperties.put(key, value); + return this; + } + + /** + * Return the additional (undeclared) property. + * + * @return a map of objects + */ + public Map getAdditionalProperties() { + return additionalProperties; + } + + /** + * Return the additional (undeclared) property with the specified name. + * + * @param key name of the property + * @return an object + */ + public Object getAdditionalProperty(String key) { + if (this.additionalProperties == null) { + return null; + } + return this.additionalProperties.get(key); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HotmountRegion hotmountRegion = (HotmountRegion) o; + return Objects.equals(this.region, hotmountRegion.region) && + Objects.equals(this.label, hotmountRegion.label) && + Objects.equals(this.geo, hotmountRegion.geo)&& + Objects.equals(this.additionalProperties, hotmountRegion.additionalProperties); + } + + @Override + public int hashCode() { + return Objects.hash(region, label, geo, additionalProperties); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class HotmountRegion {\n"); + sb.append(" region: ").append(toIndentedString(region)).append("\n"); + sb.append(" label: ").append(toIndentedString(label)).append("\n"); + sb.append(" geo: ").append(toIndentedString(geo)).append("\n"); + sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + return o == null ? "null" : o.toString().replace("\n", "\n "); + } + + + public static HashSet openapiFields; + public static HashSet openapiRequiredFields; + + static { + // a set of all properties/fields (JSON key names) + openapiFields = new HashSet(Arrays.asList("region", "label", "geo")); + + // a set of required properties/fields (JSON key names) + openapiRequiredFields = new HashSet(Arrays.asList("region", "label", "geo")); + } + + /** + * Validates the JSON Element and throws an exception if issues found + * + * @param jsonElement JSON Element + * @throws IOException if the JSON Element is invalid with respect to HotmountRegion + */ + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + if (jsonElement == null) { + if (!HotmountRegion.openapiRequiredFields.isEmpty()) { // has required fields but JSON element is null + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The required field(s) %s in HotmountRegion is not found in the empty JSON string", HotmountRegion.openapiRequiredFields.toString())); + } + } + + // check to make sure all required properties/fields are present in the JSON string + for (String requiredField : HotmountRegion.openapiRequiredFields) { + if (jsonElement.getAsJsonObject().get(requiredField) == null) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The required field `%s` is not found in the JSON string: %s", requiredField, jsonElement.toString())); + } + } + JsonObject jsonObj = jsonElement.getAsJsonObject(); + if (!jsonObj.get("region").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `region` to be a primitive type in the JSON string but got `%s`", jsonObj.get("region").toString())); + } + if (!jsonObj.get("label").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `label` to be a primitive type in the JSON string but got `%s`", jsonObj.get("label").toString())); + } + if (!jsonObj.get("geo").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `geo` to be a primitive type in the JSON string but got `%s`", jsonObj.get("geo").toString())); + } + } + + public static class CustomTypeAdapterFactory implements TypeAdapterFactory { + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (!HotmountRegion.class.isAssignableFrom(type.getRawType())) { + return null; // this class only serializes 'HotmountRegion' and its subtypes + } + final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); + final TypeAdapter thisAdapter + = gson.getDelegateAdapter(this, TypeToken.get(HotmountRegion.class)); + + return (TypeAdapter) new TypeAdapter() { + @Override + public void write(JsonWriter out, HotmountRegion value) throws IOException { + JsonObject obj = thisAdapter.toJsonTree(value).getAsJsonObject(); + obj.remove("additionalProperties"); + // serialize additional properties + if (value.getAdditionalProperties() != null) { + for (Map.Entry entry : value.getAdditionalProperties().entrySet()) { + if (entry.getValue() instanceof String) + obj.addProperty(entry.getKey(), (String) entry.getValue()); + else if (entry.getValue() instanceof Number) + obj.addProperty(entry.getKey(), (Number) entry.getValue()); + else if (entry.getValue() instanceof Boolean) + obj.addProperty(entry.getKey(), (Boolean) entry.getValue()); + else if (entry.getValue() instanceof Character) + obj.addProperty(entry.getKey(), (Character) entry.getValue()); + else { + JsonElement jsonElement = gson.toJsonTree(entry.getValue()); + if (jsonElement.isJsonArray()) { + obj.add(entry.getKey(), jsonElement.getAsJsonArray()); + } else { + obj.add(entry.getKey(), jsonElement.getAsJsonObject()); + } + } + } + } + elementAdapter.write(out, obj); + } + + @Override + public HotmountRegion read(JsonReader in) throws IOException { + JsonElement jsonElement = elementAdapter.read(in); + validateJsonElement(jsonElement); + JsonObject jsonObj = jsonElement.getAsJsonObject(); + // store additional fields in the deserialized instance + HotmountRegion instance = thisAdapter.fromJsonTree(jsonObj); + for (Map.Entry entry : jsonObj.entrySet()) { + if (!openapiFields.contains(entry.getKey())) { + if (entry.getValue().isJsonPrimitive()) { // primitive type + if (entry.getValue().getAsJsonPrimitive().isString()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsString()); + else if (entry.getValue().getAsJsonPrimitive().isNumber()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsNumber()); + else if (entry.getValue().getAsJsonPrimitive().isBoolean()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsBoolean()); + else + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The field `%s` has unknown primitive type. Value: %s", entry.getKey(), entry.getValue().toString())); + } else if (entry.getValue().isJsonArray()) { + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), List.class)); + } else { // JSON object + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), HashMap.class)); + } + } + } + return instance; + } + + }.nullSafe(); + } + } + + /** + * Create an instance of HotmountRegion given an JSON string + * + * @param jsonString JSON string + * @return An instance of HotmountRegion + * @throws IOException if the JSON string is invalid with respect to HotmountRegion + */ + public static HotmountRegion fromJson(String jsonString) throws IOException { + return JSON.getGson().fromJson(jsonString, HotmountRegion.class); + } + + /** + * Convert an instance of HotmountRegion to an JSON string + * + * @return JSON string + */ + public String toJson() { + return JSON.getGson().toJson(this); + } +} + diff --git a/api-client-java/src/main/java/io/daytona/api/client/model/Region.java b/api-client-java/src/main/java/io/daytona/api/client/model/Region.java index f3ede77cc..ca40e5578 100644 --- a/api-client-java/src/main/java/io/daytona/api/client/model/Region.java +++ b/api-client-java/src/main/java/io/daytona/api/client/model/Region.java @@ -97,6 +97,11 @@ public class Region { @javax.annotation.Nullable private String snapshotManagerUrl; + public static final String SERIALIZED_NAME_BLOCKMOUNT_ENABLED = "blockmountEnabled"; + @SerializedName(SERIALIZED_NAME_BLOCKMOUNT_ENABLED) + @javax.annotation.Nonnull + private Boolean blockmountEnabled; + public Region() { } @@ -270,6 +275,25 @@ public void setSnapshotManagerUrl(@javax.annotation.Nullable String snapshotMana this.snapshotManagerUrl = snapshotManagerUrl; } + + public Region blockmountEnabled(@javax.annotation.Nonnull Boolean blockmountEnabled) { + this.blockmountEnabled = blockmountEnabled; + return this; + } + + /** + * Whether blockmount volumes are supported in this region + * @return blockmountEnabled + */ + @javax.annotation.Nonnull + public Boolean getBlockmountEnabled() { + return blockmountEnabled; + } + + public void setBlockmountEnabled(@javax.annotation.Nonnull Boolean blockmountEnabled) { + this.blockmountEnabled = blockmountEnabled; + } + /** * A container for additional, undeclared properties. * This is a holder for any undeclared properties as specified with @@ -333,7 +357,8 @@ public boolean equals(Object o) { Objects.equals(this.updatedAt, region.updatedAt) && Objects.equals(this.proxyUrl, region.proxyUrl) && Objects.equals(this.sshGatewayUrl, region.sshGatewayUrl) && - Objects.equals(this.snapshotManagerUrl, region.snapshotManagerUrl)&& + Objects.equals(this.snapshotManagerUrl, region.snapshotManagerUrl) && + Objects.equals(this.blockmountEnabled, region.blockmountEnabled)&& Objects.equals(this.additionalProperties, region.additionalProperties); } @@ -343,7 +368,7 @@ private static boolean equalsNullable(JsonNullable a, JsonNullable b) @Override public int hashCode() { - return Objects.hash(id, name, organizationId, regionType, createdAt, updatedAt, proxyUrl, sshGatewayUrl, snapshotManagerUrl, additionalProperties); + return Objects.hash(id, name, organizationId, regionType, createdAt, updatedAt, proxyUrl, sshGatewayUrl, snapshotManagerUrl, blockmountEnabled, additionalProperties); } private static int hashCodeNullable(JsonNullable a) { @@ -366,6 +391,7 @@ public String toString() { sb.append(" proxyUrl: ").append(toIndentedString(proxyUrl)).append("\n"); sb.append(" sshGatewayUrl: ").append(toIndentedString(sshGatewayUrl)).append("\n"); sb.append(" snapshotManagerUrl: ").append(toIndentedString(snapshotManagerUrl)).append("\n"); + sb.append(" blockmountEnabled: ").append(toIndentedString(blockmountEnabled)).append("\n"); sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); sb.append("}"); return sb.toString(); @@ -385,10 +411,10 @@ private String toIndentedString(Object o) { static { // a set of all properties/fields (JSON key names) - openapiFields = new HashSet(Arrays.asList("id", "name", "organizationId", "regionType", "createdAt", "updatedAt", "proxyUrl", "sshGatewayUrl", "snapshotManagerUrl")); + openapiFields = new HashSet(Arrays.asList("id", "name", "organizationId", "regionType", "createdAt", "updatedAt", "proxyUrl", "sshGatewayUrl", "snapshotManagerUrl", "blockmountEnabled")); // a set of required properties/fields (JSON key names) - openapiRequiredFields = new HashSet(Arrays.asList("id", "name", "regionType", "createdAt", "updatedAt")); + openapiRequiredFields = new HashSet(Arrays.asList("id", "name", "regionType", "createdAt", "updatedAt", "blockmountEnabled")); } /** diff --git a/api-client-java/src/main/java/io/daytona/api/client/model/SandboxVolume.java b/api-client-java/src/main/java/io/daytona/api/client/model/SandboxVolume.java index 9e3906f6d..dd6eaf4ce 100644 --- a/api-client-java/src/main/java/io/daytona/api/client/model/SandboxVolume.java +++ b/api-client-java/src/main/java/io/daytona/api/client/model/SandboxVolume.java @@ -19,7 +19,9 @@ import com.google.gson.annotations.SerializedName; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import io.daytona.api.client.model.VolumeType; import java.io.IOException; +import java.math.BigDecimal; import java.util.Arrays; import com.google.gson.Gson; @@ -65,6 +67,51 @@ public class SandboxVolume { @javax.annotation.Nullable private String subpath; + public static final String SERIALIZED_NAME_VOLUME_TYPE = "volumeType"; + @SerializedName(SERIALIZED_NAME_VOLUME_TYPE) + @javax.annotation.Nullable + private VolumeType volumeType; + + public static final String SERIALIZED_NAME_ORGANIZATION_ID = "organizationId"; + @SerializedName(SERIALIZED_NAME_ORGANIZATION_ID) + @javax.annotation.Nullable + private String organizationId; + + public static final String SERIALIZED_NAME_SIZE_IN_GB = "sizeInGb"; + @SerializedName(SERIALIZED_NAME_SIZE_IN_GB) + @javax.annotation.Nullable + private BigDecimal sizeInGb; + + public static final String SERIALIZED_NAME_REGION = "region"; + @SerializedName(SERIALIZED_NAME_REGION) + @javax.annotation.Nullable + private String region; + + public static final String SERIALIZED_NAME_S3_ENDPOINT = "s3Endpoint"; + @SerializedName(SERIALIZED_NAME_S3_ENDPOINT) + @javax.annotation.Nullable + private String s3Endpoint; + + public static final String SERIALIZED_NAME_S3_REGION = "s3Region"; + @SerializedName(SERIALIZED_NAME_S3_REGION) + @javax.annotation.Nullable + private String s3Region; + + public static final String SERIALIZED_NAME_S3_BUCKET = "s3Bucket"; + @SerializedName(SERIALIZED_NAME_S3_BUCKET) + @javax.annotation.Nullable + private String s3Bucket; + + public static final String SERIALIZED_NAME_S3_PREFIX = "s3Prefix"; + @SerializedName(SERIALIZED_NAME_S3_PREFIX) + @javax.annotation.Nullable + private String s3Prefix; + + public static final String SERIALIZED_NAME_S3_PATH_STYLE = "s3PathStyle"; + @SerializedName(SERIALIZED_NAME_S3_PATH_STYLE) + @javax.annotation.Nullable + private Boolean s3PathStyle; + public SandboxVolume() { } @@ -124,6 +171,177 @@ public void setSubpath(@javax.annotation.Nullable String subpath) { this.subpath = subpath; } + + public SandboxVolume volumeType(@javax.annotation.Nullable VolumeType volumeType) { + this.volumeType = volumeType; + return this; + } + + /** + * The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy. + * @return volumeType + */ + @javax.annotation.Nullable + public VolumeType getVolumeType() { + return volumeType; + } + + public void setVolumeType(@javax.annotation.Nullable VolumeType volumeType) { + this.volumeType = volumeType; + } + + + public SandboxVolume organizationId(@javax.annotation.Nullable String organizationId) { + this.organizationId = organizationId; + return this; + } + + /** + * The organization that owns the volume. Forwarded to the runner to isolate the S3 prefix. Set only for blockmount volumes. + * @return organizationId + */ + @javax.annotation.Nullable + public String getOrganizationId() { + return organizationId; + } + + public void setOrganizationId(@javax.annotation.Nullable String organizationId) { + this.organizationId = organizationId; + } + + + public SandboxVolume sizeInGb(@javax.annotation.Nullable BigDecimal sizeInGb) { + this.sizeInGb = sizeInGb; + return this; + } + + /** + * The logical size of the volume in gigabytes, used by the runner as the per-sandbox scratch quota. Set only for blockmount volumes. + * @return sizeInGb + */ + @javax.annotation.Nullable + public BigDecimal getSizeInGb() { + return sizeInGb; + } + + public void setSizeInGb(@javax.annotation.Nullable BigDecimal sizeInGb) { + this.sizeInGb = sizeInGb; + } + + + public SandboxVolume region(@javax.annotation.Nullable String region) { + this.region = region; + return this; + } + + /** + * The region the blockmount volume's data lives in. Forwarded to the runner so it can fetch the region's store credentials over its authenticated channel. Set only for blockmount volumes. + * @return region + */ + @javax.annotation.Nullable + public String getRegion() { + return region; + } + + public void setRegion(@javax.annotation.Nullable String region) { + this.region = region; + } + + + public SandboxVolume s3Endpoint(@javax.annotation.Nullable String s3Endpoint) { + this.s3Endpoint = s3Endpoint; + return this; + } + + /** + * The S3 endpoint of the CAS store the blockmount volume's data lives in, resolved from the volume's region. Forwarded to the runner so cross-region attaches reach the right bucket. Omitted when the volume's region has no store configured (runner falls back to its env store). Credentials are never sent here — the runner fetches them by region. Set only for blockmount volumes. + * @return s3Endpoint + */ + @javax.annotation.Nullable + public String getS3Endpoint() { + return s3Endpoint; + } + + public void setS3Endpoint(@javax.annotation.Nullable String s3Endpoint) { + this.s3Endpoint = s3Endpoint; + } + + + public SandboxVolume s3Region(@javax.annotation.Nullable String s3Region) { + this.s3Region = s3Region; + return this; + } + + /** + * The S3 region of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes. + * @return s3Region + */ + @javax.annotation.Nullable + public String getS3Region() { + return s3Region; + } + + public void setS3Region(@javax.annotation.Nullable String s3Region) { + this.s3Region = s3Region; + } + + + public SandboxVolume s3Bucket(@javax.annotation.Nullable String s3Bucket) { + this.s3Bucket = s3Bucket; + return this; + } + + /** + * The S3 bucket of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes. + * @return s3Bucket + */ + @javax.annotation.Nullable + public String getS3Bucket() { + return s3Bucket; + } + + public void setS3Bucket(@javax.annotation.Nullable String s3Bucket) { + this.s3Bucket = s3Bucket; + } + + + public SandboxVolume s3Prefix(@javax.annotation.Nullable String s3Prefix) { + this.s3Prefix = s3Prefix; + return this; + } + + /** + * The S3 key prefix of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes. + * @return s3Prefix + */ + @javax.annotation.Nullable + public String getS3Prefix() { + return s3Prefix; + } + + public void setS3Prefix(@javax.annotation.Nullable String s3Prefix) { + this.s3Prefix = s3Prefix; + } + + + public SandboxVolume s3PathStyle(@javax.annotation.Nullable Boolean s3PathStyle) { + this.s3PathStyle = s3PathStyle; + return this; + } + + /** + * Whether the CAS store uses path-style S3 addressing. Set only for blockmount volumes. + * @return s3PathStyle + */ + @javax.annotation.Nullable + public Boolean getS3PathStyle() { + return s3PathStyle; + } + + public void setS3PathStyle(@javax.annotation.Nullable Boolean s3PathStyle) { + this.s3PathStyle = s3PathStyle; + } + /** * A container for additional, undeclared properties. * This is a holder for any undeclared properties as specified with @@ -181,13 +399,22 @@ public boolean equals(Object o) { SandboxVolume sandboxVolume = (SandboxVolume) o; return Objects.equals(this.volumeId, sandboxVolume.volumeId) && Objects.equals(this.mountPath, sandboxVolume.mountPath) && - Objects.equals(this.subpath, sandboxVolume.subpath)&& + Objects.equals(this.subpath, sandboxVolume.subpath) && + Objects.equals(this.volumeType, sandboxVolume.volumeType) && + Objects.equals(this.organizationId, sandboxVolume.organizationId) && + Objects.equals(this.sizeInGb, sandboxVolume.sizeInGb) && + Objects.equals(this.region, sandboxVolume.region) && + Objects.equals(this.s3Endpoint, sandboxVolume.s3Endpoint) && + Objects.equals(this.s3Region, sandboxVolume.s3Region) && + Objects.equals(this.s3Bucket, sandboxVolume.s3Bucket) && + Objects.equals(this.s3Prefix, sandboxVolume.s3Prefix) && + Objects.equals(this.s3PathStyle, sandboxVolume.s3PathStyle)&& Objects.equals(this.additionalProperties, sandboxVolume.additionalProperties); } @Override public int hashCode() { - return Objects.hash(volumeId, mountPath, subpath, additionalProperties); + return Objects.hash(volumeId, mountPath, subpath, volumeType, organizationId, sizeInGb, region, s3Endpoint, s3Region, s3Bucket, s3Prefix, s3PathStyle, additionalProperties); } @Override @@ -197,6 +424,15 @@ public String toString() { sb.append(" volumeId: ").append(toIndentedString(volumeId)).append("\n"); sb.append(" mountPath: ").append(toIndentedString(mountPath)).append("\n"); sb.append(" subpath: ").append(toIndentedString(subpath)).append("\n"); + sb.append(" volumeType: ").append(toIndentedString(volumeType)).append("\n"); + sb.append(" organizationId: ").append(toIndentedString(organizationId)).append("\n"); + sb.append(" sizeInGb: ").append(toIndentedString(sizeInGb)).append("\n"); + sb.append(" region: ").append(toIndentedString(region)).append("\n"); + sb.append(" s3Endpoint: ").append(toIndentedString(s3Endpoint)).append("\n"); + sb.append(" s3Region: ").append(toIndentedString(s3Region)).append("\n"); + sb.append(" s3Bucket: ").append(toIndentedString(s3Bucket)).append("\n"); + sb.append(" s3Prefix: ").append(toIndentedString(s3Prefix)).append("\n"); + sb.append(" s3PathStyle: ").append(toIndentedString(s3PathStyle)).append("\n"); sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); sb.append("}"); return sb.toString(); @@ -216,7 +452,7 @@ private String toIndentedString(Object o) { static { // a set of all properties/fields (JSON key names) - openapiFields = new HashSet(Arrays.asList("volumeId", "mountPath", "subpath")); + openapiFields = new HashSet(Arrays.asList("volumeId", "mountPath", "subpath", "volumeType", "organizationId", "sizeInGb", "region", "s3Endpoint", "s3Region", "s3Bucket", "s3Prefix", "s3PathStyle")); // a set of required properties/fields (JSON key names) openapiRequiredFields = new HashSet(Arrays.asList("volumeId", "mountPath")); @@ -251,6 +487,28 @@ public static void validateJsonElement(JsonElement jsonElement) throws IOExcepti if ((jsonObj.get("subpath") != null && !jsonObj.get("subpath").isJsonNull()) && !jsonObj.get("subpath").isJsonPrimitive()) { throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `subpath` to be a primitive type in the JSON string but got `%s`", jsonObj.get("subpath").toString())); } + // validate the optional field `volumeType` + if (jsonObj.get("volumeType") != null && !jsonObj.get("volumeType").isJsonNull()) { + VolumeType.validateJsonElement(jsonObj.get("volumeType")); + } + if ((jsonObj.get("organizationId") != null && !jsonObj.get("organizationId").isJsonNull()) && !jsonObj.get("organizationId").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `organizationId` to be a primitive type in the JSON string but got `%s`", jsonObj.get("organizationId").toString())); + } + if ((jsonObj.get("region") != null && !jsonObj.get("region").isJsonNull()) && !jsonObj.get("region").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `region` to be a primitive type in the JSON string but got `%s`", jsonObj.get("region").toString())); + } + if ((jsonObj.get("s3Endpoint") != null && !jsonObj.get("s3Endpoint").isJsonNull()) && !jsonObj.get("s3Endpoint").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `s3Endpoint` to be a primitive type in the JSON string but got `%s`", jsonObj.get("s3Endpoint").toString())); + } + if ((jsonObj.get("s3Region") != null && !jsonObj.get("s3Region").isJsonNull()) && !jsonObj.get("s3Region").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `s3Region` to be a primitive type in the JSON string but got `%s`", jsonObj.get("s3Region").toString())); + } + if ((jsonObj.get("s3Bucket") != null && !jsonObj.get("s3Bucket").isJsonNull()) && !jsonObj.get("s3Bucket").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `s3Bucket` to be a primitive type in the JSON string but got `%s`", jsonObj.get("s3Bucket").toString())); + } + if ((jsonObj.get("s3Prefix") != null && !jsonObj.get("s3Prefix").isJsonNull()) && !jsonObj.get("s3Prefix").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `s3Prefix` to be a primitive type in the JSON string but got `%s`", jsonObj.get("s3Prefix").toString())); + } } public static class CustomTypeAdapterFactory implements TypeAdapterFactory { diff --git a/api-client-java/src/main/java/io/daytona/api/client/model/VolumeDto.java b/api-client-java/src/main/java/io/daytona/api/client/model/VolumeDto.java index 485035c28..15e9ff993 100644 --- a/api-client-java/src/main/java/io/daytona/api/client/model/VolumeDto.java +++ b/api-client-java/src/main/java/io/daytona/api/client/model/VolumeDto.java @@ -19,9 +19,14 @@ import com.google.gson.annotations.SerializedName; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import io.daytona.api.client.model.BlockmountConflict; import io.daytona.api.client.model.VolumeState; +import io.daytona.api.client.model.VolumeType; import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.openapitools.jackson.nullable.JsonNullable; import com.google.gson.Gson; @@ -67,6 +72,36 @@ public class VolumeDto { @javax.annotation.Nonnull private String organizationId; + public static final String SERIALIZED_NAME_TYPE = "type"; + @SerializedName(SERIALIZED_NAME_TYPE) + @javax.annotation.Nonnull + private VolumeType type; + + public static final String SERIALIZED_NAME_SIZE_IN_GB = "sizeInGb"; + @SerializedName(SERIALIZED_NAME_SIZE_IN_GB) + @javax.annotation.Nullable + private BigDecimal sizeInGb; + + public static final String SERIALIZED_NAME_REGION = "region"; + @SerializedName(SERIALIZED_NAME_REGION) + @javax.annotation.Nullable + private String region; + + public static final String SERIALIZED_NAME_SHARED = "shared"; + @SerializedName(SERIALIZED_NAME_SHARED) + @javax.annotation.Nullable + private Boolean shared; + + public static final String SERIALIZED_NAME_LAST_MANIFEST_ID = "lastManifestId"; + @SerializedName(SERIALIZED_NAME_LAST_MANIFEST_ID) + @javax.annotation.Nullable + private String lastManifestId; + + public static final String SERIALIZED_NAME_CONFLICTS = "conflicts"; + @SerializedName(SERIALIZED_NAME_CONFLICTS) + @javax.annotation.Nullable + private List conflicts; + public static final String SERIALIZED_NAME_STATE = "state"; @SerializedName(SERIALIZED_NAME_STATE) @javax.annotation.Nonnull @@ -152,6 +187,128 @@ public void setOrganizationId(@javax.annotation.Nonnull String organizationId) { } + public VolumeDto type(@javax.annotation.Nonnull VolumeType type) { + this.type = type; + return this; + } + + /** + * Volume type + * @return type + */ + @javax.annotation.Nonnull + public VolumeType getType() { + return type; + } + + public void setType(@javax.annotation.Nonnull VolumeType type) { + this.type = type; + } + + + public VolumeDto sizeInGb(@javax.annotation.Nullable BigDecimal sizeInGb) { + this.sizeInGb = sizeInGb; + return this; + } + + /** + * The per-sandbox scratch quota in GB. Set only for blockmount volumes. + * @return sizeInGb + */ + @javax.annotation.Nullable + public BigDecimal getSizeInGb() { + return sizeInGb; + } + + public void setSizeInGb(@javax.annotation.Nullable BigDecimal sizeInGb) { + this.sizeInGb = sizeInGb; + } + + + public VolumeDto region(@javax.annotation.Nullable String region) { + this.region = region; + return this; + } + + /** + * The region the volume's data lives in. For blockmount volumes this selects the region-local CAS store (a performance/placement knob — sandboxes in any region can attach it, colocation is just faster). For hotmount volumes this is the hotmount deployment region. Set for blockmount and hotmount volumes. + * @return region + */ + @javax.annotation.Nullable + public String getRegion() { + return region; + } + + public void setRegion(@javax.annotation.Nullable String region) { + this.region = region; + } + + + public VolumeDto shared(@javax.annotation.Nullable Boolean shared) { + this.shared = shared; + return this; + } + + /** + * The hotmount sharing mode (false = single-writer write-back, true = multi-writer synchronous). Set only for hotmount volumes. + * @return shared + */ + @javax.annotation.Nullable + public Boolean getShared() { + return shared; + } + + public void setShared(@javax.annotation.Nullable Boolean shared) { + this.shared = shared; + } + + + public VolumeDto lastManifestId(@javax.annotation.Nullable String lastManifestId) { + this.lastManifestId = lastManifestId; + return this; + } + + /** + * The id of the most recent committed manifest, read-through from the reconciliation store. Set only for blockmount volumes that have been committed at least once. + * @return lastManifestId + */ + @javax.annotation.Nullable + public String getLastManifestId() { + return lastManifestId; + } + + public void setLastManifestId(@javax.annotation.Nullable String lastManifestId) { + this.lastManifestId = lastManifestId; + } + + + public VolumeDto conflicts(@javax.annotation.Nullable List conflicts) { + this.conflicts = conflicts; + return this; + } + + public VolumeDto addConflictsItem(BlockmountConflict conflictsItem) { + if (this.conflicts == null) { + this.conflicts = new ArrayList<>(); + } + this.conflicts.add(conflictsItem); + return this; + } + + /** + * Conflicts recorded on the latest manifest — concurrent same-path modifications the store resolved (last-change-wins). Read-through from the store. Set only for blockmount volumes. + * @return conflicts + */ + @javax.annotation.Nullable + public List getConflicts() { + return conflicts; + } + + public void setConflicts(@javax.annotation.Nullable List conflicts) { + this.conflicts = conflicts; + } + + public VolumeDto state(@javax.annotation.Nonnull VolumeState state) { this.state = state; return this; @@ -304,6 +461,12 @@ public boolean equals(Object o) { return Objects.equals(this.id, volumeDto.id) && Objects.equals(this.name, volumeDto.name) && Objects.equals(this.organizationId, volumeDto.organizationId) && + Objects.equals(this.type, volumeDto.type) && + Objects.equals(this.sizeInGb, volumeDto.sizeInGb) && + Objects.equals(this.region, volumeDto.region) && + Objects.equals(this.shared, volumeDto.shared) && + Objects.equals(this.lastManifestId, volumeDto.lastManifestId) && + Objects.equals(this.conflicts, volumeDto.conflicts) && Objects.equals(this.state, volumeDto.state) && Objects.equals(this.createdAt, volumeDto.createdAt) && Objects.equals(this.updatedAt, volumeDto.updatedAt) && @@ -318,7 +481,7 @@ private static boolean equalsNullable(JsonNullable a, JsonNullable b) @Override public int hashCode() { - return Objects.hash(id, name, organizationId, state, createdAt, updatedAt, lastUsedAt, errorReason, additionalProperties); + return Objects.hash(id, name, organizationId, type, sizeInGb, region, shared, lastManifestId, conflicts, state, createdAt, updatedAt, lastUsedAt, errorReason, additionalProperties); } private static int hashCodeNullable(JsonNullable a) { @@ -335,6 +498,12 @@ public String toString() { sb.append(" id: ").append(toIndentedString(id)).append("\n"); sb.append(" name: ").append(toIndentedString(name)).append("\n"); sb.append(" organizationId: ").append(toIndentedString(organizationId)).append("\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" sizeInGb: ").append(toIndentedString(sizeInGb)).append("\n"); + sb.append(" region: ").append(toIndentedString(region)).append("\n"); + sb.append(" shared: ").append(toIndentedString(shared)).append("\n"); + sb.append(" lastManifestId: ").append(toIndentedString(lastManifestId)).append("\n"); + sb.append(" conflicts: ").append(toIndentedString(conflicts)).append("\n"); sb.append(" state: ").append(toIndentedString(state)).append("\n"); sb.append(" createdAt: ").append(toIndentedString(createdAt)).append("\n"); sb.append(" updatedAt: ").append(toIndentedString(updatedAt)).append("\n"); @@ -359,10 +528,10 @@ private String toIndentedString(Object o) { static { // a set of all properties/fields (JSON key names) - openapiFields = new HashSet(Arrays.asList("id", "name", "organizationId", "state", "createdAt", "updatedAt", "lastUsedAt", "errorReason")); + openapiFields = new HashSet(Arrays.asList("id", "name", "organizationId", "type", "sizeInGb", "region", "shared", "lastManifestId", "conflicts", "state", "createdAt", "updatedAt", "lastUsedAt", "errorReason")); // a set of required properties/fields (JSON key names) - openapiRequiredFields = new HashSet(Arrays.asList("id", "name", "organizationId", "state", "createdAt", "updatedAt", "errorReason")); + openapiRequiredFields = new HashSet(Arrays.asList("id", "name", "organizationId", "type", "state", "createdAt", "updatedAt", "errorReason")); } /** @@ -394,6 +563,28 @@ public static void validateJsonElement(JsonElement jsonElement) throws IOExcepti if (!jsonObj.get("organizationId").isJsonPrimitive()) { throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `organizationId` to be a primitive type in the JSON string but got `%s`", jsonObj.get("organizationId").toString())); } + // validate the required field `type` + VolumeType.validateJsonElement(jsonObj.get("type")); + if ((jsonObj.get("region") != null && !jsonObj.get("region").isJsonNull()) && !jsonObj.get("region").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `region` to be a primitive type in the JSON string but got `%s`", jsonObj.get("region").toString())); + } + if ((jsonObj.get("lastManifestId") != null && !jsonObj.get("lastManifestId").isJsonNull()) && !jsonObj.get("lastManifestId").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `lastManifestId` to be a primitive type in the JSON string but got `%s`", jsonObj.get("lastManifestId").toString())); + } + if (jsonObj.get("conflicts") != null && !jsonObj.get("conflicts").isJsonNull()) { + JsonArray jsonArrayconflicts = jsonObj.getAsJsonArray("conflicts"); + if (jsonArrayconflicts != null) { + // ensure the json data is an array + if (!jsonObj.get("conflicts").isJsonArray()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `conflicts` to be an array in the JSON string but got `%s`", jsonObj.get("conflicts").toString())); + } + + // validate the optional field `conflicts` (array) + for (int i = 0; i < jsonArrayconflicts.size(); i++) { + BlockmountConflict.validateJsonElement(jsonArrayconflicts.get(i)); + }; + } + } // validate the required field `state` VolumeState.validateJsonElement(jsonObj.get("state")); if (!jsonObj.get("createdAt").isJsonPrimitive()) { diff --git a/api-client-java/src/main/java/io/daytona/api/client/model/VolumeMountTokenDto.java b/api-client-java/src/main/java/io/daytona/api/client/model/VolumeMountTokenDto.java new file mode 100644 index 000000000..ab67a8f76 --- /dev/null +++ b/api-client-java/src/main/java/io/daytona/api/client/model/VolumeMountTokenDto.java @@ -0,0 +1,476 @@ +/* + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.daytona.api.client.model; + +import java.util.Objects; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.Arrays; +import org.openapitools.jackson.nullable.JsonNullable; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.daytona.api.client.JSON; + +/** + * VolumeMountTokenDto + */ +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.21.0") +public class VolumeMountTokenDto { + public static final String SERIALIZED_NAME_TOKEN = "token"; + @SerializedName(SERIALIZED_NAME_TOKEN) + @javax.annotation.Nonnull + private String token; + + public static final String SERIALIZED_NAME_EXPIRES_AT = "expiresAt"; + @SerializedName(SERIALIZED_NAME_EXPIRES_AT) + @javax.annotation.Nonnull + private String expiresAt; + + public static final String SERIALIZED_NAME_REGION = "region"; + @SerializedName(SERIALIZED_NAME_REGION) + @javax.annotation.Nonnull + private String region; + + public static final String SERIALIZED_NAME_GATEWAY_GRPC = "gatewayGrpc"; + @SerializedName(SERIALIZED_NAME_GATEWAY_GRPC) + @javax.annotation.Nonnull + private String gatewayGrpc; + + public static final String SERIALIZED_NAME_GATEWAY_HTTP = "gatewayHttp"; + @SerializedName(SERIALIZED_NAME_GATEWAY_HTTP) + @javax.annotation.Nonnull + private String gatewayHttp; + + public static final String SERIALIZED_NAME_BINARIES_URL = "binariesUrl"; + @SerializedName(SERIALIZED_NAME_BINARIES_URL) + @javax.annotation.Nonnull + private String binariesUrl; + + public static final String SERIALIZED_NAME_VERSION = "version"; + @SerializedName(SERIALIZED_NAME_VERSION) + @javax.annotation.Nullable + private String version; + + public VolumeMountTokenDto() { + } + + public VolumeMountTokenDto token(@javax.annotation.Nonnull String token) { + this.token = token; + return this; + } + + /** + * The short-lived macaroon token the in-sandbox agent uses to mount the volume + * @return token + */ + @javax.annotation.Nonnull + public String getToken() { + return token; + } + + public void setToken(@javax.annotation.Nonnull String token) { + this.token = token; + } + + + public VolumeMountTokenDto expiresAt(@javax.annotation.Nonnull String expiresAt) { + this.expiresAt = expiresAt; + return this; + } + + /** + * The token expiration timestamp + * @return expiresAt + */ + @javax.annotation.Nonnull + public String getExpiresAt() { + return expiresAt; + } + + public void setExpiresAt(@javax.annotation.Nonnull String expiresAt) { + this.expiresAt = expiresAt; + } + + + public VolumeMountTokenDto region(@javax.annotation.Nonnull String region) { + this.region = region; + return this; + } + + /** + * The hotmount region the volume lives in + * @return region + */ + @javax.annotation.Nonnull + public String getRegion() { + return region; + } + + public void setRegion(@javax.annotation.Nonnull String region) { + this.region = region; + } + + + public VolumeMountTokenDto gatewayGrpc(@javax.annotation.Nonnull String gatewayGrpc) { + this.gatewayGrpc = gatewayGrpc; + return this; + } + + /** + * The gateway gRPC endpoint (SEAWEED_GATEWAY_GRPC) + * @return gatewayGrpc + */ + @javax.annotation.Nonnull + public String getGatewayGrpc() { + return gatewayGrpc; + } + + public void setGatewayGrpc(@javax.annotation.Nonnull String gatewayGrpc) { + this.gatewayGrpc = gatewayGrpc; + } + + + public VolumeMountTokenDto gatewayHttp(@javax.annotation.Nonnull String gatewayHttp) { + this.gatewayHttp = gatewayHttp; + return this; + } + + /** + * The gateway HTTPS endpoint (SEAWEED_GATEWAY_HTTP) + * @return gatewayHttp + */ + @javax.annotation.Nonnull + public String getGatewayHttp() { + return gatewayHttp; + } + + public void setGatewayHttp(@javax.annotation.Nonnull String gatewayHttp) { + this.gatewayHttp = gatewayHttp; + } + + + public VolumeMountTokenDto binariesUrl(@javax.annotation.Nonnull String binariesUrl) { + this.binariesUrl = binariesUrl; + return this; + } + + /** + * The binaries bucket base URL used to bootstrap the mount (SEAWEED_BINARIES_URL) + * @return binariesUrl + */ + @javax.annotation.Nonnull + public String getBinariesUrl() { + return binariesUrl; + } + + public void setBinariesUrl(@javax.annotation.Nonnull String binariesUrl) { + this.binariesUrl = binariesUrl; + } + + + public VolumeMountTokenDto version(@javax.annotation.Nullable String version) { + this.version = version; + return this; + } + + /** + * The pinned client binary version to use (SEAWEED_VERSION), when the region pins one + * @return version + */ + @javax.annotation.Nullable + public String getVersion() { + return version; + } + + public void setVersion(@javax.annotation.Nullable String version) { + this.version = version; + } + + /** + * A container for additional, undeclared properties. + * This is a holder for any undeclared properties as specified with + * the 'additionalProperties' keyword in the OAS document. + */ + private Map additionalProperties; + + /** + * Set the additional (undeclared) property with the specified name and value. + * If the property does not already exist, create it otherwise replace it. + * + * @param key name of the property + * @param value value of the property + * @return the VolumeMountTokenDto instance itself + */ + public VolumeMountTokenDto putAdditionalProperty(String key, Object value) { + if (this.additionalProperties == null) { + this.additionalProperties = new HashMap(); + } + this.additionalProperties.put(key, value); + return this; + } + + /** + * Return the additional (undeclared) property. + * + * @return a map of objects + */ + public Map getAdditionalProperties() { + return additionalProperties; + } + + /** + * Return the additional (undeclared) property with the specified name. + * + * @param key name of the property + * @return an object + */ + public Object getAdditionalProperty(String key) { + if (this.additionalProperties == null) { + return null; + } + return this.additionalProperties.get(key); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VolumeMountTokenDto volumeMountTokenDto = (VolumeMountTokenDto) o; + return Objects.equals(this.token, volumeMountTokenDto.token) && + Objects.equals(this.expiresAt, volumeMountTokenDto.expiresAt) && + Objects.equals(this.region, volumeMountTokenDto.region) && + Objects.equals(this.gatewayGrpc, volumeMountTokenDto.gatewayGrpc) && + Objects.equals(this.gatewayHttp, volumeMountTokenDto.gatewayHttp) && + Objects.equals(this.binariesUrl, volumeMountTokenDto.binariesUrl) && + Objects.equals(this.version, volumeMountTokenDto.version)&& + Objects.equals(this.additionalProperties, volumeMountTokenDto.additionalProperties); + } + + private static boolean equalsNullable(JsonNullable a, JsonNullable b) { + return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get())); + } + + @Override + public int hashCode() { + return Objects.hash(token, expiresAt, region, gatewayGrpc, gatewayHttp, binariesUrl, version, additionalProperties); + } + + private static int hashCodeNullable(JsonNullable a) { + if (a == null) { + return 1; + } + return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class VolumeMountTokenDto {\n"); + sb.append(" token: ").append(toIndentedString(token)).append("\n"); + sb.append(" expiresAt: ").append(toIndentedString(expiresAt)).append("\n"); + sb.append(" region: ").append(toIndentedString(region)).append("\n"); + sb.append(" gatewayGrpc: ").append(toIndentedString(gatewayGrpc)).append("\n"); + sb.append(" gatewayHttp: ").append(toIndentedString(gatewayHttp)).append("\n"); + sb.append(" binariesUrl: ").append(toIndentedString(binariesUrl)).append("\n"); + sb.append(" version: ").append(toIndentedString(version)).append("\n"); + sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + return o == null ? "null" : o.toString().replace("\n", "\n "); + } + + + public static HashSet openapiFields; + public static HashSet openapiRequiredFields; + + static { + // a set of all properties/fields (JSON key names) + openapiFields = new HashSet(Arrays.asList("token", "expiresAt", "region", "gatewayGrpc", "gatewayHttp", "binariesUrl", "version")); + + // a set of required properties/fields (JSON key names) + openapiRequiredFields = new HashSet(Arrays.asList("token", "expiresAt", "region", "gatewayGrpc", "gatewayHttp", "binariesUrl")); + } + + /** + * Validates the JSON Element and throws an exception if issues found + * + * @param jsonElement JSON Element + * @throws IOException if the JSON Element is invalid with respect to VolumeMountTokenDto + */ + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + if (jsonElement == null) { + if (!VolumeMountTokenDto.openapiRequiredFields.isEmpty()) { // has required fields but JSON element is null + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The required field(s) %s in VolumeMountTokenDto is not found in the empty JSON string", VolumeMountTokenDto.openapiRequiredFields.toString())); + } + } + + // check to make sure all required properties/fields are present in the JSON string + for (String requiredField : VolumeMountTokenDto.openapiRequiredFields) { + if (jsonElement.getAsJsonObject().get(requiredField) == null) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The required field `%s` is not found in the JSON string: %s", requiredField, jsonElement.toString())); + } + } + JsonObject jsonObj = jsonElement.getAsJsonObject(); + if (!jsonObj.get("token").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `token` to be a primitive type in the JSON string but got `%s`", jsonObj.get("token").toString())); + } + if (!jsonObj.get("expiresAt").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `expiresAt` to be a primitive type in the JSON string but got `%s`", jsonObj.get("expiresAt").toString())); + } + if (!jsonObj.get("region").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `region` to be a primitive type in the JSON string but got `%s`", jsonObj.get("region").toString())); + } + if (!jsonObj.get("gatewayGrpc").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `gatewayGrpc` to be a primitive type in the JSON string but got `%s`", jsonObj.get("gatewayGrpc").toString())); + } + if (!jsonObj.get("gatewayHttp").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `gatewayHttp` to be a primitive type in the JSON string but got `%s`", jsonObj.get("gatewayHttp").toString())); + } + if (!jsonObj.get("binariesUrl").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `binariesUrl` to be a primitive type in the JSON string but got `%s`", jsonObj.get("binariesUrl").toString())); + } + if ((jsonObj.get("version") != null && !jsonObj.get("version").isJsonNull()) && !jsonObj.get("version").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Expected the field `version` to be a primitive type in the JSON string but got `%s`", jsonObj.get("version").toString())); + } + } + + public static class CustomTypeAdapterFactory implements TypeAdapterFactory { + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (!VolumeMountTokenDto.class.isAssignableFrom(type.getRawType())) { + return null; // this class only serializes 'VolumeMountTokenDto' and its subtypes + } + final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); + final TypeAdapter thisAdapter + = gson.getDelegateAdapter(this, TypeToken.get(VolumeMountTokenDto.class)); + + return (TypeAdapter) new TypeAdapter() { + @Override + public void write(JsonWriter out, VolumeMountTokenDto value) throws IOException { + JsonObject obj = thisAdapter.toJsonTree(value).getAsJsonObject(); + obj.remove("additionalProperties"); + // serialize additional properties + if (value.getAdditionalProperties() != null) { + for (Map.Entry entry : value.getAdditionalProperties().entrySet()) { + if (entry.getValue() instanceof String) + obj.addProperty(entry.getKey(), (String) entry.getValue()); + else if (entry.getValue() instanceof Number) + obj.addProperty(entry.getKey(), (Number) entry.getValue()); + else if (entry.getValue() instanceof Boolean) + obj.addProperty(entry.getKey(), (Boolean) entry.getValue()); + else if (entry.getValue() instanceof Character) + obj.addProperty(entry.getKey(), (Character) entry.getValue()); + else { + JsonElement jsonElement = gson.toJsonTree(entry.getValue()); + if (jsonElement.isJsonArray()) { + obj.add(entry.getKey(), jsonElement.getAsJsonArray()); + } else { + obj.add(entry.getKey(), jsonElement.getAsJsonObject()); + } + } + } + } + elementAdapter.write(out, obj); + } + + @Override + public VolumeMountTokenDto read(JsonReader in) throws IOException { + JsonElement jsonElement = elementAdapter.read(in); + validateJsonElement(jsonElement); + JsonObject jsonObj = jsonElement.getAsJsonObject(); + // store additional fields in the deserialized instance + VolumeMountTokenDto instance = thisAdapter.fromJsonTree(jsonObj); + for (Map.Entry entry : jsonObj.entrySet()) { + if (!openapiFields.contains(entry.getKey())) { + if (entry.getValue().isJsonPrimitive()) { // primitive type + if (entry.getValue().getAsJsonPrimitive().isString()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsString()); + else if (entry.getValue().getAsJsonPrimitive().isNumber()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsNumber()); + else if (entry.getValue().getAsJsonPrimitive().isBoolean()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsBoolean()); + else + throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "The field `%s` has unknown primitive type. Value: %s", entry.getKey(), entry.getValue().toString())); + } else if (entry.getValue().isJsonArray()) { + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), List.class)); + } else { // JSON object + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), HashMap.class)); + } + } + } + return instance; + } + + }.nullSafe(); + } + } + + /** + * Create an instance of VolumeMountTokenDto given an JSON string + * + * @param jsonString JSON string + * @return An instance of VolumeMountTokenDto + * @throws IOException if the JSON string is invalid with respect to VolumeMountTokenDto + */ + public static VolumeMountTokenDto fromJson(String jsonString) throws IOException { + return JSON.getGson().fromJson(jsonString, VolumeMountTokenDto.class); + } + + /** + * Convert an instance of VolumeMountTokenDto to an JSON string + * + * @return JSON string + */ + public String toJson() { + return JSON.getGson().toJson(this); + } +} + diff --git a/api-client-java/src/main/java/io/daytona/api/client/model/VolumeType.java b/api-client-java/src/main/java/io/daytona/api/client/model/VolumeType.java new file mode 100644 index 000000000..e0047136f --- /dev/null +++ b/api-client-java/src/main/java/io/daytona/api/client/model/VolumeType.java @@ -0,0 +1,82 @@ +/* + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.daytona.api.client.model; + +import java.util.Objects; +import com.google.gson.annotations.SerializedName; + +import java.io.IOException; +import com.google.gson.TypeAdapter; +import com.google.gson.JsonElement; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy. + */ +@JsonAdapter(VolumeType.Adapter.class) +public enum VolumeType { + + LEGACY("legacy"), + + HOTMOUNT("hotmount"), + + BLOCKMOUNT("blockmount"), + + UNKNOWN_DEFAULT_OPEN_API("unknown_default_open_api"); + + private String value; + + VolumeType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static VolumeType fromValue(String value) { + for (VolumeType b : VolumeType.values()) { + if (b.value.equals(value)) { + return b; + } + } + return UNKNOWN_DEFAULT_OPEN_API; + } + + public static class Adapter extends TypeAdapter { + @Override + public void write(final JsonWriter jsonWriter, final VolumeType enumeration) throws IOException { + jsonWriter.value(enumeration.getValue()); + } + + @Override + public VolumeType read(final JsonReader jsonReader) throws IOException { + String value = jsonReader.nextString(); + return VolumeType.fromValue(value); + } + } + + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + String value = jsonElement.getAsString(); + VolumeType.fromValue(value); + } +} + diff --git a/api-client-java/src/test/java/io/daytona/api/client/api/VolumesApiTest.java b/api-client-java/src/test/java/io/daytona/api/client/api/VolumesApiTest.java index b8a4516ea..8ebe12f76 100644 --- a/api-client-java/src/test/java/io/daytona/api/client/api/VolumesApiTest.java +++ b/api-client-java/src/test/java/io/daytona/api/client/api/VolumesApiTest.java @@ -15,7 +15,11 @@ import io.daytona.api.client.ApiException; import io.daytona.api.client.model.CreateVolume; +import io.daytona.api.client.model.CreateVolumeMountToken; +import io.daytona.api.client.model.HotmountRegion; +import io.daytona.api.client.model.Region; import io.daytona.api.client.model.VolumeDto; +import io.daytona.api.client.model.VolumeMountTokenDto; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -45,6 +49,20 @@ public void createVolumeTest() throws ApiException { // TODO: test validations } + /** + * Create a mount token for a hotmount volume + * + * @throws ApiException if the Api call fails + */ + @Test + public void createVolumeMountTokenTest() throws ApiException { + String volumeId = null; + String xDaytonaOrganizationID = null; + CreateVolumeMountToken createVolumeMountToken = null; + VolumeMountTokenDto response = api.createVolumeMountToken(volumeId, xDaytonaOrganizationID, createVolumeMountToken); + // TODO: test validations + } + /** * Delete volume * @@ -84,6 +102,30 @@ public void getVolumeByNameTest() throws ApiException { // TODO: test validations } + /** + * List regions where blockmount volumes can be created + * + * @throws ApiException if the Api call fails + */ + @Test + public void listBlockmountRegionsTest() throws ApiException { + String xDaytonaOrganizationID = null; + List response = api.listBlockmountRegions(xDaytonaOrganizationID); + // TODO: test validations + } + + /** + * List available hotmount regions + * + * @throws ApiException if the Api call fails + */ + @Test + public void listHotmountRegionsTest() throws ApiException { + String xDaytonaOrganizationID = null; + List response = api.listHotmountRegions(xDaytonaOrganizationID); + // TODO: test validations + } + /** * List all volumes * diff --git a/api-client-java/src/test/java/io/daytona/api/client/model/BlockmountConflictTest.java b/api-client-java/src/test/java/io/daytona/api/client/model/BlockmountConflictTest.java new file mode 100644 index 000000000..af6e67d0a --- /dev/null +++ b/api-client-java/src/test/java/io/daytona/api/client/model/BlockmountConflictTest.java @@ -0,0 +1,80 @@ +/* + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.daytona.api.client.model; + +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.Arrays; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Model tests for BlockmountConflict + */ +public class BlockmountConflictTest { + private final BlockmountConflict model = new BlockmountConflict(); + + /** + * Model tests for BlockmountConflict + */ + @Test + public void testBlockmountConflict() { + // TODO: test BlockmountConflict + } + + /** + * Test the property 'path' + */ + @Test + public void pathTest() { + // TODO: test path + } + + /** + * Test the property 'winner' + */ + @Test + public void winnerTest() { + // TODO: test winner + } + + /** + * Test the property 'reason' + */ + @Test + public void reasonTest() { + // TODO: test reason + } + + /** + * Test the property 'oursSha' + */ + @Test + public void oursShaTest() { + // TODO: test oursSha + } + + /** + * Test the property 'theirsSha' + */ + @Test + public void theirsShaTest() { + // TODO: test theirsSha + } + +} diff --git a/api-client-java/src/test/java/io/daytona/api/client/model/CreateVolumeMountTokenTest.java b/api-client-java/src/test/java/io/daytona/api/client/model/CreateVolumeMountTokenTest.java new file mode 100644 index 000000000..8885bb0e3 --- /dev/null +++ b/api-client-java/src/test/java/io/daytona/api/client/model/CreateVolumeMountTokenTest.java @@ -0,0 +1,48 @@ +/* + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.daytona.api.client.model; + +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.Arrays; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Model tests for CreateVolumeMountToken + */ +public class CreateVolumeMountTokenTest { + private final CreateVolumeMountToken model = new CreateVolumeMountToken(); + + /** + * Model tests for CreateVolumeMountToken + */ + @Test + public void testCreateVolumeMountToken() { + // TODO: test CreateVolumeMountToken + } + + /** + * Test the property 'mode' + */ + @Test + public void modeTest() { + // TODO: test mode + } + +} diff --git a/api-client-java/src/test/java/io/daytona/api/client/model/CreateVolumeTest.java b/api-client-java/src/test/java/io/daytona/api/client/model/CreateVolumeTest.java index 3de5294a6..0ce4ae170 100644 --- a/api-client-java/src/test/java/io/daytona/api/client/model/CreateVolumeTest.java +++ b/api-client-java/src/test/java/io/daytona/api/client/model/CreateVolumeTest.java @@ -18,6 +18,7 @@ import com.google.gson.annotations.SerializedName; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import io.daytona.api.client.model.VolumeType; import java.io.IOException; import java.util.Arrays; import org.junit.jupiter.api.Disabled; @@ -45,4 +46,20 @@ public void nameTest() { // TODO: test name } + /** + * Test the property 'type' + */ + @Test + public void typeTest() { + // TODO: test type + } + + /** + * Test the property 'region' + */ + @Test + public void regionTest() { + // TODO: test region + } + } diff --git a/api-client-java/src/test/java/io/daytona/api/client/model/HotmountRegionTest.java b/api-client-java/src/test/java/io/daytona/api/client/model/HotmountRegionTest.java new file mode 100644 index 000000000..9b6e17090 --- /dev/null +++ b/api-client-java/src/test/java/io/daytona/api/client/model/HotmountRegionTest.java @@ -0,0 +1,64 @@ +/* + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.daytona.api.client.model; + +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.Arrays; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Model tests for HotmountRegion + */ +public class HotmountRegionTest { + private final HotmountRegion model = new HotmountRegion(); + + /** + * Model tests for HotmountRegion + */ + @Test + public void testHotmountRegion() { + // TODO: test HotmountRegion + } + + /** + * Test the property 'region' + */ + @Test + public void regionTest() { + // TODO: test region + } + + /** + * Test the property 'label' + */ + @Test + public void labelTest() { + // TODO: test label + } + + /** + * Test the property 'geo' + */ + @Test + public void geoTest() { + // TODO: test geo + } + +} diff --git a/api-client-java/src/test/java/io/daytona/api/client/model/RegionTest.java b/api-client-java/src/test/java/io/daytona/api/client/model/RegionTest.java index 111e74307..89e45e3a9 100644 --- a/api-client-java/src/test/java/io/daytona/api/client/model/RegionTest.java +++ b/api-client-java/src/test/java/io/daytona/api/client/model/RegionTest.java @@ -111,4 +111,12 @@ public void snapshotManagerUrlTest() { // TODO: test snapshotManagerUrl } + /** + * Test the property 'blockmountEnabled' + */ + @Test + public void blockmountEnabledTest() { + // TODO: test blockmountEnabled + } + } diff --git a/api-client-java/src/test/java/io/daytona/api/client/model/SandboxVolumeTest.java b/api-client-java/src/test/java/io/daytona/api/client/model/SandboxVolumeTest.java index 658eb4a69..12aaf5331 100644 --- a/api-client-java/src/test/java/io/daytona/api/client/model/SandboxVolumeTest.java +++ b/api-client-java/src/test/java/io/daytona/api/client/model/SandboxVolumeTest.java @@ -18,7 +18,9 @@ import com.google.gson.annotations.SerializedName; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import io.daytona.api.client.model.VolumeType; import java.io.IOException; +import java.math.BigDecimal; import java.util.Arrays; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -61,4 +63,76 @@ public void subpathTest() { // TODO: test subpath } + /** + * Test the property 'volumeType' + */ + @Test + public void volumeTypeTest() { + // TODO: test volumeType + } + + /** + * Test the property 'organizationId' + */ + @Test + public void organizationIdTest() { + // TODO: test organizationId + } + + /** + * Test the property 'sizeInGb' + */ + @Test + public void sizeInGbTest() { + // TODO: test sizeInGb + } + + /** + * Test the property 'region' + */ + @Test + public void regionTest() { + // TODO: test region + } + + /** + * Test the property 's3Endpoint' + */ + @Test + public void s3EndpointTest() { + // TODO: test s3Endpoint + } + + /** + * Test the property 's3Region' + */ + @Test + public void s3RegionTest() { + // TODO: test s3Region + } + + /** + * Test the property 's3Bucket' + */ + @Test + public void s3BucketTest() { + // TODO: test s3Bucket + } + + /** + * Test the property 's3Prefix' + */ + @Test + public void s3PrefixTest() { + // TODO: test s3Prefix + } + + /** + * Test the property 's3PathStyle' + */ + @Test + public void s3PathStyleTest() { + // TODO: test s3PathStyle + } + } diff --git a/api-client-java/src/test/java/io/daytona/api/client/model/VolumeDtoTest.java b/api-client-java/src/test/java/io/daytona/api/client/model/VolumeDtoTest.java index 35a3f2c57..18f0be4bb 100644 --- a/api-client-java/src/test/java/io/daytona/api/client/model/VolumeDtoTest.java +++ b/api-client-java/src/test/java/io/daytona/api/client/model/VolumeDtoTest.java @@ -18,9 +18,14 @@ import com.google.gson.annotations.SerializedName; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import io.daytona.api.client.model.BlockmountConflict; import io.daytona.api.client.model.VolumeState; +import io.daytona.api.client.model.VolumeType; import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.openapitools.jackson.nullable.JsonNullable; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -63,6 +68,54 @@ public void organizationIdTest() { // TODO: test organizationId } + /** + * Test the property 'type' + */ + @Test + public void typeTest() { + // TODO: test type + } + + /** + * Test the property 'sizeInGb' + */ + @Test + public void sizeInGbTest() { + // TODO: test sizeInGb + } + + /** + * Test the property 'region' + */ + @Test + public void regionTest() { + // TODO: test region + } + + /** + * Test the property 'shared' + */ + @Test + public void sharedTest() { + // TODO: test shared + } + + /** + * Test the property 'lastManifestId' + */ + @Test + public void lastManifestIdTest() { + // TODO: test lastManifestId + } + + /** + * Test the property 'conflicts' + */ + @Test + public void conflictsTest() { + // TODO: test conflicts + } + /** * Test the property 'state' */ diff --git a/api-client-java/src/test/java/io/daytona/api/client/model/VolumeMountTokenDtoTest.java b/api-client-java/src/test/java/io/daytona/api/client/model/VolumeMountTokenDtoTest.java new file mode 100644 index 000000000..7fdeb5c44 --- /dev/null +++ b/api-client-java/src/test/java/io/daytona/api/client/model/VolumeMountTokenDtoTest.java @@ -0,0 +1,97 @@ +/* + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.daytona.api.client.model; + +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.Arrays; +import org.openapitools.jackson.nullable.JsonNullable; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Model tests for VolumeMountTokenDto + */ +public class VolumeMountTokenDtoTest { + private final VolumeMountTokenDto model = new VolumeMountTokenDto(); + + /** + * Model tests for VolumeMountTokenDto + */ + @Test + public void testVolumeMountTokenDto() { + // TODO: test VolumeMountTokenDto + } + + /** + * Test the property 'token' + */ + @Test + public void tokenTest() { + // TODO: test token + } + + /** + * Test the property 'expiresAt' + */ + @Test + public void expiresAtTest() { + // TODO: test expiresAt + } + + /** + * Test the property 'region' + */ + @Test + public void regionTest() { + // TODO: test region + } + + /** + * Test the property 'gatewayGrpc' + */ + @Test + public void gatewayGrpcTest() { + // TODO: test gatewayGrpc + } + + /** + * Test the property 'gatewayHttp' + */ + @Test + public void gatewayHttpTest() { + // TODO: test gatewayHttp + } + + /** + * Test the property 'binariesUrl' + */ + @Test + public void binariesUrlTest() { + // TODO: test binariesUrl + } + + /** + * Test the property 'version' + */ + @Test + public void versionTest() { + // TODO: test version + } + +} diff --git a/api-client-java/src/test/java/io/daytona/api/client/model/VolumeTypeTest.java b/api-client-java/src/test/java/io/daytona/api/client/model/VolumeTypeTest.java new file mode 100644 index 000000000..57b081a01 --- /dev/null +++ b/api-client-java/src/test/java/io/daytona/api/client/model/VolumeTypeTest.java @@ -0,0 +1,32 @@ +/* + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.daytona.api.client.model; + +import com.google.gson.annotations.SerializedName; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Model tests for VolumeType + */ +public class VolumeTypeTest { + /** + * Model tests for VolumeType + */ + @Test + public void testVolumeType() { + // TODO: test VolumeType + } + +} diff --git a/api-client-python-async/.openapi-generator/FILES b/api-client-python-async/.openapi-generator/FILES index 447c97209..0214d4b79 100644 --- a/api-client-python-async/.openapi-generator/FILES +++ b/api-client-python-async/.openapi-generator/FILES @@ -34,6 +34,7 @@ daytona_api_client_async/models/api_key_list.py daytona_api_client_async/models/api_key_response.py daytona_api_client_async/models/audit_log.py daytona_api_client_async/models/available_sandbox_class.py +daytona_api_client_async/models/blockmount_conflict.py daytona_api_client_async/models/build_info.py daytona_api_client_async/models/command.py daytona_api_client_async/models/completion_context.py @@ -63,6 +64,7 @@ daytona_api_client_async/models/create_session_request.py daytona_api_client_async/models/create_snapshot.py daytona_api_client_async/models/create_user.py daytona_api_client_async/models/create_volume.py +daytona_api_client_async/models/create_volume_mount_token.py daytona_api_client_async/models/date_filter.py daytona_api_client_async/models/daytona_configuration.py daytona_api_client_async/models/display_info_response.py @@ -87,6 +89,7 @@ daytona_api_client_async/models/gpu_type.py daytona_api_client_async/models/health_controller_check200_response.py daytona_api_client_async/models/health_controller_check200_response_info_value.py daytona_api_client_async/models/health_controller_check503_response.py +daytona_api_client_async/models/hotmount_region.py daytona_api_client_async/models/int_filter.py daytona_api_client_async/models/job.py daytona_api_client_async/models/job_status.py @@ -215,7 +218,9 @@ daytona_api_client_async/models/user.py daytona_api_client_async/models/user_home_dir_response.py daytona_api_client_async/models/user_public_key.py daytona_api_client_async/models/volume_dto.py +daytona_api_client_async/models/volume_mount_token_dto.py daytona_api_client_async/models/volume_state.py +daytona_api_client_async/models/volume_type.py daytona_api_client_async/models/webhook_app_portal_access.py daytona_api_client_async/models/webhook_event.py daytona_api_client_async/models/webhook_initialization_status.py diff --git a/api-client-python-async/daytona_api_client_async/__init__.py b/api-client-python-async/daytona_api_client_async/__init__.py index 85dff1a24..0e765aa0f 100644 --- a/api-client-python-async/daytona_api_client_async/__init__.py +++ b/api-client-python-async/daytona_api_client_async/__init__.py @@ -63,6 +63,7 @@ from daytona_api_client_async.models.api_key_response import ApiKeyResponse from daytona_api_client_async.models.audit_log import AuditLog from daytona_api_client_async.models.available_sandbox_class import AvailableSandboxClass + from daytona_api_client_async.models.blockmount_conflict import BlockmountConflict from daytona_api_client_async.models.build_info import BuildInfo from daytona_api_client_async.models.command import Command from daytona_api_client_async.models.completion_context import CompletionContext @@ -92,6 +93,7 @@ from daytona_api_client_async.models.create_snapshot import CreateSnapshot from daytona_api_client_async.models.create_user import CreateUser from daytona_api_client_async.models.create_volume import CreateVolume + from daytona_api_client_async.models.create_volume_mount_token import CreateVolumeMountToken from daytona_api_client_async.models.date_filter import DateFilter from daytona_api_client_async.models.daytona_configuration import DaytonaConfiguration from daytona_api_client_async.models.display_info_response import DisplayInfoResponse @@ -116,6 +118,7 @@ from daytona_api_client_async.models.health_controller_check200_response import HealthControllerCheck200Response from daytona_api_client_async.models.health_controller_check200_response_info_value import HealthControllerCheck200ResponseInfoValue from daytona_api_client_async.models.health_controller_check503_response import HealthControllerCheck503Response + from daytona_api_client_async.models.hotmount_region import HotmountRegion from daytona_api_client_async.models.int_filter import IntFilter from daytona_api_client_async.models.job import Job from daytona_api_client_async.models.job_status import JobStatus @@ -244,7 +247,9 @@ from daytona_api_client_async.models.user_home_dir_response import UserHomeDirResponse from daytona_api_client_async.models.user_public_key import UserPublicKey from daytona_api_client_async.models.volume_dto import VolumeDto + from daytona_api_client_async.models.volume_mount_token_dto import VolumeMountTokenDto from daytona_api_client_async.models.volume_state import VolumeState + from daytona_api_client_async.models.volume_type import VolumeType from daytona_api_client_async.models.webhook_app_portal_access import WebhookAppPortalAccess from daytona_api_client_async.models.webhook_event import WebhookEvent from daytona_api_client_async.models.webhook_initialization_status import WebhookInitializationStatus @@ -292,6 +297,7 @@ "ApiKeyResponse": "daytona_api_client_async.models.api_key_response", "AuditLog": "daytona_api_client_async.models.audit_log", "AvailableSandboxClass": "daytona_api_client_async.models.available_sandbox_class", + "BlockmountConflict": "daytona_api_client_async.models.blockmount_conflict", "BuildInfo": "daytona_api_client_async.models.build_info", "Command": "daytona_api_client_async.models.command", "CompletionContext": "daytona_api_client_async.models.completion_context", @@ -321,6 +327,7 @@ "CreateSnapshot": "daytona_api_client_async.models.create_snapshot", "CreateUser": "daytona_api_client_async.models.create_user", "CreateVolume": "daytona_api_client_async.models.create_volume", + "CreateVolumeMountToken": "daytona_api_client_async.models.create_volume_mount_token", "DateFilter": "daytona_api_client_async.models.date_filter", "DaytonaConfiguration": "daytona_api_client_async.models.daytona_configuration", "DisplayInfoResponse": "daytona_api_client_async.models.display_info_response", @@ -345,6 +352,7 @@ "HealthControllerCheck200Response": "daytona_api_client_async.models.health_controller_check200_response", "HealthControllerCheck200ResponseInfoValue": "daytona_api_client_async.models.health_controller_check200_response_info_value", "HealthControllerCheck503Response": "daytona_api_client_async.models.health_controller_check503_response", + "HotmountRegion": "daytona_api_client_async.models.hotmount_region", "IntFilter": "daytona_api_client_async.models.int_filter", "Job": "daytona_api_client_async.models.job", "JobStatus": "daytona_api_client_async.models.job_status", @@ -473,7 +481,9 @@ "UserHomeDirResponse": "daytona_api_client_async.models.user_home_dir_response", "UserPublicKey": "daytona_api_client_async.models.user_public_key", "VolumeDto": "daytona_api_client_async.models.volume_dto", + "VolumeMountTokenDto": "daytona_api_client_async.models.volume_mount_token_dto", "VolumeState": "daytona_api_client_async.models.volume_state", + "VolumeType": "daytona_api_client_async.models.volume_type", "WebhookAppPortalAccess": "daytona_api_client_async.models.webhook_app_portal_access", "WebhookEvent": "daytona_api_client_async.models.webhook_event", "WebhookInitializationStatus": "daytona_api_client_async.models.webhook_initialization_status", @@ -535,6 +545,7 @@ def __dir__() -> list[str]: "ApiKeyResponse", "AuditLog", "AvailableSandboxClass", + "BlockmountConflict", "BuildInfo", "Command", "CompletionContext", @@ -564,6 +575,7 @@ def __dir__() -> list[str]: "CreateSnapshot", "CreateUser", "CreateVolume", + "CreateVolumeMountToken", "DateFilter", "DaytonaConfiguration", "DisplayInfoResponse", @@ -588,6 +600,7 @@ def __dir__() -> list[str]: "HealthControllerCheck200Response", "HealthControllerCheck200ResponseInfoValue", "HealthControllerCheck503Response", + "HotmountRegion", "IntFilter", "Job", "JobStatus", @@ -716,7 +729,9 @@ def __dir__() -> list[str]: "UserHomeDirResponse", "UserPublicKey", "VolumeDto", + "VolumeMountTokenDto", "VolumeState", + "VolumeType", "WebhookAppPortalAccess", "WebhookEvent", "WebhookInitializationStatus", diff --git a/api-client-python-async/daytona_api_client_async/api/volumes_api.py b/api-client-python-async/daytona_api_client_async/api/volumes_api.py index e7bcd6e8a..99446306b 100644 --- a/api-client-python-async/daytona_api_client_async/api/volumes_api.py +++ b/api-client-python-async/daytona_api_client_async/api/volumes_api.py @@ -20,7 +20,11 @@ from typing import List, Optional from typing_extensions import Annotated from daytona_api_client_async.models.create_volume import CreateVolume +from daytona_api_client_async.models.create_volume_mount_token import CreateVolumeMountToken +from daytona_api_client_async.models.hotmount_region import HotmountRegion +from daytona_api_client_async.models.region import Region from daytona_api_client_async.models.volume_dto import VolumeDto +from daytona_api_client_async.models.volume_mount_token_dto import VolumeMountTokenDto from daytona_api_client_async.api_client import ApiClient, RequestSerialized from daytona_api_client_async.api_response import ApiResponse @@ -328,10 +332,11 @@ def _create_volume_serialize( @validate_call - async def delete_volume( + async def create_volume_mount_token( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + create_volume_mount_token: Optional[CreateVolumeMountToken] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -344,14 +349,16 @@ async def delete_volume( _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> None: - """Delete volume + ) -> VolumeMountTokenDto: + """Create a mount token for a hotmount volume :param volume_id: ID of the volume (required) :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str + :param create_volume_mount_token: + :type create_volume_mount_token: CreateVolumeMountToken :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -374,9 +381,10 @@ async def delete_volume( :return: Returns the result object. """ # noqa: E501 - _param = self._delete_volume_serialize( + _param = self._create_volume_mount_token_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, + create_volume_mount_token=create_volume_mount_token, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -384,8 +392,7 @@ async def delete_volume( ) _response_types_map: Dict[str, Optional[str]] = { - '200': None, - '409': None, + '200': "VolumeMountTokenDto", } response_data = await self.api_client.call_api( *_param, @@ -399,10 +406,11 @@ async def delete_volume( @validate_call - async def delete_volume_with_http_info( + async def create_volume_mount_token_with_http_info( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + create_volume_mount_token: Optional[CreateVolumeMountToken] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -415,14 +423,16 @@ async def delete_volume_with_http_info( _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ApiResponse[None]: - """Delete volume + ) -> ApiResponse[VolumeMountTokenDto]: + """Create a mount token for a hotmount volume :param volume_id: ID of the volume (required) :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str + :param create_volume_mount_token: + :type create_volume_mount_token: CreateVolumeMountToken :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -445,9 +455,10 @@ async def delete_volume_with_http_info( :return: Returns the result object. """ # noqa: E501 - _param = self._delete_volume_serialize( + _param = self._create_volume_mount_token_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, + create_volume_mount_token=create_volume_mount_token, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -455,8 +466,7 @@ async def delete_volume_with_http_info( ) _response_types_map: Dict[str, Optional[str]] = { - '200': None, - '409': None, + '200': "VolumeMountTokenDto", } response_data = await self.api_client.call_api( *_param, @@ -470,10 +480,11 @@ async def delete_volume_with_http_info( @validate_call - async def delete_volume_without_preload_content( + async def create_volume_mount_token_without_preload_content( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + create_volume_mount_token: Optional[CreateVolumeMountToken] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -487,13 +498,15 @@ async def delete_volume_without_preload_content( _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Delete volume + """Create a mount token for a hotmount volume :param volume_id: ID of the volume (required) :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str + :param create_volume_mount_token: + :type create_volume_mount_token: CreateVolumeMountToken :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -516,9 +529,10 @@ async def delete_volume_without_preload_content( :return: Returns the result object. """ # noqa: E501 - _param = self._delete_volume_serialize( + _param = self._create_volume_mount_token_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, + create_volume_mount_token=create_volume_mount_token, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -526,8 +540,7 @@ async def delete_volume_without_preload_content( ) _response_types_map: Dict[str, Optional[str]] = { - '200': None, - '409': None, + '200': "VolumeMountTokenDto", } response_data = await self.api_client.call_api( *_param, @@ -536,10 +549,11 @@ async def delete_volume_without_preload_content( return response_data.response - def _delete_volume_serialize( + def _create_volume_mount_token_serialize( self, volume_id, x_daytona_organization_id, + create_volume_mount_token, _request_auth, _content_type, _headers, @@ -569,9 +583,31 @@ def _delete_volume_serialize( _header_params['X-Daytona-Organization-ID'] = x_daytona_organization_id # process the form parameters # process the body parameter + if create_volume_mount_token is not None: + _body_params = create_volume_mount_token + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type # authentication setting _auth_settings: List[str] = [ @@ -580,8 +616,8 @@ def _delete_volume_serialize( ] return self.api_client.param_serialize( - method='DELETE', - resource_path='/volumes/{volumeId}', + method='POST', + resource_path='/volumes/{volumeId}/mount-token', path_params=_path_params, query_params=_query_params, header_params=_header_params, @@ -598,7 +634,7 @@ def _delete_volume_serialize( @validate_call - async def get_volume( + async def delete_volume( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, @@ -614,8 +650,8 @@ async def get_volume( _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> VolumeDto: - """Get volume details + ) -> None: + """Delete volume :param volume_id: ID of the volume (required) @@ -644,7 +680,7 @@ async def get_volume( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_serialize( + _param = self._delete_volume_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, @@ -654,7 +690,8 @@ async def get_volume( ) _response_types_map: Dict[str, Optional[str]] = { - '200': "VolumeDto", + '200': None, + '409': None, } response_data = await self.api_client.call_api( *_param, @@ -668,7 +705,7 @@ async def get_volume( @validate_call - async def get_volume_with_http_info( + async def delete_volume_with_http_info( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, @@ -684,8 +721,8 @@ async def get_volume_with_http_info( _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ApiResponse[VolumeDto]: - """Get volume details + ) -> ApiResponse[None]: + """Delete volume :param volume_id: ID of the volume (required) @@ -714,7 +751,7 @@ async def get_volume_with_http_info( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_serialize( + _param = self._delete_volume_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, @@ -724,7 +761,8 @@ async def get_volume_with_http_info( ) _response_types_map: Dict[str, Optional[str]] = { - '200': "VolumeDto", + '200': None, + '409': None, } response_data = await self.api_client.call_api( *_param, @@ -738,7 +776,7 @@ async def get_volume_with_http_info( @validate_call - async def get_volume_without_preload_content( + async def delete_volume_without_preload_content( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, @@ -755,7 +793,7 @@ async def get_volume_without_preload_content( _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Get volume details + """Delete volume :param volume_id: ID of the volume (required) @@ -784,7 +822,7 @@ async def get_volume_without_preload_content( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_serialize( + _param = self._delete_volume_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, @@ -794,7 +832,8 @@ async def get_volume_without_preload_content( ) _response_types_map: Dict[str, Optional[str]] = { - '200': "VolumeDto", + '200': None, + '409': None, } response_data = await self.api_client.call_api( *_param, @@ -803,7 +842,7 @@ async def get_volume_without_preload_content( return response_data.response - def _get_volume_serialize( + def _delete_volume_serialize( self, volume_id, x_daytona_organization_id, @@ -838,13 +877,6 @@ def _get_volume_serialize( # process the body parameter - # set the HTTP header `Accept` - if 'Accept' not in _header_params: - _header_params['Accept'] = self.api_client.select_header_accept( - [ - 'application/json' - ] - ) # authentication setting @@ -854,7 +886,7 @@ def _get_volume_serialize( ] return self.api_client.param_serialize( - method='GET', + method='DELETE', resource_path='/volumes/{volumeId}', path_params=_path_params, query_params=_query_params, @@ -872,9 +904,9 @@ def _get_volume_serialize( @validate_call - async def get_volume_by_name( + async def get_volume( self, - name: Annotated[StrictStr, Field(description="Name of the volume")], + volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, _request_timeout: Union[ None, @@ -889,11 +921,11 @@ async def get_volume_by_name( _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> VolumeDto: - """Get volume details by name + """Get volume details - :param name: Name of the volume (required) - :type name: str + :param volume_id: ID of the volume (required) + :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str :param _request_timeout: timeout setting for this request. If one @@ -918,8 +950,8 @@ async def get_volume_by_name( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_by_name_serialize( - name=name, + _param = self._get_volume_serialize( + volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, _content_type=_content_type, @@ -942,9 +974,9 @@ async def get_volume_by_name( @validate_call - async def get_volume_by_name_with_http_info( + async def get_volume_with_http_info( self, - name: Annotated[StrictStr, Field(description="Name of the volume")], + volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, _request_timeout: Union[ None, @@ -959,11 +991,11 @@ async def get_volume_by_name_with_http_info( _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> ApiResponse[VolumeDto]: - """Get volume details by name + """Get volume details - :param name: Name of the volume (required) - :type name: str + :param volume_id: ID of the volume (required) + :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str :param _request_timeout: timeout setting for this request. If one @@ -988,8 +1020,8 @@ async def get_volume_by_name_with_http_info( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_by_name_serialize( - name=name, + _param = self._get_volume_serialize( + volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, _content_type=_content_type, @@ -1012,9 +1044,9 @@ async def get_volume_by_name_with_http_info( @validate_call - async def get_volume_by_name_without_preload_content( + async def get_volume_without_preload_content( self, - name: Annotated[StrictStr, Field(description="Name of the volume")], + volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, _request_timeout: Union[ None, @@ -1029,11 +1061,11 @@ async def get_volume_by_name_without_preload_content( _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Get volume details by name + """Get volume details - :param name: Name of the volume (required) - :type name: str + :param volume_id: ID of the volume (required) + :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str :param _request_timeout: timeout setting for this request. If one @@ -1058,8 +1090,8 @@ async def get_volume_by_name_without_preload_content( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_by_name_serialize( - name=name, + _param = self._get_volume_serialize( + volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, _content_type=_content_type, @@ -1077,9 +1109,9 @@ async def get_volume_by_name_without_preload_content( return response_data.response - def _get_volume_by_name_serialize( + def _get_volume_serialize( self, - name, + volume_id, x_daytona_organization_id, _request_auth, _content_type, @@ -1102,8 +1134,8 @@ def _get_volume_by_name_serialize( _body_params: Optional[bytes] = None # process the path parameters - if name is not None: - _path_params['name'] = name + if volume_id is not None: + _path_params['volumeId'] = volume_id # process the query parameters # process the header parameters if x_daytona_organization_id is not None: @@ -1129,7 +1161,799 @@ def _get_volume_by_name_serialize( return self.api_client.param_serialize( method='GET', - resource_path='/volumes/by-name/{name}', + resource_path='/volumes/{volumeId}', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def get_volume_by_name( + self, + name: Annotated[StrictStr, Field(description="Name of the volume")], + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> VolumeDto: + """Get volume details by name + + + :param name: Name of the volume (required) + :type name: str + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_volume_by_name_serialize( + name=name, + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "VolumeDto", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def get_volume_by_name_with_http_info( + self, + name: Annotated[StrictStr, Field(description="Name of the volume")], + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[VolumeDto]: + """Get volume details by name + + + :param name: Name of the volume (required) + :type name: str + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_volume_by_name_serialize( + name=name, + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "VolumeDto", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def get_volume_by_name_without_preload_content( + self, + name: Annotated[StrictStr, Field(description="Name of the volume")], + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Get volume details by name + + + :param name: Name of the volume (required) + :type name: str + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_volume_by_name_serialize( + name=name, + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "VolumeDto", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _get_volume_by_name_serialize( + self, + name, + x_daytona_organization_id, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + if name is not None: + _path_params['name'] = name + # process the query parameters + # process the header parameters + if x_daytona_organization_id is not None: + _header_params['X-Daytona-Organization-ID'] = x_daytona_organization_id + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'bearer', + 'oauth2' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/volumes/by-name/{name}', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def list_blockmount_regions( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> List[Region]: + """List regions where blockmount volumes can be created + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_blockmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[Region]", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def list_blockmount_regions_with_http_info( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[List[Region]]: + """List regions where blockmount volumes can be created + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_blockmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[Region]", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def list_blockmount_regions_without_preload_content( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """List regions where blockmount volumes can be created + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_blockmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[Region]", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _list_blockmount_regions_serialize( + self, + x_daytona_organization_id, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + if x_daytona_organization_id is not None: + _header_params['X-Daytona-Organization-ID'] = x_daytona_organization_id + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'bearer', + 'oauth2' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/volumes/blockmount-regions', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def list_hotmount_regions( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> List[HotmountRegion]: + """List available hotmount regions + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_hotmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[HotmountRegion]", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def list_hotmount_regions_with_http_info( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[List[HotmountRegion]]: + """List available hotmount regions + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_hotmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[HotmountRegion]", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def list_hotmount_regions_without_preload_content( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """List available hotmount regions + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_hotmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[HotmountRegion]", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _list_hotmount_regions_serialize( + self, + x_daytona_organization_id, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + if x_daytona_organization_id is not None: + _header_params['X-Daytona-Organization-ID'] = x_daytona_organization_id + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'bearer', + 'oauth2' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/volumes/hotmount-regions', path_params=_path_params, query_params=_query_params, header_params=_header_params, diff --git a/api-client-python-async/daytona_api_client_async/models/__init__.py b/api-client-python-async/daytona_api_client_async/models/__init__.py index 1f777621a..e7d1dc1c3 100644 --- a/api-client-python-async/daytona_api_client_async/models/__init__.py +++ b/api-client-python-async/daytona_api_client_async/models/__init__.py @@ -29,6 +29,7 @@ from daytona_api_client_async.models.api_key_response import ApiKeyResponse from daytona_api_client_async.models.audit_log import AuditLog from daytona_api_client_async.models.available_sandbox_class import AvailableSandboxClass + from daytona_api_client_async.models.blockmount_conflict import BlockmountConflict from daytona_api_client_async.models.build_info import BuildInfo from daytona_api_client_async.models.command import Command from daytona_api_client_async.models.completion_context import CompletionContext @@ -58,6 +59,7 @@ from daytona_api_client_async.models.create_snapshot import CreateSnapshot from daytona_api_client_async.models.create_user import CreateUser from daytona_api_client_async.models.create_volume import CreateVolume + from daytona_api_client_async.models.create_volume_mount_token import CreateVolumeMountToken from daytona_api_client_async.models.date_filter import DateFilter from daytona_api_client_async.models.daytona_configuration import DaytonaConfiguration from daytona_api_client_async.models.display_info_response import DisplayInfoResponse @@ -82,6 +84,7 @@ from daytona_api_client_async.models.health_controller_check200_response import HealthControllerCheck200Response from daytona_api_client_async.models.health_controller_check200_response_info_value import HealthControllerCheck200ResponseInfoValue from daytona_api_client_async.models.health_controller_check503_response import HealthControllerCheck503Response + from daytona_api_client_async.models.hotmount_region import HotmountRegion from daytona_api_client_async.models.int_filter import IntFilter from daytona_api_client_async.models.job import Job from daytona_api_client_async.models.job_status import JobStatus @@ -210,7 +213,9 @@ from daytona_api_client_async.models.user_home_dir_response import UserHomeDirResponse from daytona_api_client_async.models.user_public_key import UserPublicKey from daytona_api_client_async.models.volume_dto import VolumeDto + from daytona_api_client_async.models.volume_mount_token_dto import VolumeMountTokenDto from daytona_api_client_async.models.volume_state import VolumeState + from daytona_api_client_async.models.volume_type import VolumeType from daytona_api_client_async.models.webhook_app_portal_access import WebhookAppPortalAccess from daytona_api_client_async.models.webhook_event import WebhookEvent from daytona_api_client_async.models.webhook_initialization_status import WebhookInitializationStatus @@ -227,6 +232,7 @@ "ApiKeyResponse": "daytona_api_client_async.models.api_key_response", "AuditLog": "daytona_api_client_async.models.audit_log", "AvailableSandboxClass": "daytona_api_client_async.models.available_sandbox_class", + "BlockmountConflict": "daytona_api_client_async.models.blockmount_conflict", "BuildInfo": "daytona_api_client_async.models.build_info", "Command": "daytona_api_client_async.models.command", "CompletionContext": "daytona_api_client_async.models.completion_context", @@ -256,6 +262,7 @@ "CreateSnapshot": "daytona_api_client_async.models.create_snapshot", "CreateUser": "daytona_api_client_async.models.create_user", "CreateVolume": "daytona_api_client_async.models.create_volume", + "CreateVolumeMountToken": "daytona_api_client_async.models.create_volume_mount_token", "DateFilter": "daytona_api_client_async.models.date_filter", "DaytonaConfiguration": "daytona_api_client_async.models.daytona_configuration", "DisplayInfoResponse": "daytona_api_client_async.models.display_info_response", @@ -280,6 +287,7 @@ "HealthControllerCheck200Response": "daytona_api_client_async.models.health_controller_check200_response", "HealthControllerCheck200ResponseInfoValue": "daytona_api_client_async.models.health_controller_check200_response_info_value", "HealthControllerCheck503Response": "daytona_api_client_async.models.health_controller_check503_response", + "HotmountRegion": "daytona_api_client_async.models.hotmount_region", "IntFilter": "daytona_api_client_async.models.int_filter", "Job": "daytona_api_client_async.models.job", "JobStatus": "daytona_api_client_async.models.job_status", @@ -408,7 +416,9 @@ "UserHomeDirResponse": "daytona_api_client_async.models.user_home_dir_response", "UserPublicKey": "daytona_api_client_async.models.user_public_key", "VolumeDto": "daytona_api_client_async.models.volume_dto", + "VolumeMountTokenDto": "daytona_api_client_async.models.volume_mount_token_dto", "VolumeState": "daytona_api_client_async.models.volume_state", + "VolumeType": "daytona_api_client_async.models.volume_type", "WebhookAppPortalAccess": "daytona_api_client_async.models.webhook_app_portal_access", "WebhookEvent": "daytona_api_client_async.models.webhook_event", "WebhookInitializationStatus": "daytona_api_client_async.models.webhook_initialization_status", diff --git a/api-client-python-async/daytona_api_client_async/models/blockmount_conflict.py b/api-client-python-async/daytona_api_client_async/models/blockmount_conflict.py new file mode 100644 index 000000000..51da29778 --- /dev/null +++ b/api-client-python-async/daytona_api_client_async/models/blockmount_conflict.py @@ -0,0 +1,111 @@ +# coding: utf-8 + +""" + Daytona + + Daytona AI platform API Docs + + The version of the OpenAPI document: 1.0 + Contact: support@daytona.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from pydantic import TypeAdapter +from typing import Optional, Set +from typing_extensions import Self + +_JSON_ADAPTER = TypeAdapter(Dict[str, Any]) + +class BlockmountConflict(BaseModel): + """ + BlockmountConflict + """ # noqa: E501 + path: StrictStr = Field(description="The path (relative to the volume root) that was concurrently modified") + winner: StrictStr = Field(description="Which side won the merge: \"ours\" (the committing writer) or \"theirs\" (the state already in latest)") + reason: StrictStr = Field(description="Why the winner won: \"mtime\" (newer change), \"tie\" (equal mtimes, committer won), \"modify-over-delete\", or \"type\"") + ours_sha: Optional[StrictStr] = Field(default=None, description="Content hash of the committing writer’s version, when both sides were files", serialization_alias="oursSha") + theirs_sha: Optional[StrictStr] = Field(default=None, description="Content hash of the concurrent version found in latest, when both sides were files", serialization_alias="theirsSha") + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["path", "winner", "reason", "oursSha", "theirsSha"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return _JSON_ADAPTER.dump_json(self.to_dict()).decode() + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of BlockmountConflict from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of BlockmountConflict from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "path": obj.get("path"), + "winner": obj.get("winner"), + "reason": obj.get("reason"), + "ours_sha": obj.get("oursSha"), + "theirs_sha": obj.get("theirsSha") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/api-client-python-async/daytona_api_client_async/models/create_volume.py b/api-client-python-async/daytona_api_client_async/models/create_volume.py index db8b85266..a62a754b1 100644 --- a/api-client-python-async/daytona_api_client_async/models/create_volume.py +++ b/api-client-python-async/daytona_api_client_async/models/create_volume.py @@ -18,8 +18,9 @@ import re # noqa: F401 import json -from pydantic import BaseModel, ConfigDict, StrictStr -from typing import Any, ClassVar, Dict, List +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from daytona_api_client_async.models.volume_type import VolumeType from pydantic import TypeAdapter from typing import Optional, Set from typing_extensions import Self @@ -31,8 +32,10 @@ class CreateVolume(BaseModel): CreateVolume """ # noqa: E501 name: StrictStr + type: Optional[VolumeType] = Field(default=None, description="The type of the volume. Defaults to legacy.") + region: Optional[StrictStr] = Field(default=None, description="The region to create the volume in. For blockmount volumes it selects the region-local CAS store the volume's data lives in — a performance/placement knob, not an attach restriction, so sandboxes in any region can attach the volume (colocation is just faster). Optional for blockmount: when omitted it defaults to the organization's default region (or the first region that offers blockmount). For hotmount volumes it selects the hotmount deployment region and defaults to an active region. Not allowed for legacy volumes. The volume's region is fixed for its lifetime.") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["name"] + __properties: ClassVar[List[str]] = ["name", "type", "region"] model_config = ConfigDict( populate_by_name=True, @@ -91,7 +94,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: return cls.model_validate(obj) _obj = cls.model_validate({ - "name": obj.get("name") + "name": obj.get("name"), + "type": obj.get("type"), + "region": obj.get("region") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/api-client-python-async/daytona_api_client_async/models/create_volume_mount_token.py b/api-client-python-async/daytona_api_client_async/models/create_volume_mount_token.py new file mode 100644 index 000000000..561145b6d --- /dev/null +++ b/api-client-python-async/daytona_api_client_async/models/create_volume_mount_token.py @@ -0,0 +1,103 @@ +# coding: utf-8 + +""" + Daytona + + Daytona AI platform API Docs + + The version of the OpenAPI document: 1.0 + Contact: support@daytona.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from pydantic import TypeAdapter +from typing import Optional, Set +from typing_extensions import Self + +_JSON_ADAPTER = TypeAdapter(Dict[str, Any]) + +class CreateVolumeMountToken(BaseModel): + """ + CreateVolumeMountToken + """ # noqa: E501 + mode: Optional[StrictStr] = Field(default='rw', description="The access mode for the mount. Defaults to rw.") + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["mode"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return _JSON_ADAPTER.dump_json(self.to_dict()).decode() + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of CreateVolumeMountToken from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of CreateVolumeMountToken from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "mode": obj.get("mode") if obj.get("mode") is not None else 'rw' + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/api-client-python-async/daytona_api_client_async/models/hotmount_region.py b/api-client-python-async/daytona_api_client_async/models/hotmount_region.py new file mode 100644 index 000000000..58171d5f2 --- /dev/null +++ b/api-client-python-async/daytona_api_client_async/models/hotmount_region.py @@ -0,0 +1,107 @@ +# coding: utf-8 + +""" + Daytona + + Daytona AI platform API Docs + + The version of the OpenAPI document: 1.0 + Contact: support@daytona.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List +from pydantic import TypeAdapter +from typing import Optional, Set +from typing_extensions import Self + +_JSON_ADAPTER = TypeAdapter(Dict[str, Any]) + +class HotmountRegion(BaseModel): + """ + HotmountRegion + """ # noqa: E501 + region: StrictStr = Field(description="Stable region id") + label: StrictStr = Field(description="User-facing region name") + geo: StrictStr = Field(description="Geo hint used for default region selection") + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["region", "label", "geo"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return _JSON_ADAPTER.dump_json(self.to_dict()).decode() + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of HotmountRegion from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of HotmountRegion from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "region": obj.get("region"), + "label": obj.get("label"), + "geo": obj.get("geo") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/api-client-python-async/daytona_api_client_async/models/region.py b/api-client-python-async/daytona_api_client_async/models/region.py index 538777671..e26889150 100644 --- a/api-client-python-async/daytona_api_client_async/models/region.py +++ b/api-client-python-async/daytona_api_client_async/models/region.py @@ -18,7 +18,7 @@ import re # noqa: F401 import json -from pydantic import BaseModel, ConfigDict, Field, StrictStr +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr from typing import Any, ClassVar, Dict, List, Optional from daytona_api_client_async.models.region_type import RegionType from pydantic import TypeAdapter @@ -40,8 +40,9 @@ class Region(BaseModel): proxy_url: Optional[StrictStr] = Field(default=None, description="Proxy URL for the region", serialization_alias="proxyUrl") ssh_gateway_url: Optional[StrictStr] = Field(default=None, description="SSH Gateway URL for the region", serialization_alias="sshGatewayUrl") snapshot_manager_url: Optional[StrictStr] = Field(default=None, description="Snapshot Manager URL for the region", serialization_alias="snapshotManagerUrl") + blockmount_enabled: StrictBool = Field(description="Whether blockmount volumes are supported in this region", serialization_alias="blockmountEnabled") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["id", "name", "organizationId", "regionType", "createdAt", "updatedAt", "proxyUrl", "sshGatewayUrl", "snapshotManagerUrl"] + __properties: ClassVar[List[str]] = ["id", "name", "organizationId", "regionType", "createdAt", "updatedAt", "proxyUrl", "sshGatewayUrl", "snapshotManagerUrl", "blockmountEnabled"] model_config = ConfigDict( populate_by_name=True, @@ -128,7 +129,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "updated_at": obj.get("updatedAt"), "proxy_url": obj.get("proxyUrl"), "ssh_gateway_url": obj.get("sshGatewayUrl"), - "snapshot_manager_url": obj.get("snapshotManagerUrl") + "snapshot_manager_url": obj.get("snapshotManagerUrl"), + "blockmount_enabled": obj.get("blockmountEnabled") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/api-client-python-async/daytona_api_client_async/models/sandbox_volume.py b/api-client-python-async/daytona_api_client_async/models/sandbox_volume.py index e7d0cdf5d..d152d596c 100644 --- a/api-client-python-async/daytona_api_client_async/models/sandbox_volume.py +++ b/api-client-python-async/daytona_api_client_async/models/sandbox_volume.py @@ -18,8 +18,9 @@ import re # noqa: F401 import json -from pydantic import BaseModel, ConfigDict, Field, StrictStr -from typing import Any, ClassVar, Dict, List, Optional +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr +from typing import Any, ClassVar, Dict, List, Optional, Union +from daytona_api_client_async.models.volume_type import VolumeType from pydantic import TypeAdapter from typing import Optional, Set from typing_extensions import Self @@ -33,8 +34,17 @@ class SandboxVolume(BaseModel): volume_id: StrictStr = Field(description="The ID or name of the volume. Resolved to the volume ID on sandbox create.", serialization_alias="volumeId") mount_path: StrictStr = Field(description="The mount path for the volume", serialization_alias="mountPath") subpath: Optional[StrictStr] = Field(default=None, description="Optional subpath within the volume to mount. When specified, only this S3 prefix will be accessible. When omitted, the entire volume is mounted.") + volume_type: Optional[VolumeType] = Field(default=None, description="The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy.", serialization_alias="volumeType") + organization_id: Optional[StrictStr] = Field(default=None, description="The organization that owns the volume. Forwarded to the runner to isolate the S3 prefix. Set only for blockmount volumes.", serialization_alias="organizationId") + size_in_gb: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="The logical size of the volume in gigabytes, used by the runner as the per-sandbox scratch quota. Set only for blockmount volumes.", serialization_alias="sizeInGb") + region: Optional[StrictStr] = Field(default=None, description="The region the blockmount volume's data lives in. Forwarded to the runner so it can fetch the region's store credentials over its authenticated channel. Set only for blockmount volumes.") + s3_endpoint: Optional[StrictStr] = Field(default=None, description="The S3 endpoint of the CAS store the blockmount volume's data lives in, resolved from the volume's region. Forwarded to the runner so cross-region attaches reach the right bucket. Omitted when the volume's region has no store configured (runner falls back to its env store). Credentials are never sent here — the runner fetches them by region. Set only for blockmount volumes.", serialization_alias="s3Endpoint") + s3_region: Optional[StrictStr] = Field(default=None, description="The S3 region of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes.", serialization_alias="s3Region") + s3_bucket: Optional[StrictStr] = Field(default=None, description="The S3 bucket of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes.", serialization_alias="s3Bucket") + s3_prefix: Optional[StrictStr] = Field(default=None, description="The S3 key prefix of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes.", serialization_alias="s3Prefix") + s3_path_style: Optional[StrictBool] = Field(default=None, description="Whether the CAS store uses path-style S3 addressing. Set only for blockmount volumes.", serialization_alias="s3PathStyle") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["volumeId", "mountPath", "subpath"] + __properties: ClassVar[List[str]] = ["volumeId", "mountPath", "subpath", "volumeType", "organizationId", "sizeInGb", "region", "s3Endpoint", "s3Region", "s3Bucket", "s3Prefix", "s3PathStyle"] model_config = ConfigDict( populate_by_name=True, @@ -95,7 +105,16 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: _obj = cls.model_validate({ "volume_id": obj.get("volumeId"), "mount_path": obj.get("mountPath"), - "subpath": obj.get("subpath") + "subpath": obj.get("subpath"), + "volume_type": obj.get("volumeType"), + "organization_id": obj.get("organizationId"), + "size_in_gb": obj.get("sizeInGb"), + "region": obj.get("region"), + "s3_endpoint": obj.get("s3Endpoint"), + "s3_region": obj.get("s3Region"), + "s3_bucket": obj.get("s3Bucket"), + "s3_prefix": obj.get("s3Prefix"), + "s3_path_style": obj.get("s3PathStyle") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/api-client-python-async/daytona_api_client_async/models/volume_dto.py b/api-client-python-async/daytona_api_client_async/models/volume_dto.py index cd7e1c7bb..bdd2ee90a 100644 --- a/api-client-python-async/daytona_api_client_async/models/volume_dto.py +++ b/api-client-python-async/daytona_api_client_async/models/volume_dto.py @@ -18,9 +18,11 @@ import re # noqa: F401 import json -from pydantic import BaseModel, ConfigDict, Field, StrictStr -from typing import Any, ClassVar, Dict, List, Optional +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr +from typing import Any, ClassVar, Dict, List, Optional, Union +from daytona_api_client_async.models.blockmount_conflict import BlockmountConflict from daytona_api_client_async.models.volume_state import VolumeState +from daytona_api_client_async.models.volume_type import VolumeType from pydantic import TypeAdapter from typing import Optional, Set from typing_extensions import Self @@ -34,13 +36,19 @@ class VolumeDto(BaseModel): id: StrictStr = Field(description="Volume ID") name: StrictStr = Field(description="Volume name") organization_id: StrictStr = Field(description="Organization ID", serialization_alias="organizationId") + type: VolumeType = Field(description="Volume type") + size_in_gb: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="The per-sandbox scratch quota in GB. Set only for blockmount volumes.", serialization_alias="sizeInGb") + region: Optional[StrictStr] = Field(default=None, description="The region the volume's data lives in. For blockmount volumes this selects the region-local CAS store (a performance/placement knob — sandboxes in any region can attach it, colocation is just faster). For hotmount volumes this is the hotmount deployment region. Set for blockmount and hotmount volumes.") + shared: Optional[StrictBool] = Field(default=None, description="The hotmount sharing mode (false = single-writer write-back, true = multi-writer synchronous). Set only for hotmount volumes.") + last_manifest_id: Optional[StrictStr] = Field(default=None, description="The id of the most recent committed manifest, read-through from the reconciliation store. Set only for blockmount volumes that have been committed at least once.", serialization_alias="lastManifestId") + conflicts: Optional[List[BlockmountConflict]] = Field(default=None, description="Conflicts recorded on the latest manifest — concurrent same-path modifications the store resolved (last-change-wins). Read-through from the store. Set only for blockmount volumes.") state: VolumeState = Field(description="Volume state") created_at: StrictStr = Field(description="Creation timestamp", serialization_alias="createdAt") updated_at: StrictStr = Field(description="Last update timestamp", serialization_alias="updatedAt") last_used_at: Optional[StrictStr] = Field(default=None, description="Last used timestamp", serialization_alias="lastUsedAt") error_reason: Optional[StrictStr] = Field(description="The error reason of the volume", serialization_alias="errorReason") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["id", "name", "organizationId", "state", "createdAt", "updatedAt", "lastUsedAt", "errorReason"] + __properties: ClassVar[List[str]] = ["id", "name", "organizationId", "type", "sizeInGb", "region", "shared", "lastManifestId", "conflicts", "state", "createdAt", "updatedAt", "lastUsedAt", "errorReason"] model_config = ConfigDict( populate_by_name=True, @@ -82,11 +90,43 @@ def to_dict(self) -> Dict[str, Any]: exclude=excluded_fields, exclude_none=True, ) + # override the default output from pydantic by calling `to_dict()` of each item in conflicts (list) + _items = [] + if self.conflicts: + for _item_conflicts in self.conflicts: + if _item_conflicts: + _items.append(_item_conflicts.to_dict()) + _dict['conflicts'] = _items # puts key-value pairs in additional_properties in the top level if self.additional_properties is not None: for _key, _value in self.additional_properties.items(): _dict[_key] = _value + # set to None if size_in_gb (nullable) is None + # and model_fields_set contains the field + if self.size_in_gb is None and "size_in_gb" in self.model_fields_set: + _dict['sizeInGb'] = None + + # set to None if region (nullable) is None + # and model_fields_set contains the field + if self.region is None and "region" in self.model_fields_set: + _dict['region'] = None + + # set to None if shared (nullable) is None + # and model_fields_set contains the field + if self.shared is None and "shared" in self.model_fields_set: + _dict['shared'] = None + + # set to None if last_manifest_id (nullable) is None + # and model_fields_set contains the field + if self.last_manifest_id is None and "last_manifest_id" in self.model_fields_set: + _dict['lastManifestId'] = None + + # set to None if conflicts (nullable) is None + # and model_fields_set contains the field + if self.conflicts is None and "conflicts" in self.model_fields_set: + _dict['conflicts'] = None + # set to None if last_used_at (nullable) is None # and model_fields_set contains the field if self.last_used_at is None and "last_used_at" in self.model_fields_set: @@ -112,6 +152,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "id": obj.get("id"), "name": obj.get("name"), "organization_id": obj.get("organizationId"), + "type": obj.get("type"), + "size_in_gb": obj.get("sizeInGb"), + "region": obj.get("region"), + "shared": obj.get("shared"), + "last_manifest_id": obj.get("lastManifestId"), + "conflicts": [BlockmountConflict.from_dict(_item) for _item in obj["conflicts"]] if obj.get("conflicts") is not None else None, "state": obj.get("state"), "created_at": obj.get("createdAt"), "updated_at": obj.get("updatedAt"), diff --git a/api-client-python-async/daytona_api_client_async/models/volume_mount_token_dto.py b/api-client-python-async/daytona_api_client_async/models/volume_mount_token_dto.py new file mode 100644 index 000000000..d2c05e80b --- /dev/null +++ b/api-client-python-async/daytona_api_client_async/models/volume_mount_token_dto.py @@ -0,0 +1,120 @@ +# coding: utf-8 + +""" + Daytona + + Daytona AI platform API Docs + + The version of the OpenAPI document: 1.0 + Contact: support@daytona.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from pydantic import TypeAdapter +from typing import Optional, Set +from typing_extensions import Self + +_JSON_ADAPTER = TypeAdapter(Dict[str, Any]) + +class VolumeMountTokenDto(BaseModel): + """ + VolumeMountTokenDto + """ # noqa: E501 + token: StrictStr = Field(description="The short-lived macaroon token the in-sandbox agent uses to mount the volume") + expires_at: StrictStr = Field(description="The token expiration timestamp", serialization_alias="expiresAt") + region: StrictStr = Field(description="The hotmount region the volume lives in") + gateway_grpc: StrictStr = Field(description="The gateway gRPC endpoint (SEAWEED_GATEWAY_GRPC)", serialization_alias="gatewayGrpc") + gateway_http: StrictStr = Field(description="The gateway HTTPS endpoint (SEAWEED_GATEWAY_HTTP)", serialization_alias="gatewayHttp") + binaries_url: StrictStr = Field(description="The binaries bucket base URL used to bootstrap the mount (SEAWEED_BINARIES_URL)", serialization_alias="binariesUrl") + version: Optional[StrictStr] = Field(default=None, description="The pinned client binary version to use (SEAWEED_VERSION), when the region pins one") + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["token", "expiresAt", "region", "gatewayGrpc", "gatewayHttp", "binariesUrl", "version"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return _JSON_ADAPTER.dump_json(self.to_dict()).decode() + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of VolumeMountTokenDto from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + # set to None if version (nullable) is None + # and model_fields_set contains the field + if self.version is None and "version" in self.model_fields_set: + _dict['version'] = None + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of VolumeMountTokenDto from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "token": obj.get("token"), + "expires_at": obj.get("expiresAt"), + "region": obj.get("region"), + "gateway_grpc": obj.get("gatewayGrpc"), + "gateway_http": obj.get("gatewayHttp"), + "binaries_url": obj.get("binariesUrl"), + "version": obj.get("version") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/api-client-python-async/daytona_api_client_async/models/volume_type.py b/api-client-python-async/daytona_api_client_async/models/volume_type.py new file mode 100644 index 000000000..c31b36719 --- /dev/null +++ b/api-client-python-async/daytona_api_client_async/models/volume_type.py @@ -0,0 +1,44 @@ +# coding: utf-8 + +""" + Daytona + + Daytona AI platform API Docs + + The version of the OpenAPI document: 1.0 + Contact: support@daytona.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +from enum import Enum +from typing_extensions import Self + + +class VolumeType(str, Enum): + """ + The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy. + """ + + """ + allowed enum values + """ + LEGACY = 'legacy' + HOTMOUNT = 'hotmount' + BLOCKMOUNT = 'blockmount' + UNKNOWN_DEFAULT_OPEN_API = 'unknown_default_open_api' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of VolumeType from a JSON string""" + return cls(json.loads(json_str)) + + + @classmethod + def _missing_(cls, value): + return cls.UNKNOWN_DEFAULT_OPEN_API + diff --git a/api-client-python/.openapi-generator/FILES b/api-client-python/.openapi-generator/FILES index 96a7df798..f9a7368b3 100644 --- a/api-client-python/.openapi-generator/FILES +++ b/api-client-python/.openapi-generator/FILES @@ -34,6 +34,7 @@ daytona_api_client/models/api_key_list.py daytona_api_client/models/api_key_response.py daytona_api_client/models/audit_log.py daytona_api_client/models/available_sandbox_class.py +daytona_api_client/models/blockmount_conflict.py daytona_api_client/models/build_info.py daytona_api_client/models/command.py daytona_api_client/models/completion_context.py @@ -63,6 +64,7 @@ daytona_api_client/models/create_session_request.py daytona_api_client/models/create_snapshot.py daytona_api_client/models/create_user.py daytona_api_client/models/create_volume.py +daytona_api_client/models/create_volume_mount_token.py daytona_api_client/models/date_filter.py daytona_api_client/models/daytona_configuration.py daytona_api_client/models/display_info_response.py @@ -87,6 +89,7 @@ daytona_api_client/models/gpu_type.py daytona_api_client/models/health_controller_check200_response.py daytona_api_client/models/health_controller_check200_response_info_value.py daytona_api_client/models/health_controller_check503_response.py +daytona_api_client/models/hotmount_region.py daytona_api_client/models/int_filter.py daytona_api_client/models/job.py daytona_api_client/models/job_status.py @@ -215,7 +218,9 @@ daytona_api_client/models/user.py daytona_api_client/models/user_home_dir_response.py daytona_api_client/models/user_public_key.py daytona_api_client/models/volume_dto.py +daytona_api_client/models/volume_mount_token_dto.py daytona_api_client/models/volume_state.py +daytona_api_client/models/volume_type.py daytona_api_client/models/webhook_app_portal_access.py daytona_api_client/models/webhook_event.py daytona_api_client/models/webhook_initialization_status.py diff --git a/api-client-python/daytona_api_client/__init__.py b/api-client-python/daytona_api_client/__init__.py index 3c3cdac33..a194a83b4 100644 --- a/api-client-python/daytona_api_client/__init__.py +++ b/api-client-python/daytona_api_client/__init__.py @@ -63,6 +63,7 @@ from daytona_api_client.models.api_key_response import ApiKeyResponse from daytona_api_client.models.audit_log import AuditLog from daytona_api_client.models.available_sandbox_class import AvailableSandboxClass + from daytona_api_client.models.blockmount_conflict import BlockmountConflict from daytona_api_client.models.build_info import BuildInfo from daytona_api_client.models.command import Command from daytona_api_client.models.completion_context import CompletionContext @@ -92,6 +93,7 @@ from daytona_api_client.models.create_snapshot import CreateSnapshot from daytona_api_client.models.create_user import CreateUser from daytona_api_client.models.create_volume import CreateVolume + from daytona_api_client.models.create_volume_mount_token import CreateVolumeMountToken from daytona_api_client.models.date_filter import DateFilter from daytona_api_client.models.daytona_configuration import DaytonaConfiguration from daytona_api_client.models.display_info_response import DisplayInfoResponse @@ -116,6 +118,7 @@ from daytona_api_client.models.health_controller_check200_response import HealthControllerCheck200Response from daytona_api_client.models.health_controller_check200_response_info_value import HealthControllerCheck200ResponseInfoValue from daytona_api_client.models.health_controller_check503_response import HealthControllerCheck503Response + from daytona_api_client.models.hotmount_region import HotmountRegion from daytona_api_client.models.int_filter import IntFilter from daytona_api_client.models.job import Job from daytona_api_client.models.job_status import JobStatus @@ -244,7 +247,9 @@ from daytona_api_client.models.user_home_dir_response import UserHomeDirResponse from daytona_api_client.models.user_public_key import UserPublicKey from daytona_api_client.models.volume_dto import VolumeDto + from daytona_api_client.models.volume_mount_token_dto import VolumeMountTokenDto from daytona_api_client.models.volume_state import VolumeState + from daytona_api_client.models.volume_type import VolumeType from daytona_api_client.models.webhook_app_portal_access import WebhookAppPortalAccess from daytona_api_client.models.webhook_event import WebhookEvent from daytona_api_client.models.webhook_initialization_status import WebhookInitializationStatus @@ -292,6 +297,7 @@ "ApiKeyResponse": "daytona_api_client.models.api_key_response", "AuditLog": "daytona_api_client.models.audit_log", "AvailableSandboxClass": "daytona_api_client.models.available_sandbox_class", + "BlockmountConflict": "daytona_api_client.models.blockmount_conflict", "BuildInfo": "daytona_api_client.models.build_info", "Command": "daytona_api_client.models.command", "CompletionContext": "daytona_api_client.models.completion_context", @@ -321,6 +327,7 @@ "CreateSnapshot": "daytona_api_client.models.create_snapshot", "CreateUser": "daytona_api_client.models.create_user", "CreateVolume": "daytona_api_client.models.create_volume", + "CreateVolumeMountToken": "daytona_api_client.models.create_volume_mount_token", "DateFilter": "daytona_api_client.models.date_filter", "DaytonaConfiguration": "daytona_api_client.models.daytona_configuration", "DisplayInfoResponse": "daytona_api_client.models.display_info_response", @@ -345,6 +352,7 @@ "HealthControllerCheck200Response": "daytona_api_client.models.health_controller_check200_response", "HealthControllerCheck200ResponseInfoValue": "daytona_api_client.models.health_controller_check200_response_info_value", "HealthControllerCheck503Response": "daytona_api_client.models.health_controller_check503_response", + "HotmountRegion": "daytona_api_client.models.hotmount_region", "IntFilter": "daytona_api_client.models.int_filter", "Job": "daytona_api_client.models.job", "JobStatus": "daytona_api_client.models.job_status", @@ -473,7 +481,9 @@ "UserHomeDirResponse": "daytona_api_client.models.user_home_dir_response", "UserPublicKey": "daytona_api_client.models.user_public_key", "VolumeDto": "daytona_api_client.models.volume_dto", + "VolumeMountTokenDto": "daytona_api_client.models.volume_mount_token_dto", "VolumeState": "daytona_api_client.models.volume_state", + "VolumeType": "daytona_api_client.models.volume_type", "WebhookAppPortalAccess": "daytona_api_client.models.webhook_app_portal_access", "WebhookEvent": "daytona_api_client.models.webhook_event", "WebhookInitializationStatus": "daytona_api_client.models.webhook_initialization_status", @@ -535,6 +545,7 @@ def __dir__() -> list[str]: "ApiKeyResponse", "AuditLog", "AvailableSandboxClass", + "BlockmountConflict", "BuildInfo", "Command", "CompletionContext", @@ -564,6 +575,7 @@ def __dir__() -> list[str]: "CreateSnapshot", "CreateUser", "CreateVolume", + "CreateVolumeMountToken", "DateFilter", "DaytonaConfiguration", "DisplayInfoResponse", @@ -588,6 +600,7 @@ def __dir__() -> list[str]: "HealthControllerCheck200Response", "HealthControllerCheck200ResponseInfoValue", "HealthControllerCheck503Response", + "HotmountRegion", "IntFilter", "Job", "JobStatus", @@ -716,7 +729,9 @@ def __dir__() -> list[str]: "UserHomeDirResponse", "UserPublicKey", "VolumeDto", + "VolumeMountTokenDto", "VolumeState", + "VolumeType", "WebhookAppPortalAccess", "WebhookEvent", "WebhookInitializationStatus", diff --git a/api-client-python/daytona_api_client/api/volumes_api.py b/api-client-python/daytona_api_client/api/volumes_api.py index f76a27986..06ff9a33e 100644 --- a/api-client-python/daytona_api_client/api/volumes_api.py +++ b/api-client-python/daytona_api_client/api/volumes_api.py @@ -20,7 +20,11 @@ from typing import List, Optional from typing_extensions import Annotated from daytona_api_client.models.create_volume import CreateVolume +from daytona_api_client.models.create_volume_mount_token import CreateVolumeMountToken +from daytona_api_client.models.hotmount_region import HotmountRegion +from daytona_api_client.models.region import Region from daytona_api_client.models.volume_dto import VolumeDto +from daytona_api_client.models.volume_mount_token_dto import VolumeMountTokenDto from daytona_api_client.api_client import ApiClient, RequestSerialized from daytona_api_client.api_response import ApiResponse @@ -328,10 +332,11 @@ def _create_volume_serialize( @validate_call - def delete_volume( + def create_volume_mount_token( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + create_volume_mount_token: Optional[CreateVolumeMountToken] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -344,14 +349,16 @@ def delete_volume( _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> None: - """Delete volume + ) -> VolumeMountTokenDto: + """Create a mount token for a hotmount volume :param volume_id: ID of the volume (required) :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str + :param create_volume_mount_token: + :type create_volume_mount_token: CreateVolumeMountToken :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -374,9 +381,10 @@ def delete_volume( :return: Returns the result object. """ # noqa: E501 - _param = self._delete_volume_serialize( + _param = self._create_volume_mount_token_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, + create_volume_mount_token=create_volume_mount_token, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -384,8 +392,7 @@ def delete_volume( ) _response_types_map: Dict[str, Optional[str]] = { - '200': None, - '409': None, + '200': "VolumeMountTokenDto", } response_data = self.api_client.call_api( *_param, @@ -399,10 +406,11 @@ def delete_volume( @validate_call - def delete_volume_with_http_info( + def create_volume_mount_token_with_http_info( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + create_volume_mount_token: Optional[CreateVolumeMountToken] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -415,14 +423,16 @@ def delete_volume_with_http_info( _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ApiResponse[None]: - """Delete volume + ) -> ApiResponse[VolumeMountTokenDto]: + """Create a mount token for a hotmount volume :param volume_id: ID of the volume (required) :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str + :param create_volume_mount_token: + :type create_volume_mount_token: CreateVolumeMountToken :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -445,9 +455,10 @@ def delete_volume_with_http_info( :return: Returns the result object. """ # noqa: E501 - _param = self._delete_volume_serialize( + _param = self._create_volume_mount_token_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, + create_volume_mount_token=create_volume_mount_token, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -455,8 +466,7 @@ def delete_volume_with_http_info( ) _response_types_map: Dict[str, Optional[str]] = { - '200': None, - '409': None, + '200': "VolumeMountTokenDto", } response_data = self.api_client.call_api( *_param, @@ -470,10 +480,11 @@ def delete_volume_with_http_info( @validate_call - def delete_volume_without_preload_content( + def create_volume_mount_token_without_preload_content( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + create_volume_mount_token: Optional[CreateVolumeMountToken] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -487,13 +498,15 @@ def delete_volume_without_preload_content( _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Delete volume + """Create a mount token for a hotmount volume :param volume_id: ID of the volume (required) :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str + :param create_volume_mount_token: + :type create_volume_mount_token: CreateVolumeMountToken :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -516,9 +529,10 @@ def delete_volume_without_preload_content( :return: Returns the result object. """ # noqa: E501 - _param = self._delete_volume_serialize( + _param = self._create_volume_mount_token_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, + create_volume_mount_token=create_volume_mount_token, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -526,8 +540,7 @@ def delete_volume_without_preload_content( ) _response_types_map: Dict[str, Optional[str]] = { - '200': None, - '409': None, + '200': "VolumeMountTokenDto", } response_data = self.api_client.call_api( *_param, @@ -536,10 +549,11 @@ def delete_volume_without_preload_content( return response_data.response - def _delete_volume_serialize( + def _create_volume_mount_token_serialize( self, volume_id, x_daytona_organization_id, + create_volume_mount_token, _request_auth, _content_type, _headers, @@ -569,9 +583,31 @@ def _delete_volume_serialize( _header_params['X-Daytona-Organization-ID'] = x_daytona_organization_id # process the form parameters # process the body parameter + if create_volume_mount_token is not None: + _body_params = create_volume_mount_token + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type # authentication setting _auth_settings: List[str] = [ @@ -580,8 +616,8 @@ def _delete_volume_serialize( ] return self.api_client.param_serialize( - method='DELETE', - resource_path='/volumes/{volumeId}', + method='POST', + resource_path='/volumes/{volumeId}/mount-token', path_params=_path_params, query_params=_query_params, header_params=_header_params, @@ -598,7 +634,7 @@ def _delete_volume_serialize( @validate_call - def get_volume( + def delete_volume( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, @@ -614,8 +650,8 @@ def get_volume( _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> VolumeDto: - """Get volume details + ) -> None: + """Delete volume :param volume_id: ID of the volume (required) @@ -644,7 +680,7 @@ def get_volume( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_serialize( + _param = self._delete_volume_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, @@ -654,7 +690,8 @@ def get_volume( ) _response_types_map: Dict[str, Optional[str]] = { - '200': "VolumeDto", + '200': None, + '409': None, } response_data = self.api_client.call_api( *_param, @@ -668,7 +705,7 @@ def get_volume( @validate_call - def get_volume_with_http_info( + def delete_volume_with_http_info( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, @@ -684,8 +721,8 @@ def get_volume_with_http_info( _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ApiResponse[VolumeDto]: - """Get volume details + ) -> ApiResponse[None]: + """Delete volume :param volume_id: ID of the volume (required) @@ -714,7 +751,7 @@ def get_volume_with_http_info( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_serialize( + _param = self._delete_volume_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, @@ -724,7 +761,8 @@ def get_volume_with_http_info( ) _response_types_map: Dict[str, Optional[str]] = { - '200': "VolumeDto", + '200': None, + '409': None, } response_data = self.api_client.call_api( *_param, @@ -738,7 +776,7 @@ def get_volume_with_http_info( @validate_call - def get_volume_without_preload_content( + def delete_volume_without_preload_content( self, volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, @@ -755,7 +793,7 @@ def get_volume_without_preload_content( _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Get volume details + """Delete volume :param volume_id: ID of the volume (required) @@ -784,7 +822,7 @@ def get_volume_without_preload_content( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_serialize( + _param = self._delete_volume_serialize( volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, @@ -794,7 +832,8 @@ def get_volume_without_preload_content( ) _response_types_map: Dict[str, Optional[str]] = { - '200': "VolumeDto", + '200': None, + '409': None, } response_data = self.api_client.call_api( *_param, @@ -803,7 +842,7 @@ def get_volume_without_preload_content( return response_data.response - def _get_volume_serialize( + def _delete_volume_serialize( self, volume_id, x_daytona_organization_id, @@ -838,13 +877,6 @@ def _get_volume_serialize( # process the body parameter - # set the HTTP header `Accept` - if 'Accept' not in _header_params: - _header_params['Accept'] = self.api_client.select_header_accept( - [ - 'application/json' - ] - ) # authentication setting @@ -854,7 +886,7 @@ def _get_volume_serialize( ] return self.api_client.param_serialize( - method='GET', + method='DELETE', resource_path='/volumes/{volumeId}', path_params=_path_params, query_params=_query_params, @@ -872,9 +904,9 @@ def _get_volume_serialize( @validate_call - def get_volume_by_name( + def get_volume( self, - name: Annotated[StrictStr, Field(description="Name of the volume")], + volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, _request_timeout: Union[ None, @@ -889,11 +921,11 @@ def get_volume_by_name( _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> VolumeDto: - """Get volume details by name + """Get volume details - :param name: Name of the volume (required) - :type name: str + :param volume_id: ID of the volume (required) + :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str :param _request_timeout: timeout setting for this request. If one @@ -918,8 +950,8 @@ def get_volume_by_name( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_by_name_serialize( - name=name, + _param = self._get_volume_serialize( + volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, _content_type=_content_type, @@ -942,9 +974,9 @@ def get_volume_by_name( @validate_call - def get_volume_by_name_with_http_info( + def get_volume_with_http_info( self, - name: Annotated[StrictStr, Field(description="Name of the volume")], + volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, _request_timeout: Union[ None, @@ -959,11 +991,11 @@ def get_volume_by_name_with_http_info( _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> ApiResponse[VolumeDto]: - """Get volume details by name + """Get volume details - :param name: Name of the volume (required) - :type name: str + :param volume_id: ID of the volume (required) + :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str :param _request_timeout: timeout setting for this request. If one @@ -988,8 +1020,8 @@ def get_volume_by_name_with_http_info( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_by_name_serialize( - name=name, + _param = self._get_volume_serialize( + volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, _content_type=_content_type, @@ -1012,9 +1044,9 @@ def get_volume_by_name_with_http_info( @validate_call - def get_volume_by_name_without_preload_content( + def get_volume_without_preload_content( self, - name: Annotated[StrictStr, Field(description="Name of the volume")], + volume_id: Annotated[StrictStr, Field(description="ID of the volume")], x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, _request_timeout: Union[ None, @@ -1029,11 +1061,11 @@ def get_volume_by_name_without_preload_content( _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Get volume details by name + """Get volume details - :param name: Name of the volume (required) - :type name: str + :param volume_id: ID of the volume (required) + :type volume_id: str :param x_daytona_organization_id: Use with JWT to specify the organization ID :type x_daytona_organization_id: str :param _request_timeout: timeout setting for this request. If one @@ -1058,8 +1090,8 @@ def get_volume_by_name_without_preload_content( :return: Returns the result object. """ # noqa: E501 - _param = self._get_volume_by_name_serialize( - name=name, + _param = self._get_volume_serialize( + volume_id=volume_id, x_daytona_organization_id=x_daytona_organization_id, _request_auth=_request_auth, _content_type=_content_type, @@ -1077,9 +1109,9 @@ def get_volume_by_name_without_preload_content( return response_data.response - def _get_volume_by_name_serialize( + def _get_volume_serialize( self, - name, + volume_id, x_daytona_organization_id, _request_auth, _content_type, @@ -1102,8 +1134,8 @@ def _get_volume_by_name_serialize( _body_params: Optional[bytes] = None # process the path parameters - if name is not None: - _path_params['name'] = name + if volume_id is not None: + _path_params['volumeId'] = volume_id # process the query parameters # process the header parameters if x_daytona_organization_id is not None: @@ -1129,7 +1161,799 @@ def _get_volume_by_name_serialize( return self.api_client.param_serialize( method='GET', - resource_path='/volumes/by-name/{name}', + resource_path='/volumes/{volumeId}', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + def get_volume_by_name( + self, + name: Annotated[StrictStr, Field(description="Name of the volume")], + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> VolumeDto: + """Get volume details by name + + + :param name: Name of the volume (required) + :type name: str + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_volume_by_name_serialize( + name=name, + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "VolumeDto", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def get_volume_by_name_with_http_info( + self, + name: Annotated[StrictStr, Field(description="Name of the volume")], + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[VolumeDto]: + """Get volume details by name + + + :param name: Name of the volume (required) + :type name: str + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_volume_by_name_serialize( + name=name, + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "VolumeDto", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def get_volume_by_name_without_preload_content( + self, + name: Annotated[StrictStr, Field(description="Name of the volume")], + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Get volume details by name + + + :param name: Name of the volume (required) + :type name: str + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_volume_by_name_serialize( + name=name, + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "VolumeDto", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _get_volume_by_name_serialize( + self, + name, + x_daytona_organization_id, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + if name is not None: + _path_params['name'] = name + # process the query parameters + # process the header parameters + if x_daytona_organization_id is not None: + _header_params['X-Daytona-Organization-ID'] = x_daytona_organization_id + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'bearer', + 'oauth2' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/volumes/by-name/{name}', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + def list_blockmount_regions( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> List[Region]: + """List regions where blockmount volumes can be created + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_blockmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[Region]", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def list_blockmount_regions_with_http_info( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[List[Region]]: + """List regions where blockmount volumes can be created + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_blockmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[Region]", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def list_blockmount_regions_without_preload_content( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """List regions where blockmount volumes can be created + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_blockmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[Region]", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _list_blockmount_regions_serialize( + self, + x_daytona_organization_id, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + if x_daytona_organization_id is not None: + _header_params['X-Daytona-Organization-ID'] = x_daytona_organization_id + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'bearer', + 'oauth2' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/volumes/blockmount-regions', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + def list_hotmount_regions( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> List[HotmountRegion]: + """List available hotmount regions + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_hotmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[HotmountRegion]", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def list_hotmount_regions_with_http_info( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[List[HotmountRegion]]: + """List available hotmount regions + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_hotmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[HotmountRegion]", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def list_hotmount_regions_without_preload_content( + self, + x_daytona_organization_id: Annotated[Optional[StrictStr], Field(description="Use with JWT to specify the organization ID")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """List available hotmount regions + + + :param x_daytona_organization_id: Use with JWT to specify the organization ID + :type x_daytona_organization_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_hotmount_regions_serialize( + x_daytona_organization_id=x_daytona_organization_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "List[HotmountRegion]", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _list_hotmount_regions_serialize( + self, + x_daytona_organization_id, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + if x_daytona_organization_id is not None: + _header_params['X-Daytona-Organization-ID'] = x_daytona_organization_id + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'bearer', + 'oauth2' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/volumes/hotmount-regions', path_params=_path_params, query_params=_query_params, header_params=_header_params, diff --git a/api-client-python/daytona_api_client/models/__init__.py b/api-client-python/daytona_api_client/models/__init__.py index 45a8c3fdb..01ff4ea53 100644 --- a/api-client-python/daytona_api_client/models/__init__.py +++ b/api-client-python/daytona_api_client/models/__init__.py @@ -29,6 +29,7 @@ from daytona_api_client.models.api_key_response import ApiKeyResponse from daytona_api_client.models.audit_log import AuditLog from daytona_api_client.models.available_sandbox_class import AvailableSandboxClass + from daytona_api_client.models.blockmount_conflict import BlockmountConflict from daytona_api_client.models.build_info import BuildInfo from daytona_api_client.models.command import Command from daytona_api_client.models.completion_context import CompletionContext @@ -58,6 +59,7 @@ from daytona_api_client.models.create_snapshot import CreateSnapshot from daytona_api_client.models.create_user import CreateUser from daytona_api_client.models.create_volume import CreateVolume + from daytona_api_client.models.create_volume_mount_token import CreateVolumeMountToken from daytona_api_client.models.date_filter import DateFilter from daytona_api_client.models.daytona_configuration import DaytonaConfiguration from daytona_api_client.models.display_info_response import DisplayInfoResponse @@ -82,6 +84,7 @@ from daytona_api_client.models.health_controller_check200_response import HealthControllerCheck200Response from daytona_api_client.models.health_controller_check200_response_info_value import HealthControllerCheck200ResponseInfoValue from daytona_api_client.models.health_controller_check503_response import HealthControllerCheck503Response + from daytona_api_client.models.hotmount_region import HotmountRegion from daytona_api_client.models.int_filter import IntFilter from daytona_api_client.models.job import Job from daytona_api_client.models.job_status import JobStatus @@ -210,7 +213,9 @@ from daytona_api_client.models.user_home_dir_response import UserHomeDirResponse from daytona_api_client.models.user_public_key import UserPublicKey from daytona_api_client.models.volume_dto import VolumeDto + from daytona_api_client.models.volume_mount_token_dto import VolumeMountTokenDto from daytona_api_client.models.volume_state import VolumeState + from daytona_api_client.models.volume_type import VolumeType from daytona_api_client.models.webhook_app_portal_access import WebhookAppPortalAccess from daytona_api_client.models.webhook_event import WebhookEvent from daytona_api_client.models.webhook_initialization_status import WebhookInitializationStatus @@ -227,6 +232,7 @@ "ApiKeyResponse": "daytona_api_client.models.api_key_response", "AuditLog": "daytona_api_client.models.audit_log", "AvailableSandboxClass": "daytona_api_client.models.available_sandbox_class", + "BlockmountConflict": "daytona_api_client.models.blockmount_conflict", "BuildInfo": "daytona_api_client.models.build_info", "Command": "daytona_api_client.models.command", "CompletionContext": "daytona_api_client.models.completion_context", @@ -256,6 +262,7 @@ "CreateSnapshot": "daytona_api_client.models.create_snapshot", "CreateUser": "daytona_api_client.models.create_user", "CreateVolume": "daytona_api_client.models.create_volume", + "CreateVolumeMountToken": "daytona_api_client.models.create_volume_mount_token", "DateFilter": "daytona_api_client.models.date_filter", "DaytonaConfiguration": "daytona_api_client.models.daytona_configuration", "DisplayInfoResponse": "daytona_api_client.models.display_info_response", @@ -280,6 +287,7 @@ "HealthControllerCheck200Response": "daytona_api_client.models.health_controller_check200_response", "HealthControllerCheck200ResponseInfoValue": "daytona_api_client.models.health_controller_check200_response_info_value", "HealthControllerCheck503Response": "daytona_api_client.models.health_controller_check503_response", + "HotmountRegion": "daytona_api_client.models.hotmount_region", "IntFilter": "daytona_api_client.models.int_filter", "Job": "daytona_api_client.models.job", "JobStatus": "daytona_api_client.models.job_status", @@ -408,7 +416,9 @@ "UserHomeDirResponse": "daytona_api_client.models.user_home_dir_response", "UserPublicKey": "daytona_api_client.models.user_public_key", "VolumeDto": "daytona_api_client.models.volume_dto", + "VolumeMountTokenDto": "daytona_api_client.models.volume_mount_token_dto", "VolumeState": "daytona_api_client.models.volume_state", + "VolumeType": "daytona_api_client.models.volume_type", "WebhookAppPortalAccess": "daytona_api_client.models.webhook_app_portal_access", "WebhookEvent": "daytona_api_client.models.webhook_event", "WebhookInitializationStatus": "daytona_api_client.models.webhook_initialization_status", diff --git a/api-client-python/daytona_api_client/models/blockmount_conflict.py b/api-client-python/daytona_api_client/models/blockmount_conflict.py new file mode 100644 index 000000000..51da29778 --- /dev/null +++ b/api-client-python/daytona_api_client/models/blockmount_conflict.py @@ -0,0 +1,111 @@ +# coding: utf-8 + +""" + Daytona + + Daytona AI platform API Docs + + The version of the OpenAPI document: 1.0 + Contact: support@daytona.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from pydantic import TypeAdapter +from typing import Optional, Set +from typing_extensions import Self + +_JSON_ADAPTER = TypeAdapter(Dict[str, Any]) + +class BlockmountConflict(BaseModel): + """ + BlockmountConflict + """ # noqa: E501 + path: StrictStr = Field(description="The path (relative to the volume root) that was concurrently modified") + winner: StrictStr = Field(description="Which side won the merge: \"ours\" (the committing writer) or \"theirs\" (the state already in latest)") + reason: StrictStr = Field(description="Why the winner won: \"mtime\" (newer change), \"tie\" (equal mtimes, committer won), \"modify-over-delete\", or \"type\"") + ours_sha: Optional[StrictStr] = Field(default=None, description="Content hash of the committing writer’s version, when both sides were files", serialization_alias="oursSha") + theirs_sha: Optional[StrictStr] = Field(default=None, description="Content hash of the concurrent version found in latest, when both sides were files", serialization_alias="theirsSha") + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["path", "winner", "reason", "oursSha", "theirsSha"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return _JSON_ADAPTER.dump_json(self.to_dict()).decode() + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of BlockmountConflict from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of BlockmountConflict from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "path": obj.get("path"), + "winner": obj.get("winner"), + "reason": obj.get("reason"), + "ours_sha": obj.get("oursSha"), + "theirs_sha": obj.get("theirsSha") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/api-client-python/daytona_api_client/models/create_volume.py b/api-client-python/daytona_api_client/models/create_volume.py index db8b85266..3eda32dff 100644 --- a/api-client-python/daytona_api_client/models/create_volume.py +++ b/api-client-python/daytona_api_client/models/create_volume.py @@ -18,8 +18,9 @@ import re # noqa: F401 import json -from pydantic import BaseModel, ConfigDict, StrictStr -from typing import Any, ClassVar, Dict, List +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from daytona_api_client.models.volume_type import VolumeType from pydantic import TypeAdapter from typing import Optional, Set from typing_extensions import Self @@ -31,8 +32,10 @@ class CreateVolume(BaseModel): CreateVolume """ # noqa: E501 name: StrictStr + type: Optional[VolumeType] = Field(default=None, description="The type of the volume. Defaults to legacy.") + region: Optional[StrictStr] = Field(default=None, description="The region to create the volume in. For blockmount volumes it selects the region-local CAS store the volume's data lives in — a performance/placement knob, not an attach restriction, so sandboxes in any region can attach the volume (colocation is just faster). Optional for blockmount: when omitted it defaults to the organization's default region (or the first region that offers blockmount). For hotmount volumes it selects the hotmount deployment region and defaults to an active region. Not allowed for legacy volumes. The volume's region is fixed for its lifetime.") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["name"] + __properties: ClassVar[List[str]] = ["name", "type", "region"] model_config = ConfigDict( populate_by_name=True, @@ -91,7 +94,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: return cls.model_validate(obj) _obj = cls.model_validate({ - "name": obj.get("name") + "name": obj.get("name"), + "type": obj.get("type"), + "region": obj.get("region") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/api-client-python/daytona_api_client/models/create_volume_mount_token.py b/api-client-python/daytona_api_client/models/create_volume_mount_token.py new file mode 100644 index 000000000..561145b6d --- /dev/null +++ b/api-client-python/daytona_api_client/models/create_volume_mount_token.py @@ -0,0 +1,103 @@ +# coding: utf-8 + +""" + Daytona + + Daytona AI platform API Docs + + The version of the OpenAPI document: 1.0 + Contact: support@daytona.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from pydantic import TypeAdapter +from typing import Optional, Set +from typing_extensions import Self + +_JSON_ADAPTER = TypeAdapter(Dict[str, Any]) + +class CreateVolumeMountToken(BaseModel): + """ + CreateVolumeMountToken + """ # noqa: E501 + mode: Optional[StrictStr] = Field(default='rw', description="The access mode for the mount. Defaults to rw.") + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["mode"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return _JSON_ADAPTER.dump_json(self.to_dict()).decode() + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of CreateVolumeMountToken from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of CreateVolumeMountToken from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "mode": obj.get("mode") if obj.get("mode") is not None else 'rw' + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/api-client-python/daytona_api_client/models/hotmount_region.py b/api-client-python/daytona_api_client/models/hotmount_region.py new file mode 100644 index 000000000..58171d5f2 --- /dev/null +++ b/api-client-python/daytona_api_client/models/hotmount_region.py @@ -0,0 +1,107 @@ +# coding: utf-8 + +""" + Daytona + + Daytona AI platform API Docs + + The version of the OpenAPI document: 1.0 + Contact: support@daytona.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List +from pydantic import TypeAdapter +from typing import Optional, Set +from typing_extensions import Self + +_JSON_ADAPTER = TypeAdapter(Dict[str, Any]) + +class HotmountRegion(BaseModel): + """ + HotmountRegion + """ # noqa: E501 + region: StrictStr = Field(description="Stable region id") + label: StrictStr = Field(description="User-facing region name") + geo: StrictStr = Field(description="Geo hint used for default region selection") + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["region", "label", "geo"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return _JSON_ADAPTER.dump_json(self.to_dict()).decode() + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of HotmountRegion from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of HotmountRegion from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "region": obj.get("region"), + "label": obj.get("label"), + "geo": obj.get("geo") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/api-client-python/daytona_api_client/models/region.py b/api-client-python/daytona_api_client/models/region.py index 4d7c531cd..d47f89984 100644 --- a/api-client-python/daytona_api_client/models/region.py +++ b/api-client-python/daytona_api_client/models/region.py @@ -18,7 +18,7 @@ import re # noqa: F401 import json -from pydantic import BaseModel, ConfigDict, Field, StrictStr +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr from typing import Any, ClassVar, Dict, List, Optional from daytona_api_client.models.region_type import RegionType from pydantic import TypeAdapter @@ -40,8 +40,9 @@ class Region(BaseModel): proxy_url: Optional[StrictStr] = Field(default=None, description="Proxy URL for the region", serialization_alias="proxyUrl") ssh_gateway_url: Optional[StrictStr] = Field(default=None, description="SSH Gateway URL for the region", serialization_alias="sshGatewayUrl") snapshot_manager_url: Optional[StrictStr] = Field(default=None, description="Snapshot Manager URL for the region", serialization_alias="snapshotManagerUrl") + blockmount_enabled: StrictBool = Field(description="Whether blockmount volumes are supported in this region", serialization_alias="blockmountEnabled") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["id", "name", "organizationId", "regionType", "createdAt", "updatedAt", "proxyUrl", "sshGatewayUrl", "snapshotManagerUrl"] + __properties: ClassVar[List[str]] = ["id", "name", "organizationId", "regionType", "createdAt", "updatedAt", "proxyUrl", "sshGatewayUrl", "snapshotManagerUrl", "blockmountEnabled"] model_config = ConfigDict( populate_by_name=True, @@ -128,7 +129,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "updated_at": obj.get("updatedAt"), "proxy_url": obj.get("proxyUrl"), "ssh_gateway_url": obj.get("sshGatewayUrl"), - "snapshot_manager_url": obj.get("snapshotManagerUrl") + "snapshot_manager_url": obj.get("snapshotManagerUrl"), + "blockmount_enabled": obj.get("blockmountEnabled") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/api-client-python/daytona_api_client/models/sandbox_volume.py b/api-client-python/daytona_api_client/models/sandbox_volume.py index e7d0cdf5d..563ec1955 100644 --- a/api-client-python/daytona_api_client/models/sandbox_volume.py +++ b/api-client-python/daytona_api_client/models/sandbox_volume.py @@ -18,8 +18,9 @@ import re # noqa: F401 import json -from pydantic import BaseModel, ConfigDict, Field, StrictStr -from typing import Any, ClassVar, Dict, List, Optional +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr +from typing import Any, ClassVar, Dict, List, Optional, Union +from daytona_api_client.models.volume_type import VolumeType from pydantic import TypeAdapter from typing import Optional, Set from typing_extensions import Self @@ -33,8 +34,17 @@ class SandboxVolume(BaseModel): volume_id: StrictStr = Field(description="The ID or name of the volume. Resolved to the volume ID on sandbox create.", serialization_alias="volumeId") mount_path: StrictStr = Field(description="The mount path for the volume", serialization_alias="mountPath") subpath: Optional[StrictStr] = Field(default=None, description="Optional subpath within the volume to mount. When specified, only this S3 prefix will be accessible. When omitted, the entire volume is mounted.") + volume_type: Optional[VolumeType] = Field(default=None, description="The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy.", serialization_alias="volumeType") + organization_id: Optional[StrictStr] = Field(default=None, description="The organization that owns the volume. Forwarded to the runner to isolate the S3 prefix. Set only for blockmount volumes.", serialization_alias="organizationId") + size_in_gb: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="The logical size of the volume in gigabytes, used by the runner as the per-sandbox scratch quota. Set only for blockmount volumes.", serialization_alias="sizeInGb") + region: Optional[StrictStr] = Field(default=None, description="The region the blockmount volume's data lives in. Forwarded to the runner so it can fetch the region's store credentials over its authenticated channel. Set only for blockmount volumes.") + s3_endpoint: Optional[StrictStr] = Field(default=None, description="The S3 endpoint of the CAS store the blockmount volume's data lives in, resolved from the volume's region. Forwarded to the runner so cross-region attaches reach the right bucket. Omitted when the volume's region has no store configured (runner falls back to its env store). Credentials are never sent here — the runner fetches them by region. Set only for blockmount volumes.", serialization_alias="s3Endpoint") + s3_region: Optional[StrictStr] = Field(default=None, description="The S3 region of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes.", serialization_alias="s3Region") + s3_bucket: Optional[StrictStr] = Field(default=None, description="The S3 bucket of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes.", serialization_alias="s3Bucket") + s3_prefix: Optional[StrictStr] = Field(default=None, description="The S3 key prefix of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes.", serialization_alias="s3Prefix") + s3_path_style: Optional[StrictBool] = Field(default=None, description="Whether the CAS store uses path-style S3 addressing. Set only for blockmount volumes.", serialization_alias="s3PathStyle") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["volumeId", "mountPath", "subpath"] + __properties: ClassVar[List[str]] = ["volumeId", "mountPath", "subpath", "volumeType", "organizationId", "sizeInGb", "region", "s3Endpoint", "s3Region", "s3Bucket", "s3Prefix", "s3PathStyle"] model_config = ConfigDict( populate_by_name=True, @@ -95,7 +105,16 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: _obj = cls.model_validate({ "volume_id": obj.get("volumeId"), "mount_path": obj.get("mountPath"), - "subpath": obj.get("subpath") + "subpath": obj.get("subpath"), + "volume_type": obj.get("volumeType"), + "organization_id": obj.get("organizationId"), + "size_in_gb": obj.get("sizeInGb"), + "region": obj.get("region"), + "s3_endpoint": obj.get("s3Endpoint"), + "s3_region": obj.get("s3Region"), + "s3_bucket": obj.get("s3Bucket"), + "s3_prefix": obj.get("s3Prefix"), + "s3_path_style": obj.get("s3PathStyle") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/api-client-python/daytona_api_client/models/volume_dto.py b/api-client-python/daytona_api_client/models/volume_dto.py index efd52a1df..92e613f06 100644 --- a/api-client-python/daytona_api_client/models/volume_dto.py +++ b/api-client-python/daytona_api_client/models/volume_dto.py @@ -18,9 +18,11 @@ import re # noqa: F401 import json -from pydantic import BaseModel, ConfigDict, Field, StrictStr -from typing import Any, ClassVar, Dict, List, Optional +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr +from typing import Any, ClassVar, Dict, List, Optional, Union +from daytona_api_client.models.blockmount_conflict import BlockmountConflict from daytona_api_client.models.volume_state import VolumeState +from daytona_api_client.models.volume_type import VolumeType from pydantic import TypeAdapter from typing import Optional, Set from typing_extensions import Self @@ -34,13 +36,19 @@ class VolumeDto(BaseModel): id: StrictStr = Field(description="Volume ID") name: StrictStr = Field(description="Volume name") organization_id: StrictStr = Field(description="Organization ID", serialization_alias="organizationId") + type: VolumeType = Field(description="Volume type") + size_in_gb: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="The per-sandbox scratch quota in GB. Set only for blockmount volumes.", serialization_alias="sizeInGb") + region: Optional[StrictStr] = Field(default=None, description="The region the volume's data lives in. For blockmount volumes this selects the region-local CAS store (a performance/placement knob — sandboxes in any region can attach it, colocation is just faster). For hotmount volumes this is the hotmount deployment region. Set for blockmount and hotmount volumes.") + shared: Optional[StrictBool] = Field(default=None, description="The hotmount sharing mode (false = single-writer write-back, true = multi-writer synchronous). Set only for hotmount volumes.") + last_manifest_id: Optional[StrictStr] = Field(default=None, description="The id of the most recent committed manifest, read-through from the reconciliation store. Set only for blockmount volumes that have been committed at least once.", serialization_alias="lastManifestId") + conflicts: Optional[List[BlockmountConflict]] = Field(default=None, description="Conflicts recorded on the latest manifest — concurrent same-path modifications the store resolved (last-change-wins). Read-through from the store. Set only for blockmount volumes.") state: VolumeState = Field(description="Volume state") created_at: StrictStr = Field(description="Creation timestamp", serialization_alias="createdAt") updated_at: StrictStr = Field(description="Last update timestamp", serialization_alias="updatedAt") last_used_at: Optional[StrictStr] = Field(default=None, description="Last used timestamp", serialization_alias="lastUsedAt") error_reason: Optional[StrictStr] = Field(description="The error reason of the volume", serialization_alias="errorReason") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["id", "name", "organizationId", "state", "createdAt", "updatedAt", "lastUsedAt", "errorReason"] + __properties: ClassVar[List[str]] = ["id", "name", "organizationId", "type", "sizeInGb", "region", "shared", "lastManifestId", "conflicts", "state", "createdAt", "updatedAt", "lastUsedAt", "errorReason"] model_config = ConfigDict( populate_by_name=True, @@ -82,11 +90,43 @@ def to_dict(self) -> Dict[str, Any]: exclude=excluded_fields, exclude_none=True, ) + # override the default output from pydantic by calling `to_dict()` of each item in conflicts (list) + _items = [] + if self.conflicts: + for _item_conflicts in self.conflicts: + if _item_conflicts: + _items.append(_item_conflicts.to_dict()) + _dict['conflicts'] = _items # puts key-value pairs in additional_properties in the top level if self.additional_properties is not None: for _key, _value in self.additional_properties.items(): _dict[_key] = _value + # set to None if size_in_gb (nullable) is None + # and model_fields_set contains the field + if self.size_in_gb is None and "size_in_gb" in self.model_fields_set: + _dict['sizeInGb'] = None + + # set to None if region (nullable) is None + # and model_fields_set contains the field + if self.region is None and "region" in self.model_fields_set: + _dict['region'] = None + + # set to None if shared (nullable) is None + # and model_fields_set contains the field + if self.shared is None and "shared" in self.model_fields_set: + _dict['shared'] = None + + # set to None if last_manifest_id (nullable) is None + # and model_fields_set contains the field + if self.last_manifest_id is None and "last_manifest_id" in self.model_fields_set: + _dict['lastManifestId'] = None + + # set to None if conflicts (nullable) is None + # and model_fields_set contains the field + if self.conflicts is None and "conflicts" in self.model_fields_set: + _dict['conflicts'] = None + # set to None if last_used_at (nullable) is None # and model_fields_set contains the field if self.last_used_at is None and "last_used_at" in self.model_fields_set: @@ -112,6 +152,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "id": obj.get("id"), "name": obj.get("name"), "organization_id": obj.get("organizationId"), + "type": obj.get("type"), + "size_in_gb": obj.get("sizeInGb"), + "region": obj.get("region"), + "shared": obj.get("shared"), + "last_manifest_id": obj.get("lastManifestId"), + "conflicts": [BlockmountConflict.from_dict(_item) for _item in obj["conflicts"]] if obj.get("conflicts") is not None else None, "state": obj.get("state"), "created_at": obj.get("createdAt"), "updated_at": obj.get("updatedAt"), diff --git a/api-client-python/daytona_api_client/models/volume_mount_token_dto.py b/api-client-python/daytona_api_client/models/volume_mount_token_dto.py new file mode 100644 index 000000000..d2c05e80b --- /dev/null +++ b/api-client-python/daytona_api_client/models/volume_mount_token_dto.py @@ -0,0 +1,120 @@ +# coding: utf-8 + +""" + Daytona + + Daytona AI platform API Docs + + The version of the OpenAPI document: 1.0 + Contact: support@daytona.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from pydantic import TypeAdapter +from typing import Optional, Set +from typing_extensions import Self + +_JSON_ADAPTER = TypeAdapter(Dict[str, Any]) + +class VolumeMountTokenDto(BaseModel): + """ + VolumeMountTokenDto + """ # noqa: E501 + token: StrictStr = Field(description="The short-lived macaroon token the in-sandbox agent uses to mount the volume") + expires_at: StrictStr = Field(description="The token expiration timestamp", serialization_alias="expiresAt") + region: StrictStr = Field(description="The hotmount region the volume lives in") + gateway_grpc: StrictStr = Field(description="The gateway gRPC endpoint (SEAWEED_GATEWAY_GRPC)", serialization_alias="gatewayGrpc") + gateway_http: StrictStr = Field(description="The gateway HTTPS endpoint (SEAWEED_GATEWAY_HTTP)", serialization_alias="gatewayHttp") + binaries_url: StrictStr = Field(description="The binaries bucket base URL used to bootstrap the mount (SEAWEED_BINARIES_URL)", serialization_alias="binariesUrl") + version: Optional[StrictStr] = Field(default=None, description="The pinned client binary version to use (SEAWEED_VERSION), when the region pins one") + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["token", "expiresAt", "region", "gatewayGrpc", "gatewayHttp", "binariesUrl", "version"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return _JSON_ADAPTER.dump_json(self.to_dict()).decode() + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of VolumeMountTokenDto from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + # set to None if version (nullable) is None + # and model_fields_set contains the field + if self.version is None and "version" in self.model_fields_set: + _dict['version'] = None + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of VolumeMountTokenDto from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "token": obj.get("token"), + "expires_at": obj.get("expiresAt"), + "region": obj.get("region"), + "gateway_grpc": obj.get("gatewayGrpc"), + "gateway_http": obj.get("gatewayHttp"), + "binaries_url": obj.get("binariesUrl"), + "version": obj.get("version") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/api-client-python/daytona_api_client/models/volume_type.py b/api-client-python/daytona_api_client/models/volume_type.py new file mode 100644 index 000000000..c31b36719 --- /dev/null +++ b/api-client-python/daytona_api_client/models/volume_type.py @@ -0,0 +1,44 @@ +# coding: utf-8 + +""" + Daytona + + Daytona AI platform API Docs + + The version of the OpenAPI document: 1.0 + Contact: support@daytona.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +from enum import Enum +from typing_extensions import Self + + +class VolumeType(str, Enum): + """ + The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy. + """ + + """ + allowed enum values + """ + LEGACY = 'legacy' + HOTMOUNT = 'hotmount' + BLOCKMOUNT = 'blockmount' + UNKNOWN_DEFAULT_OPEN_API = 'unknown_default_open_api' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of VolumeType from a JSON string""" + return cls(json.loads(json_str)) + + + @classmethod + def _missing_(cls, value): + return cls.UNKNOWN_DEFAULT_OPEN_API + diff --git a/api-client-ruby/.openapi-generator/FILES b/api-client-ruby/.openapi-generator/FILES index 992a3c301..d30cec995 100644 --- a/api-client-ruby/.openapi-generator/FILES +++ b/api-client-ruby/.openapi-generator/FILES @@ -36,6 +36,7 @@ lib/daytona_api_client/models/api_key_list.rb lib/daytona_api_client/models/api_key_response.rb lib/daytona_api_client/models/audit_log.rb lib/daytona_api_client/models/available_sandbox_class.rb +lib/daytona_api_client/models/blockmount_conflict.rb lib/daytona_api_client/models/build_info.rb lib/daytona_api_client/models/command.rb lib/daytona_api_client/models/completion_context.rb @@ -65,6 +66,7 @@ lib/daytona_api_client/models/create_session_request.rb lib/daytona_api_client/models/create_snapshot.rb lib/daytona_api_client/models/create_user.rb lib/daytona_api_client/models/create_volume.rb +lib/daytona_api_client/models/create_volume_mount_token.rb lib/daytona_api_client/models/date_filter.rb lib/daytona_api_client/models/daytona_configuration.rb lib/daytona_api_client/models/display_info_response.rb @@ -89,6 +91,7 @@ lib/daytona_api_client/models/gpu_type.rb lib/daytona_api_client/models/health_controller_check200_response.rb lib/daytona_api_client/models/health_controller_check200_response_info_value.rb lib/daytona_api_client/models/health_controller_check503_response.rb +lib/daytona_api_client/models/hotmount_region.rb lib/daytona_api_client/models/int_filter.rb lib/daytona_api_client/models/job.rb lib/daytona_api_client/models/job_status.rb @@ -217,7 +220,9 @@ lib/daytona_api_client/models/user.rb lib/daytona_api_client/models/user_home_dir_response.rb lib/daytona_api_client/models/user_public_key.rb lib/daytona_api_client/models/volume_dto.rb +lib/daytona_api_client/models/volume_mount_token_dto.rb lib/daytona_api_client/models/volume_state.rb +lib/daytona_api_client/models/volume_type.rb lib/daytona_api_client/models/webhook_app_portal_access.rb lib/daytona_api_client/models/webhook_event.rb lib/daytona_api_client/models/webhook_initialization_status.rb diff --git a/api-client-ruby/lib/daytona_api_client.rb b/api-client-ruby/lib/daytona_api_client.rb index b039bb7d4..ff72e4b03 100644 --- a/api-client-ruby/lib/daytona_api_client.rb +++ b/api-client-ruby/lib/daytona_api_client.rb @@ -27,6 +27,7 @@ require 'daytona_api_client/models/api_key_response' require 'daytona_api_client/models/audit_log' require 'daytona_api_client/models/available_sandbox_class' +require 'daytona_api_client/models/blockmount_conflict' require 'daytona_api_client/models/build_info' require 'daytona_api_client/models/command' require 'daytona_api_client/models/completion_context' @@ -56,6 +57,7 @@ require 'daytona_api_client/models/create_snapshot' require 'daytona_api_client/models/create_user' require 'daytona_api_client/models/create_volume' +require 'daytona_api_client/models/create_volume_mount_token' require 'daytona_api_client/models/date_filter' require 'daytona_api_client/models/daytona_configuration' require 'daytona_api_client/models/display_info_response' @@ -80,6 +82,7 @@ require 'daytona_api_client/models/health_controller_check200_response' require 'daytona_api_client/models/health_controller_check200_response_info_value' require 'daytona_api_client/models/health_controller_check503_response' +require 'daytona_api_client/models/hotmount_region' require 'daytona_api_client/models/int_filter' require 'daytona_api_client/models/job' require 'daytona_api_client/models/job_status' @@ -208,7 +211,9 @@ require 'daytona_api_client/models/user_home_dir_response' require 'daytona_api_client/models/user_public_key' require 'daytona_api_client/models/volume_dto' +require 'daytona_api_client/models/volume_mount_token_dto' require 'daytona_api_client/models/volume_state' +require 'daytona_api_client/models/volume_type' require 'daytona_api_client/models/webhook_app_portal_access' require 'daytona_api_client/models/webhook_event' require 'daytona_api_client/models/webhook_initialization_status' diff --git a/api-client-ruby/lib/daytona_api_client/api/volumes_api.rb b/api-client-ruby/lib/daytona_api_client/api/volumes_api.rb index 6b25eab7f..4f74ca0dc 100644 --- a/api-client-ruby/lib/daytona_api_client/api/volumes_api.rb +++ b/api-client-ruby/lib/daytona_api_client/api/volumes_api.rb @@ -88,6 +88,77 @@ def create_volume_with_http_info(create_volume, opts = {}) return data, status_code, headers end + # Create a mount token for a hotmount volume + # @param volume_id [String] ID of the volume + # @param [Hash] opts the optional parameters + # @option opts [String] :x_daytona_organization_id Use with JWT to specify the organization ID + # @option opts [CreateVolumeMountToken] :create_volume_mount_token + # @return [VolumeMountTokenDto] + def create_volume_mount_token(volume_id, opts = {}) + data, _status_code, _headers = create_volume_mount_token_with_http_info(volume_id, opts) + data + end + + # Create a mount token for a hotmount volume + # @param volume_id [String] ID of the volume + # @param [Hash] opts the optional parameters + # @option opts [String] :x_daytona_organization_id Use with JWT to specify the organization ID + # @option opts [CreateVolumeMountToken] :create_volume_mount_token + # @return [Array<(VolumeMountTokenDto, Integer, Hash)>] VolumeMountTokenDto data, response status code and response headers + def create_volume_mount_token_with_http_info(volume_id, opts = {}) + if @api_client.config.debugging + @api_client.config.logger.debug 'Calling API: VolumesApi.create_volume_mount_token ...' + end + # verify the required parameter 'volume_id' is set + if @api_client.config.client_side_validation && volume_id.nil? + fail ArgumentError, "Missing the required parameter 'volume_id' when calling VolumesApi.create_volume_mount_token" + end + # resource path + local_var_path = '/volumes/{volumeId}/mount-token'.sub('{' + 'volumeId' + '}', CGI.escape(volume_id.to_s)) + + # query parameters + query_params = opts[:query_params] || {} + + # header parameters + header_params = opts[:header_params] || {} + # HTTP header 'Accept' (if needed) + header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept'] + # HTTP header 'Content-Type' + content_type = @api_client.select_header_content_type(['application/json']) + if !content_type.nil? + header_params['Content-Type'] = content_type + end + header_params[:'X-Daytona-Organization-ID'] = opts[:'x_daytona_organization_id'] if !opts[:'x_daytona_organization_id'].nil? + + # form parameters + form_params = opts[:form_params] || {} + + # http body (model) + post_body = opts[:debug_body] || @api_client.object_to_http_body(opts[:'create_volume_mount_token']) + + # return_type + return_type = opts[:debug_return_type] || 'VolumeMountTokenDto' + + # auth_names + auth_names = opts[:debug_auth_names] || ['bearer', 'oauth2'] + + new_options = opts.merge( + :operation => :"VolumesApi.create_volume_mount_token", + :header_params => header_params, + :query_params => query_params, + :form_params => form_params, + :body => post_body, + :auth_names => auth_names, + :return_type => return_type + ) + + data, status_code, headers = @api_client.call_api(:POST, local_var_path, new_options) + if @api_client.config.debugging + @api_client.config.logger.debug "API called: VolumesApi#create_volume_mount_token\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}" + end + return data, status_code, headers + end + # Delete volume # @param volume_id [String] ID of the volume # @param [Hash] opts the optional parameters @@ -278,6 +349,122 @@ def get_volume_by_name_with_http_info(name, opts = {}) return data, status_code, headers end + # List regions where blockmount volumes can be created + # @param [Hash] opts the optional parameters + # @option opts [String] :x_daytona_organization_id Use with JWT to specify the organization ID + # @return [Array] + def list_blockmount_regions(opts = {}) + data, _status_code, _headers = list_blockmount_regions_with_http_info(opts) + data + end + + # List regions where blockmount volumes can be created + # @param [Hash] opts the optional parameters + # @option opts [String] :x_daytona_organization_id Use with JWT to specify the organization ID + # @return [Array<(Array, Integer, Hash)>] Array data, response status code and response headers + def list_blockmount_regions_with_http_info(opts = {}) + if @api_client.config.debugging + @api_client.config.logger.debug 'Calling API: VolumesApi.list_blockmount_regions ...' + end + # resource path + local_var_path = '/volumes/blockmount-regions' + + # query parameters + query_params = opts[:query_params] || {} + + # header parameters + header_params = opts[:header_params] || {} + # HTTP header 'Accept' (if needed) + header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept'] + header_params[:'X-Daytona-Organization-ID'] = opts[:'x_daytona_organization_id'] if !opts[:'x_daytona_organization_id'].nil? + + # form parameters + form_params = opts[:form_params] || {} + + # http body (model) + post_body = opts[:debug_body] + + # return_type + return_type = opts[:debug_return_type] || 'Array' + + # auth_names + auth_names = opts[:debug_auth_names] || ['bearer', 'oauth2'] + + new_options = opts.merge( + :operation => :"VolumesApi.list_blockmount_regions", + :header_params => header_params, + :query_params => query_params, + :form_params => form_params, + :body => post_body, + :auth_names => auth_names, + :return_type => return_type + ) + + data, status_code, headers = @api_client.call_api(:GET, local_var_path, new_options) + if @api_client.config.debugging + @api_client.config.logger.debug "API called: VolumesApi#list_blockmount_regions\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}" + end + return data, status_code, headers + end + + # List available hotmount regions + # @param [Hash] opts the optional parameters + # @option opts [String] :x_daytona_organization_id Use with JWT to specify the organization ID + # @return [Array] + def list_hotmount_regions(opts = {}) + data, _status_code, _headers = list_hotmount_regions_with_http_info(opts) + data + end + + # List available hotmount regions + # @param [Hash] opts the optional parameters + # @option opts [String] :x_daytona_organization_id Use with JWT to specify the organization ID + # @return [Array<(Array, Integer, Hash)>] Array data, response status code and response headers + def list_hotmount_regions_with_http_info(opts = {}) + if @api_client.config.debugging + @api_client.config.logger.debug 'Calling API: VolumesApi.list_hotmount_regions ...' + end + # resource path + local_var_path = '/volumes/hotmount-regions' + + # query parameters + query_params = opts[:query_params] || {} + + # header parameters + header_params = opts[:header_params] || {} + # HTTP header 'Accept' (if needed) + header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept'] + header_params[:'X-Daytona-Organization-ID'] = opts[:'x_daytona_organization_id'] if !opts[:'x_daytona_organization_id'].nil? + + # form parameters + form_params = opts[:form_params] || {} + + # http body (model) + post_body = opts[:debug_body] + + # return_type + return_type = opts[:debug_return_type] || 'Array' + + # auth_names + auth_names = opts[:debug_auth_names] || ['bearer', 'oauth2'] + + new_options = opts.merge( + :operation => :"VolumesApi.list_hotmount_regions", + :header_params => header_params, + :query_params => query_params, + :form_params => form_params, + :body => post_body, + :auth_names => auth_names, + :return_type => return_type + ) + + data, status_code, headers = @api_client.call_api(:GET, local_var_path, new_options) + if @api_client.config.debugging + @api_client.config.logger.debug "API called: VolumesApi#list_hotmount_regions\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}" + end + return data, status_code, headers + end + # List all volumes # @param [Hash] opts the optional parameters # @option opts [String] :x_daytona_organization_id Use with JWT to specify the organization ID diff --git a/api-client-ruby/lib/daytona_api_client/models/blockmount_conflict.rb b/api-client-ruby/lib/daytona_api_client/models/blockmount_conflict.rb new file mode 100644 index 000000000..0971f90e3 --- /dev/null +++ b/api-client-ruby/lib/daytona_api_client/models/blockmount_conflict.rb @@ -0,0 +1,239 @@ +=begin +#Daytona + +#Daytona AI platform API Docs + +The version of the OpenAPI document: 1.0 +Contact: support@daytona.com +Generated by: https://openapi-generator.tech +Generator version: 7.21.0 + +=end + +require 'date' +require 'time' + +module DaytonaApiClient + class BlockmountConflict < ApiModelBase + # The path (relative to the volume root) that was concurrently modified + attr_accessor :path + + # Which side won the merge: \"ours\" (the committing writer) or \"theirs\" (the state already in latest) + attr_accessor :winner + + # Why the winner won: \"mtime\" (newer change), \"tie\" (equal mtimes, committer won), \"modify-over-delete\", or \"type\" + attr_accessor :reason + + # Content hash of the committing writer’s version, when both sides were files + attr_accessor :ours_sha + + # Content hash of the concurrent version found in latest, when both sides were files + attr_accessor :theirs_sha + + # Attribute mapping from ruby-style variable name to JSON key. + def self.attribute_map + { + :'path' => :'path', + :'winner' => :'winner', + :'reason' => :'reason', + :'ours_sha' => :'oursSha', + :'theirs_sha' => :'theirsSha' + } + end + + # Returns attribute mapping this model knows about + def self.acceptable_attribute_map + attribute_map + end + + # Returns all the JSON keys this model knows about + def self.acceptable_attributes + acceptable_attribute_map.values + end + + # Attribute type mapping. + def self.openapi_types + { + :'path' => :'String', + :'winner' => :'String', + :'reason' => :'String', + :'ours_sha' => :'String', + :'theirs_sha' => :'String' + } + end + + # List of attributes with nullable: true + def self.openapi_nullable + Set.new([ + ]) + end + + # Initializes the object + # @param [Hash] attributes Model attributes in the form of hash + def initialize(attributes = {}) + if (!attributes.is_a?(Hash)) + fail ArgumentError, "The input argument (attributes) must be a hash in `DaytonaApiClient::BlockmountConflict` initialize method" + end + + # check to see if the attribute exists and convert string to symbol for hash key + acceptable_attribute_map = self.class.acceptable_attribute_map + attributes = attributes.each_with_object({}) { |(k, v), h| + if (!acceptable_attribute_map.key?(k.to_sym)) + fail ArgumentError, "`#{k}` is not a valid attribute in `DaytonaApiClient::BlockmountConflict`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect + end + h[k.to_sym] = v + } + + if attributes.key?(:'path') + self.path = attributes[:'path'] + else + self.path = nil + end + + if attributes.key?(:'winner') + self.winner = attributes[:'winner'] + else + self.winner = nil + end + + if attributes.key?(:'reason') + self.reason = attributes[:'reason'] + else + self.reason = nil + end + + if attributes.key?(:'ours_sha') + self.ours_sha = attributes[:'ours_sha'] + end + + if attributes.key?(:'theirs_sha') + self.theirs_sha = attributes[:'theirs_sha'] + end + end + + # Show invalid properties with the reasons. Usually used together with valid? + # @return Array for valid properties with the reasons + def list_invalid_properties + warn '[DEPRECATED] the `list_invalid_properties` method is obsolete' + invalid_properties = Array.new + if @path.nil? + invalid_properties.push('invalid value for "path", path cannot be nil.') + end + + if @winner.nil? + invalid_properties.push('invalid value for "winner", winner cannot be nil.') + end + + if @reason.nil? + invalid_properties.push('invalid value for "reason", reason cannot be nil.') + end + + invalid_properties + end + + # Check to see if the all the properties in the model are valid + # @return true if the model is valid + def valid? + warn '[DEPRECATED] the `valid?` method is obsolete' + return false if @path.nil? + return false if @winner.nil? + return false if @reason.nil? + true + end + + # Custom attribute writer method with validation + # @param [Object] path Value to be assigned + def path=(path) + if path.nil? + fail ArgumentError, 'path cannot be nil' + end + + @path = path + end + + # Custom attribute writer method with validation + # @param [Object] winner Value to be assigned + def winner=(winner) + if winner.nil? + fail ArgumentError, 'winner cannot be nil' + end + + @winner = winner + end + + # Custom attribute writer method with validation + # @param [Object] reason Value to be assigned + def reason=(reason) + if reason.nil? + fail ArgumentError, 'reason cannot be nil' + end + + @reason = reason + end + + # Checks equality by comparing each attribute. + # @param [Object] Object to be compared + def ==(o) + return true if self.equal?(o) + self.class == o.class && + path == o.path && + winner == o.winner && + reason == o.reason && + ours_sha == o.ours_sha && + theirs_sha == o.theirs_sha + end + + # @see the `==` method + # @param [Object] Object to be compared + def eql?(o) + self == o + end + + # Calculates hash code according to all attributes. + # @return [Integer] Hash code + def hash + [path, winner, reason, ours_sha, theirs_sha].hash + end + + # Builds the object from hash + # @param [Hash] attributes Model attributes in the form of hash + # @return [Object] Returns the model itself + def self.build_from_hash(attributes) + return nil unless attributes.is_a?(Hash) + attributes = attributes.transform_keys(&:to_sym) + transformed_hash = {} + openapi_types.each_pair do |key, type| + if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = nil + elsif type =~ /\AArray<(.*)>/i + # check to ensure the input is an array given that the attribute + # is documented as an array but the input is not + if attributes[attribute_map[key]].is_a?(Array) + transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) } + end + elsif !attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]]) + end + end + new(transformed_hash) + end + + # Returns the object in the form of hash + # @return [Hash] Returns the object in the form of hash + def to_hash + hash = {} + self.class.attribute_map.each_pair do |attr, param| + value = self.send(attr) + if value.nil? + is_nullable = self.class.openapi_nullable.include?(attr) + next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}")) + end + + hash[param] = _to_hash(value) + end + hash + end + + end + +end diff --git a/api-client-ruby/lib/daytona_api_client/models/create_volume.rb b/api-client-ruby/lib/daytona_api_client/models/create_volume.rb index d3fab6cba..bf6797dda 100644 --- a/api-client-ruby/lib/daytona_api_client/models/create_volume.rb +++ b/api-client-ruby/lib/daytona_api_client/models/create_volume.rb @@ -17,10 +17,40 @@ module DaytonaApiClient class CreateVolume < ApiModelBase attr_accessor :name + # The type of the volume. Defaults to legacy. + attr_accessor :type + + # The region to create the volume in. For blockmount volumes it selects the region-local CAS store the volume's data lives in — a performance/placement knob, not an attach restriction, so sandboxes in any region can attach the volume (colocation is just faster). Optional for blockmount: when omitted it defaults to the organization's default region (or the first region that offers blockmount). For hotmount volumes it selects the hotmount deployment region and defaults to an active region. Not allowed for legacy volumes. The volume's region is fixed for its lifetime. + attr_accessor :region + + class EnumAttributeValidator + attr_reader :datatype + attr_reader :allowable_values + + def initialize(datatype, allowable_values) + @allowable_values = allowable_values.map do |value| + case datatype.to_s + when /Integer/i + value.to_i + when /Float/i + value.to_f + else + value + end + end + end + + def valid?(value) + !value || allowable_values.include?(value) + end + end + # Attribute mapping from ruby-style variable name to JSON key. def self.attribute_map { - :'name' => :'name' + :'name' => :'name', + :'type' => :'type', + :'region' => :'region' } end @@ -37,7 +67,9 @@ def self.acceptable_attributes # Attribute type mapping. def self.openapi_types { - :'name' => :'String' + :'name' => :'String', + :'type' => :'VolumeType', + :'region' => :'String' } end @@ -68,6 +100,14 @@ def initialize(attributes = {}) else self.name = nil end + + if attributes.key?(:'type') + self.type = attributes[:'type'] + end + + if attributes.key?(:'region') + self.region = attributes[:'region'] + end end # Show invalid properties with the reasons. Usually used together with valid? @@ -105,7 +145,9 @@ def name=(name) def ==(o) return true if self.equal?(o) self.class == o.class && - name == o.name + name == o.name && + type == o.type && + region == o.region end # @see the `==` method @@ -117,7 +159,7 @@ def eql?(o) # Calculates hash code according to all attributes. # @return [Integer] Hash code def hash - [name].hash + [name, type, region].hash end # Builds the object from hash diff --git a/api-client-ruby/lib/daytona_api_client/models/create_volume_mount_token.rb b/api-client-ruby/lib/daytona_api_client/models/create_volume_mount_token.rb new file mode 100644 index 000000000..758020126 --- /dev/null +++ b/api-client-ruby/lib/daytona_api_client/models/create_volume_mount_token.rb @@ -0,0 +1,184 @@ +=begin +#Daytona + +#Daytona AI platform API Docs + +The version of the OpenAPI document: 1.0 +Contact: support@daytona.com +Generated by: https://openapi-generator.tech +Generator version: 7.21.0 + +=end + +require 'date' +require 'time' + +module DaytonaApiClient + class CreateVolumeMountToken < ApiModelBase + # The access mode for the mount. Defaults to rw. + attr_accessor :mode + + class EnumAttributeValidator + attr_reader :datatype + attr_reader :allowable_values + + def initialize(datatype, allowable_values) + @allowable_values = allowable_values.map do |value| + case datatype.to_s + when /Integer/i + value.to_i + when /Float/i + value.to_f + else + value + end + end + end + + def valid?(value) + !value || allowable_values.include?(value) + end + end + + # Attribute mapping from ruby-style variable name to JSON key. + def self.attribute_map + { + :'mode' => :'mode' + } + end + + # Returns attribute mapping this model knows about + def self.acceptable_attribute_map + attribute_map + end + + # Returns all the JSON keys this model knows about + def self.acceptable_attributes + acceptable_attribute_map.values + end + + # Attribute type mapping. + def self.openapi_types + { + :'mode' => :'String' + } + end + + # List of attributes with nullable: true + def self.openapi_nullable + Set.new([ + ]) + end + + # Initializes the object + # @param [Hash] attributes Model attributes in the form of hash + def initialize(attributes = {}) + if (!attributes.is_a?(Hash)) + fail ArgumentError, "The input argument (attributes) must be a hash in `DaytonaApiClient::CreateVolumeMountToken` initialize method" + end + + # check to see if the attribute exists and convert string to symbol for hash key + acceptable_attribute_map = self.class.acceptable_attribute_map + attributes = attributes.each_with_object({}) { |(k, v), h| + if (!acceptable_attribute_map.key?(k.to_sym)) + fail ArgumentError, "`#{k}` is not a valid attribute in `DaytonaApiClient::CreateVolumeMountToken`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect + end + h[k.to_sym] = v + } + + if attributes.key?(:'mode') + self.mode = attributes[:'mode'] + else + self.mode = 'rw' + end + end + + # Show invalid properties with the reasons. Usually used together with valid? + # @return Array for valid properties with the reasons + def list_invalid_properties + warn '[DEPRECATED] the `list_invalid_properties` method is obsolete' + invalid_properties = Array.new + invalid_properties + end + + # Check to see if the all the properties in the model are valid + # @return true if the model is valid + def valid? + warn '[DEPRECATED] the `valid?` method is obsolete' + mode_validator = EnumAttributeValidator.new('String', ["rw", "ro", "unknown_default_open_api"]) + return false unless mode_validator.valid?(@mode) + true + end + + # Custom attribute writer method checking allowed values (enum). + # @param [Object] mode Object to be assigned + def mode=(mode) + validator = EnumAttributeValidator.new('String', ["rw", "ro", "unknown_default_open_api"]) + unless validator.valid?(mode) + fail ArgumentError, "invalid value for \"mode\", must be one of #{validator.allowable_values}." + end + @mode = mode + end + + # Checks equality by comparing each attribute. + # @param [Object] Object to be compared + def ==(o) + return true if self.equal?(o) + self.class == o.class && + mode == o.mode + end + + # @see the `==` method + # @param [Object] Object to be compared + def eql?(o) + self == o + end + + # Calculates hash code according to all attributes. + # @return [Integer] Hash code + def hash + [mode].hash + end + + # Builds the object from hash + # @param [Hash] attributes Model attributes in the form of hash + # @return [Object] Returns the model itself + def self.build_from_hash(attributes) + return nil unless attributes.is_a?(Hash) + attributes = attributes.transform_keys(&:to_sym) + transformed_hash = {} + openapi_types.each_pair do |key, type| + if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = nil + elsif type =~ /\AArray<(.*)>/i + # check to ensure the input is an array given that the attribute + # is documented as an array but the input is not + if attributes[attribute_map[key]].is_a?(Array) + transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) } + end + elsif !attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]]) + end + end + new(transformed_hash) + end + + # Returns the object in the form of hash + # @return [Hash] Returns the object in the form of hash + def to_hash + hash = {} + self.class.attribute_map.each_pair do |attr, param| + value = self.send(attr) + if value.nil? + is_nullable = self.class.openapi_nullable.include?(attr) + next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}")) + end + + hash[param] = _to_hash(value) + end + hash + end + + end + +end diff --git a/api-client-ruby/lib/daytona_api_client/models/hotmount_region.rb b/api-client-ruby/lib/daytona_api_client/models/hotmount_region.rb new file mode 100644 index 000000000..087351ea3 --- /dev/null +++ b/api-client-ruby/lib/daytona_api_client/models/hotmount_region.rb @@ -0,0 +1,219 @@ +=begin +#Daytona + +#Daytona AI platform API Docs + +The version of the OpenAPI document: 1.0 +Contact: support@daytona.com +Generated by: https://openapi-generator.tech +Generator version: 7.21.0 + +=end + +require 'date' +require 'time' + +module DaytonaApiClient + class HotmountRegion < ApiModelBase + # Stable region id + attr_accessor :region + + # User-facing region name + attr_accessor :label + + # Geo hint used for default region selection + attr_accessor :geo + + # Attribute mapping from ruby-style variable name to JSON key. + def self.attribute_map + { + :'region' => :'region', + :'label' => :'label', + :'geo' => :'geo' + } + end + + # Returns attribute mapping this model knows about + def self.acceptable_attribute_map + attribute_map + end + + # Returns all the JSON keys this model knows about + def self.acceptable_attributes + acceptable_attribute_map.values + end + + # Attribute type mapping. + def self.openapi_types + { + :'region' => :'String', + :'label' => :'String', + :'geo' => :'String' + } + end + + # List of attributes with nullable: true + def self.openapi_nullable + Set.new([ + ]) + end + + # Initializes the object + # @param [Hash] attributes Model attributes in the form of hash + def initialize(attributes = {}) + if (!attributes.is_a?(Hash)) + fail ArgumentError, "The input argument (attributes) must be a hash in `DaytonaApiClient::HotmountRegion` initialize method" + end + + # check to see if the attribute exists and convert string to symbol for hash key + acceptable_attribute_map = self.class.acceptable_attribute_map + attributes = attributes.each_with_object({}) { |(k, v), h| + if (!acceptable_attribute_map.key?(k.to_sym)) + fail ArgumentError, "`#{k}` is not a valid attribute in `DaytonaApiClient::HotmountRegion`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect + end + h[k.to_sym] = v + } + + if attributes.key?(:'region') + self.region = attributes[:'region'] + else + self.region = nil + end + + if attributes.key?(:'label') + self.label = attributes[:'label'] + else + self.label = nil + end + + if attributes.key?(:'geo') + self.geo = attributes[:'geo'] + else + self.geo = nil + end + end + + # Show invalid properties with the reasons. Usually used together with valid? + # @return Array for valid properties with the reasons + def list_invalid_properties + warn '[DEPRECATED] the `list_invalid_properties` method is obsolete' + invalid_properties = Array.new + if @region.nil? + invalid_properties.push('invalid value for "region", region cannot be nil.') + end + + if @label.nil? + invalid_properties.push('invalid value for "label", label cannot be nil.') + end + + if @geo.nil? + invalid_properties.push('invalid value for "geo", geo cannot be nil.') + end + + invalid_properties + end + + # Check to see if the all the properties in the model are valid + # @return true if the model is valid + def valid? + warn '[DEPRECATED] the `valid?` method is obsolete' + return false if @region.nil? + return false if @label.nil? + return false if @geo.nil? + true + end + + # Custom attribute writer method with validation + # @param [Object] region Value to be assigned + def region=(region) + if region.nil? + fail ArgumentError, 'region cannot be nil' + end + + @region = region + end + + # Custom attribute writer method with validation + # @param [Object] label Value to be assigned + def label=(label) + if label.nil? + fail ArgumentError, 'label cannot be nil' + end + + @label = label + end + + # Custom attribute writer method with validation + # @param [Object] geo Value to be assigned + def geo=(geo) + if geo.nil? + fail ArgumentError, 'geo cannot be nil' + end + + @geo = geo + end + + # Checks equality by comparing each attribute. + # @param [Object] Object to be compared + def ==(o) + return true if self.equal?(o) + self.class == o.class && + region == o.region && + label == o.label && + geo == o.geo + end + + # @see the `==` method + # @param [Object] Object to be compared + def eql?(o) + self == o + end + + # Calculates hash code according to all attributes. + # @return [Integer] Hash code + def hash + [region, label, geo].hash + end + + # Builds the object from hash + # @param [Hash] attributes Model attributes in the form of hash + # @return [Object] Returns the model itself + def self.build_from_hash(attributes) + return nil unless attributes.is_a?(Hash) + attributes = attributes.transform_keys(&:to_sym) + transformed_hash = {} + openapi_types.each_pair do |key, type| + if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = nil + elsif type =~ /\AArray<(.*)>/i + # check to ensure the input is an array given that the attribute + # is documented as an array but the input is not + if attributes[attribute_map[key]].is_a?(Array) + transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) } + end + elsif !attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]]) + end + end + new(transformed_hash) + end + + # Returns the object in the form of hash + # @return [Hash] Returns the object in the form of hash + def to_hash + hash = {} + self.class.attribute_map.each_pair do |attr, param| + value = self.send(attr) + if value.nil? + is_nullable = self.class.openapi_nullable.include?(attr) + next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}")) + end + + hash[param] = _to_hash(value) + end + hash + end + + end + +end diff --git a/api-client-ruby/lib/daytona_api_client/models/region.rb b/api-client-ruby/lib/daytona_api_client/models/region.rb index b03257a40..7aefc7452 100644 --- a/api-client-ruby/lib/daytona_api_client/models/region.rb +++ b/api-client-ruby/lib/daytona_api_client/models/region.rb @@ -42,6 +42,9 @@ class Region < ApiModelBase # Snapshot Manager URL for the region attr_accessor :snapshot_manager_url + # Whether blockmount volumes are supported in this region + attr_accessor :blockmount_enabled + class EnumAttributeValidator attr_reader :datatype attr_reader :allowable_values @@ -75,7 +78,8 @@ def self.attribute_map :'updated_at' => :'updatedAt', :'proxy_url' => :'proxyUrl', :'ssh_gateway_url' => :'sshGatewayUrl', - :'snapshot_manager_url' => :'snapshotManagerUrl' + :'snapshot_manager_url' => :'snapshotManagerUrl', + :'blockmount_enabled' => :'blockmountEnabled' } end @@ -100,7 +104,8 @@ def self.openapi_types :'updated_at' => :'String', :'proxy_url' => :'String', :'ssh_gateway_url' => :'String', - :'snapshot_manager_url' => :'String' + :'snapshot_manager_url' => :'String', + :'blockmount_enabled' => :'Boolean' } end @@ -110,7 +115,7 @@ def self.openapi_nullable :'organization_id', :'proxy_url', :'ssh_gateway_url', - :'snapshot_manager_url' + :'snapshot_manager_url', ]) end @@ -175,6 +180,12 @@ def initialize(attributes = {}) if attributes.key?(:'snapshot_manager_url') self.snapshot_manager_url = attributes[:'snapshot_manager_url'] end + + if attributes.key?(:'blockmount_enabled') + self.blockmount_enabled = attributes[:'blockmount_enabled'] + else + self.blockmount_enabled = nil + end end # Show invalid properties with the reasons. Usually used together with valid? @@ -202,6 +213,10 @@ def list_invalid_properties invalid_properties.push('invalid value for "updated_at", updated_at cannot be nil.') end + if @blockmount_enabled.nil? + invalid_properties.push('invalid value for "blockmount_enabled", blockmount_enabled cannot be nil.') + end + invalid_properties end @@ -214,6 +229,7 @@ def valid? return false if @region_type.nil? return false if @created_at.nil? return false if @updated_at.nil? + return false if @blockmount_enabled.nil? true end @@ -267,6 +283,16 @@ def updated_at=(updated_at) @updated_at = updated_at end + # Custom attribute writer method with validation + # @param [Object] blockmount_enabled Value to be assigned + def blockmount_enabled=(blockmount_enabled) + if blockmount_enabled.nil? + fail ArgumentError, 'blockmount_enabled cannot be nil' + end + + @blockmount_enabled = blockmount_enabled + end + # Checks equality by comparing each attribute. # @param [Object] Object to be compared def ==(o) @@ -280,7 +306,8 @@ def ==(o) updated_at == o.updated_at && proxy_url == o.proxy_url && ssh_gateway_url == o.ssh_gateway_url && - snapshot_manager_url == o.snapshot_manager_url + snapshot_manager_url == o.snapshot_manager_url && + blockmount_enabled == o.blockmount_enabled end # @see the `==` method @@ -292,7 +319,7 @@ def eql?(o) # Calculates hash code according to all attributes. # @return [Integer] Hash code def hash - [id, name, organization_id, region_type, created_at, updated_at, proxy_url, ssh_gateway_url, snapshot_manager_url].hash + [id, name, organization_id, region_type, created_at, updated_at, proxy_url, ssh_gateway_url, snapshot_manager_url, blockmount_enabled].hash end # Builds the object from hash diff --git a/api-client-ruby/lib/daytona_api_client/models/sandbox_volume.rb b/api-client-ruby/lib/daytona_api_client/models/sandbox_volume.rb index 1f60242cd..6492135a6 100644 --- a/api-client-ruby/lib/daytona_api_client/models/sandbox_volume.rb +++ b/api-client-ruby/lib/daytona_api_client/models/sandbox_volume.rb @@ -24,12 +24,70 @@ class SandboxVolume < ApiModelBase # Optional subpath within the volume to mount. When specified, only this S3 prefix will be accessible. When omitted, the entire volume is mounted. attr_accessor :subpath + # The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy. + attr_accessor :volume_type + + # The organization that owns the volume. Forwarded to the runner to isolate the S3 prefix. Set only for blockmount volumes. + attr_accessor :organization_id + + # The logical size of the volume in gigabytes, used by the runner as the per-sandbox scratch quota. Set only for blockmount volumes. + attr_accessor :size_in_gb + + # The region the blockmount volume's data lives in. Forwarded to the runner so it can fetch the region's store credentials over its authenticated channel. Set only for blockmount volumes. + attr_accessor :region + + # The S3 endpoint of the CAS store the blockmount volume's data lives in, resolved from the volume's region. Forwarded to the runner so cross-region attaches reach the right bucket. Omitted when the volume's region has no store configured (runner falls back to its env store). Credentials are never sent here — the runner fetches them by region. Set only for blockmount volumes. + attr_accessor :s3_endpoint + + # The S3 region of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes. + attr_accessor :s3_region + + # The S3 bucket of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes. + attr_accessor :s3_bucket + + # The S3 key prefix of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes. + attr_accessor :s3_prefix + + # Whether the CAS store uses path-style S3 addressing. Set only for blockmount volumes. + attr_accessor :s3_path_style + + class EnumAttributeValidator + attr_reader :datatype + attr_reader :allowable_values + + def initialize(datatype, allowable_values) + @allowable_values = allowable_values.map do |value| + case datatype.to_s + when /Integer/i + value.to_i + when /Float/i + value.to_f + else + value + end + end + end + + def valid?(value) + !value || allowable_values.include?(value) + end + end + # Attribute mapping from ruby-style variable name to JSON key. def self.attribute_map { :'volume_id' => :'volumeId', :'mount_path' => :'mountPath', - :'subpath' => :'subpath' + :'subpath' => :'subpath', + :'volume_type' => :'volumeType', + :'organization_id' => :'organizationId', + :'size_in_gb' => :'sizeInGb', + :'region' => :'region', + :'s3_endpoint' => :'s3Endpoint', + :'s3_region' => :'s3Region', + :'s3_bucket' => :'s3Bucket', + :'s3_prefix' => :'s3Prefix', + :'s3_path_style' => :'s3PathStyle' } end @@ -48,7 +106,16 @@ def self.openapi_types { :'volume_id' => :'String', :'mount_path' => :'String', - :'subpath' => :'String' + :'subpath' => :'String', + :'volume_type' => :'VolumeType', + :'organization_id' => :'String', + :'size_in_gb' => :'Float', + :'region' => :'String', + :'s3_endpoint' => :'String', + :'s3_region' => :'String', + :'s3_bucket' => :'String', + :'s3_prefix' => :'String', + :'s3_path_style' => :'Boolean' } end @@ -89,6 +156,42 @@ def initialize(attributes = {}) if attributes.key?(:'subpath') self.subpath = attributes[:'subpath'] end + + if attributes.key?(:'volume_type') + self.volume_type = attributes[:'volume_type'] + end + + if attributes.key?(:'organization_id') + self.organization_id = attributes[:'organization_id'] + end + + if attributes.key?(:'size_in_gb') + self.size_in_gb = attributes[:'size_in_gb'] + end + + if attributes.key?(:'region') + self.region = attributes[:'region'] + end + + if attributes.key?(:'s3_endpoint') + self.s3_endpoint = attributes[:'s3_endpoint'] + end + + if attributes.key?(:'s3_region') + self.s3_region = attributes[:'s3_region'] + end + + if attributes.key?(:'s3_bucket') + self.s3_bucket = attributes[:'s3_bucket'] + end + + if attributes.key?(:'s3_prefix') + self.s3_prefix = attributes[:'s3_prefix'] + end + + if attributes.key?(:'s3_path_style') + self.s3_path_style = attributes[:'s3_path_style'] + end end # Show invalid properties with the reasons. Usually used together with valid? @@ -143,7 +246,16 @@ def ==(o) self.class == o.class && volume_id == o.volume_id && mount_path == o.mount_path && - subpath == o.subpath + subpath == o.subpath && + volume_type == o.volume_type && + organization_id == o.organization_id && + size_in_gb == o.size_in_gb && + region == o.region && + s3_endpoint == o.s3_endpoint && + s3_region == o.s3_region && + s3_bucket == o.s3_bucket && + s3_prefix == o.s3_prefix && + s3_path_style == o.s3_path_style end # @see the `==` method @@ -155,7 +267,7 @@ def eql?(o) # Calculates hash code according to all attributes. # @return [Integer] Hash code def hash - [volume_id, mount_path, subpath].hash + [volume_id, mount_path, subpath, volume_type, organization_id, size_in_gb, region, s3_endpoint, s3_region, s3_bucket, s3_prefix, s3_path_style].hash end # Builds the object from hash diff --git a/api-client-ruby/lib/daytona_api_client/models/volume_dto.rb b/api-client-ruby/lib/daytona_api_client/models/volume_dto.rb index d9d1ce0a4..3862f4c27 100644 --- a/api-client-ruby/lib/daytona_api_client/models/volume_dto.rb +++ b/api-client-ruby/lib/daytona_api_client/models/volume_dto.rb @@ -24,6 +24,24 @@ class VolumeDto < ApiModelBase # Organization ID attr_accessor :organization_id + # Volume type + attr_accessor :type + + # The per-sandbox scratch quota in GB. Set only for blockmount volumes. + attr_accessor :size_in_gb + + # The region the volume's data lives in. For blockmount volumes this selects the region-local CAS store (a performance/placement knob — sandboxes in any region can attach it, colocation is just faster). For hotmount volumes this is the hotmount deployment region. Set for blockmount and hotmount volumes. + attr_accessor :region + + # The hotmount sharing mode (false = single-writer write-back, true = multi-writer synchronous). Set only for hotmount volumes. + attr_accessor :shared + + # The id of the most recent committed manifest, read-through from the reconciliation store. Set only for blockmount volumes that have been committed at least once. + attr_accessor :last_manifest_id + + # Conflicts recorded on the latest manifest — concurrent same-path modifications the store resolved (last-change-wins). Read-through from the store. Set only for blockmount volumes. + attr_accessor :conflicts + # Volume state attr_accessor :state @@ -67,6 +85,12 @@ def self.attribute_map :'id' => :'id', :'name' => :'name', :'organization_id' => :'organizationId', + :'type' => :'type', + :'size_in_gb' => :'sizeInGb', + :'region' => :'region', + :'shared' => :'shared', + :'last_manifest_id' => :'lastManifestId', + :'conflicts' => :'conflicts', :'state' => :'state', :'created_at' => :'createdAt', :'updated_at' => :'updatedAt', @@ -91,6 +115,12 @@ def self.openapi_types :'id' => :'String', :'name' => :'String', :'organization_id' => :'String', + :'type' => :'VolumeType', + :'size_in_gb' => :'Float', + :'region' => :'String', + :'shared' => :'Boolean', + :'last_manifest_id' => :'String', + :'conflicts' => :'Array', :'state' => :'VolumeState', :'created_at' => :'String', :'updated_at' => :'String', @@ -102,6 +132,11 @@ def self.openapi_types # List of attributes with nullable: true def self.openapi_nullable Set.new([ + :'size_in_gb', + :'region', + :'shared', + :'last_manifest_id', + :'conflicts', :'last_used_at', :'error_reason' ]) @@ -141,6 +176,34 @@ def initialize(attributes = {}) self.organization_id = nil end + if attributes.key?(:'type') + self.type = attributes[:'type'] + else + self.type = nil + end + + if attributes.key?(:'size_in_gb') + self.size_in_gb = attributes[:'size_in_gb'] + end + + if attributes.key?(:'region') + self.region = attributes[:'region'] + end + + if attributes.key?(:'shared') + self.shared = attributes[:'shared'] + end + + if attributes.key?(:'last_manifest_id') + self.last_manifest_id = attributes[:'last_manifest_id'] + end + + if attributes.key?(:'conflicts') + if (value = attributes[:'conflicts']).is_a?(Array) + self.conflicts = value + end + end + if attributes.key?(:'state') self.state = attributes[:'state'] else @@ -187,6 +250,10 @@ def list_invalid_properties invalid_properties.push('invalid value for "organization_id", organization_id cannot be nil.') end + if @type.nil? + invalid_properties.push('invalid value for "type", type cannot be nil.') + end + if @state.nil? invalid_properties.push('invalid value for "state", state cannot be nil.') end @@ -209,6 +276,7 @@ def valid? return false if @id.nil? return false if @name.nil? return false if @organization_id.nil? + return false if @type.nil? return false if @state.nil? return false if @created_at.nil? return false if @updated_at.nil? @@ -245,6 +313,16 @@ def organization_id=(organization_id) @organization_id = organization_id end + # Custom attribute writer method with validation + # @param [Object] type Value to be assigned + def type=(type) + if type.nil? + fail ArgumentError, 'type cannot be nil' + end + + @type = type + end + # Custom attribute writer method with validation # @param [Object] state Value to be assigned def state=(state) @@ -283,6 +361,12 @@ def ==(o) id == o.id && name == o.name && organization_id == o.organization_id && + type == o.type && + size_in_gb == o.size_in_gb && + region == o.region && + shared == o.shared && + last_manifest_id == o.last_manifest_id && + conflicts == o.conflicts && state == o.state && created_at == o.created_at && updated_at == o.updated_at && @@ -299,7 +383,7 @@ def eql?(o) # Calculates hash code according to all attributes. # @return [Integer] Hash code def hash - [id, name, organization_id, state, created_at, updated_at, last_used_at, error_reason].hash + [id, name, organization_id, type, size_in_gb, region, shared, last_manifest_id, conflicts, state, created_at, updated_at, last_used_at, error_reason].hash end # Builds the object from hash diff --git a/api-client-ruby/lib/daytona_api_client/models/volume_mount_token_dto.rb b/api-client-ruby/lib/daytona_api_client/models/volume_mount_token_dto.rb new file mode 100644 index 000000000..2946c9b02 --- /dev/null +++ b/api-client-ruby/lib/daytona_api_client/models/volume_mount_token_dto.rb @@ -0,0 +1,311 @@ +=begin +#Daytona + +#Daytona AI platform API Docs + +The version of the OpenAPI document: 1.0 +Contact: support@daytona.com +Generated by: https://openapi-generator.tech +Generator version: 7.21.0 + +=end + +require 'date' +require 'time' + +module DaytonaApiClient + class VolumeMountTokenDto < ApiModelBase + # The short-lived macaroon token the in-sandbox agent uses to mount the volume + attr_accessor :token + + # The token expiration timestamp + attr_accessor :expires_at + + # The hotmount region the volume lives in + attr_accessor :region + + # The gateway gRPC endpoint (SEAWEED_GATEWAY_GRPC) + attr_accessor :gateway_grpc + + # The gateway HTTPS endpoint (SEAWEED_GATEWAY_HTTP) + attr_accessor :gateway_http + + # The binaries bucket base URL used to bootstrap the mount (SEAWEED_BINARIES_URL) + attr_accessor :binaries_url + + # The pinned client binary version to use (SEAWEED_VERSION), when the region pins one + attr_accessor :version + + # Attribute mapping from ruby-style variable name to JSON key. + def self.attribute_map + { + :'token' => :'token', + :'expires_at' => :'expiresAt', + :'region' => :'region', + :'gateway_grpc' => :'gatewayGrpc', + :'gateway_http' => :'gatewayHttp', + :'binaries_url' => :'binariesUrl', + :'version' => :'version' + } + end + + # Returns attribute mapping this model knows about + def self.acceptable_attribute_map + attribute_map + end + + # Returns all the JSON keys this model knows about + def self.acceptable_attributes + acceptable_attribute_map.values + end + + # Attribute type mapping. + def self.openapi_types + { + :'token' => :'String', + :'expires_at' => :'String', + :'region' => :'String', + :'gateway_grpc' => :'String', + :'gateway_http' => :'String', + :'binaries_url' => :'String', + :'version' => :'String' + } + end + + # List of attributes with nullable: true + def self.openapi_nullable + Set.new([ + :'version' + ]) + end + + # Initializes the object + # @param [Hash] attributes Model attributes in the form of hash + def initialize(attributes = {}) + if (!attributes.is_a?(Hash)) + fail ArgumentError, "The input argument (attributes) must be a hash in `DaytonaApiClient::VolumeMountTokenDto` initialize method" + end + + # check to see if the attribute exists and convert string to symbol for hash key + acceptable_attribute_map = self.class.acceptable_attribute_map + attributes = attributes.each_with_object({}) { |(k, v), h| + if (!acceptable_attribute_map.key?(k.to_sym)) + fail ArgumentError, "`#{k}` is not a valid attribute in `DaytonaApiClient::VolumeMountTokenDto`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect + end + h[k.to_sym] = v + } + + if attributes.key?(:'token') + self.token = attributes[:'token'] + else + self.token = nil + end + + if attributes.key?(:'expires_at') + self.expires_at = attributes[:'expires_at'] + else + self.expires_at = nil + end + + if attributes.key?(:'region') + self.region = attributes[:'region'] + else + self.region = nil + end + + if attributes.key?(:'gateway_grpc') + self.gateway_grpc = attributes[:'gateway_grpc'] + else + self.gateway_grpc = nil + end + + if attributes.key?(:'gateway_http') + self.gateway_http = attributes[:'gateway_http'] + else + self.gateway_http = nil + end + + if attributes.key?(:'binaries_url') + self.binaries_url = attributes[:'binaries_url'] + else + self.binaries_url = nil + end + + if attributes.key?(:'version') + self.version = attributes[:'version'] + end + end + + # Show invalid properties with the reasons. Usually used together with valid? + # @return Array for valid properties with the reasons + def list_invalid_properties + warn '[DEPRECATED] the `list_invalid_properties` method is obsolete' + invalid_properties = Array.new + if @token.nil? + invalid_properties.push('invalid value for "token", token cannot be nil.') + end + + if @expires_at.nil? + invalid_properties.push('invalid value for "expires_at", expires_at cannot be nil.') + end + + if @region.nil? + invalid_properties.push('invalid value for "region", region cannot be nil.') + end + + if @gateway_grpc.nil? + invalid_properties.push('invalid value for "gateway_grpc", gateway_grpc cannot be nil.') + end + + if @gateway_http.nil? + invalid_properties.push('invalid value for "gateway_http", gateway_http cannot be nil.') + end + + if @binaries_url.nil? + invalid_properties.push('invalid value for "binaries_url", binaries_url cannot be nil.') + end + + invalid_properties + end + + # Check to see if the all the properties in the model are valid + # @return true if the model is valid + def valid? + warn '[DEPRECATED] the `valid?` method is obsolete' + return false if @token.nil? + return false if @expires_at.nil? + return false if @region.nil? + return false if @gateway_grpc.nil? + return false if @gateway_http.nil? + return false if @binaries_url.nil? + true + end + + # Custom attribute writer method with validation + # @param [Object] token Value to be assigned + def token=(token) + if token.nil? + fail ArgumentError, 'token cannot be nil' + end + + @token = token + end + + # Custom attribute writer method with validation + # @param [Object] expires_at Value to be assigned + def expires_at=(expires_at) + if expires_at.nil? + fail ArgumentError, 'expires_at cannot be nil' + end + + @expires_at = expires_at + end + + # Custom attribute writer method with validation + # @param [Object] region Value to be assigned + def region=(region) + if region.nil? + fail ArgumentError, 'region cannot be nil' + end + + @region = region + end + + # Custom attribute writer method with validation + # @param [Object] gateway_grpc Value to be assigned + def gateway_grpc=(gateway_grpc) + if gateway_grpc.nil? + fail ArgumentError, 'gateway_grpc cannot be nil' + end + + @gateway_grpc = gateway_grpc + end + + # Custom attribute writer method with validation + # @param [Object] gateway_http Value to be assigned + def gateway_http=(gateway_http) + if gateway_http.nil? + fail ArgumentError, 'gateway_http cannot be nil' + end + + @gateway_http = gateway_http + end + + # Custom attribute writer method with validation + # @param [Object] binaries_url Value to be assigned + def binaries_url=(binaries_url) + if binaries_url.nil? + fail ArgumentError, 'binaries_url cannot be nil' + end + + @binaries_url = binaries_url + end + + # Checks equality by comparing each attribute. + # @param [Object] Object to be compared + def ==(o) + return true if self.equal?(o) + self.class == o.class && + token == o.token && + expires_at == o.expires_at && + region == o.region && + gateway_grpc == o.gateway_grpc && + gateway_http == o.gateway_http && + binaries_url == o.binaries_url && + version == o.version + end + + # @see the `==` method + # @param [Object] Object to be compared + def eql?(o) + self == o + end + + # Calculates hash code according to all attributes. + # @return [Integer] Hash code + def hash + [token, expires_at, region, gateway_grpc, gateway_http, binaries_url, version].hash + end + + # Builds the object from hash + # @param [Hash] attributes Model attributes in the form of hash + # @return [Object] Returns the model itself + def self.build_from_hash(attributes) + return nil unless attributes.is_a?(Hash) + attributes = attributes.transform_keys(&:to_sym) + transformed_hash = {} + openapi_types.each_pair do |key, type| + if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = nil + elsif type =~ /\AArray<(.*)>/i + # check to ensure the input is an array given that the attribute + # is documented as an array but the input is not + if attributes[attribute_map[key]].is_a?(Array) + transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) } + end + elsif !attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]]) + end + end + new(transformed_hash) + end + + # Returns the object in the form of hash + # @return [Hash] Returns the object in the form of hash + def to_hash + hash = {} + self.class.attribute_map.each_pair do |attr, param| + value = self.send(attr) + if value.nil? + is_nullable = self.class.openapi_nullable.include?(attr) + next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}")) + end + + hash[param] = _to_hash(value) + end + hash + end + + end + +end diff --git a/api-client-ruby/lib/daytona_api_client/models/volume_type.rb b/api-client-ruby/lib/daytona_api_client/models/volume_type.rb new file mode 100644 index 000000000..b373c88b0 --- /dev/null +++ b/api-client-ruby/lib/daytona_api_client/models/volume_type.rb @@ -0,0 +1,42 @@ +=begin +#Daytona + +#Daytona AI platform API Docs + +The version of the OpenAPI document: 1.0 +Contact: support@daytona.com +Generated by: https://openapi-generator.tech +Generator version: 7.21.0 + +=end + +require 'date' +require 'time' + +module DaytonaApiClient + class VolumeType + LEGACY = "legacy".freeze + HOTMOUNT = "hotmount".freeze + BLOCKMOUNT = "blockmount".freeze + UNKNOWN_DEFAULT_OPEN_API = "unknown_default_open_api".freeze + + def self.all_vars + @all_vars ||= [LEGACY, HOTMOUNT, BLOCKMOUNT, UNKNOWN_DEFAULT_OPEN_API].freeze + end + + # Builds the enum from string + # @param [String] The enum value in the form of the string + # @return [String] The enum value + def self.build_from_hash(value) + new.build_from_hash(value) + end + + # Builds the enum from string + # @param [String] The enum value in the form of the string + # @return [String] The enum value + def build_from_hash(value) + return value if VolumeType.all_vars.include?(value) + UNKNOWN_DEFAULT_OPEN_API + end + end +end diff --git a/api-client/src/.openapi-generator/FILES b/api-client/src/.openapi-generator/FILES index 4c91b758d..665f8e412 100644 --- a/api-client/src/.openapi-generator/FILES +++ b/api-client/src/.openapi-generator/FILES @@ -33,6 +33,7 @@ models/api-key-list.ts models/api-key-response.ts models/audit-log.ts models/available-sandbox-class.ts +models/blockmount-conflict.ts models/build-info.ts models/command.ts models/completion-context.ts @@ -61,6 +62,7 @@ models/create-secret.ts models/create-session-request.ts models/create-snapshot.ts models/create-user.ts +models/create-volume-mount-token.ts models/create-volume.ts models/date-filter.ts models/daytona-configuration.ts @@ -86,6 +88,7 @@ models/gpu-type.ts models/health-controller-check200-response-info-value.ts models/health-controller-check200-response.ts models/health-controller-check503-response.ts +models/hotmount-region.ts models/index.ts models/int-filter.ts models/job-status.ts @@ -215,7 +218,9 @@ models/user-home-dir-response.ts models/user-public-key.ts models/user.ts models/volume-dto.ts +models/volume-mount-token-dto.ts models/volume-state.ts +models/volume-type.ts models/webhook-app-portal-access.ts models/webhook-event.ts models/webhook-initialization-status.ts diff --git a/api-client/src/api/volumes-api.ts b/api-client/src/api/volumes-api.ts index 2252cc834..ae13f5334 100644 --- a/api-client/src/api/volumes-api.ts +++ b/api-client/src/api/volumes-api.ts @@ -24,7 +24,15 @@ import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError // @ts-ignore import type { CreateVolume } from '../models'; // @ts-ignore +import type { CreateVolumeMountToken } from '../models'; +// @ts-ignore +import type { HotmountRegion } from '../models'; +// @ts-ignore +import type { Region } from '../models'; +// @ts-ignore import type { VolumeDto } from '../models'; +// @ts-ignore +import type { VolumeMountTokenDto } from '../models'; /** * VolumesApi - axios parameter creator */ @@ -75,6 +83,53 @@ export const VolumesApiAxiosParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, + /** + * + * @summary Create a mount token for a hotmount volume + * @param {string} volumeId ID of the volume + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {CreateVolumeMountToken} [createVolumeMountToken] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createVolumeMountToken: async (volumeId: string, xDaytonaOrganizationID?: string, createVolumeMountToken?: CreateVolumeMountToken, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'volumeId' is not null or undefined + assertParamExists('createVolumeMountToken', 'volumeId', volumeId) + const localVarPath = `/volumes/{volumeId}/mount-token` + .replace(`{${"volumeId"}}`, encodeURIComponent(String(volumeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + // authentication oauth2 required + + localVarHeaderParameter['Content-Type'] = 'application/json'; + localVarHeaderParameter['Accept'] = 'application/json'; + + if (xDaytonaOrganizationID != null) { + localVarHeaderParameter['X-Daytona-Organization-ID'] = String(xDaytonaOrganizationID); + } + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(createVolumeMountToken, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @summary Delete volume @@ -206,6 +261,86 @@ export const VolumesApiAxiosParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, + /** + * + * @summary List regions where blockmount volumes can be created + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listBlockmountRegions: async (xDaytonaOrganizationID?: string, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/volumes/blockmount-regions`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + // authentication oauth2 required + + localVarHeaderParameter['Accept'] = 'application/json'; + + if (xDaytonaOrganizationID != null) { + localVarHeaderParameter['X-Daytona-Organization-ID'] = String(xDaytonaOrganizationID); + } + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary List available hotmount regions + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listHotmountRegions: async (xDaytonaOrganizationID?: string, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/volumes/hotmount-regions`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + // authentication oauth2 required + + localVarHeaderParameter['Accept'] = 'application/json'; + + if (xDaytonaOrganizationID != null) { + localVarHeaderParameter['X-Daytona-Organization-ID'] = String(xDaytonaOrganizationID); + } + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @summary List all volumes @@ -274,6 +409,21 @@ export const VolumesApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['VolumesApi.createVolume']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * + * @summary Create a mount token for a hotmount volume + * @param {string} volumeId ID of the volume + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {CreateVolumeMountToken} [createVolumeMountToken] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createVolumeMountToken(volumeId: string, xDaytonaOrganizationID?: string, createVolumeMountToken?: CreateVolumeMountToken, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createVolumeMountToken(volumeId, xDaytonaOrganizationID, createVolumeMountToken, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['VolumesApi.createVolumeMountToken']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * * @summary Delete volume @@ -316,6 +466,32 @@ export const VolumesApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['VolumesApi.getVolumeByName']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * + * @summary List regions where blockmount volumes can be created + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listBlockmountRegions(xDaytonaOrganizationID?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listBlockmountRegions(xDaytonaOrganizationID, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['VolumesApi.listBlockmountRegions']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * + * @summary List available hotmount regions + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listHotmountRegions(xDaytonaOrganizationID?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listHotmountRegions(xDaytonaOrganizationID, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['VolumesApi.listHotmountRegions']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * * @summary List all volumes @@ -350,6 +526,18 @@ export const VolumesApiFactory = function (configuration?: Configuration, basePa createVolume(createVolume: CreateVolume, xDaytonaOrganizationID?: string, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.createVolume(createVolume, xDaytonaOrganizationID, options).then((request) => request(axios, basePath)); }, + /** + * + * @summary Create a mount token for a hotmount volume + * @param {string} volumeId ID of the volume + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {CreateVolumeMountToken} [createVolumeMountToken] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createVolumeMountToken(volumeId: string, xDaytonaOrganizationID?: string, createVolumeMountToken?: CreateVolumeMountToken, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.createVolumeMountToken(volumeId, xDaytonaOrganizationID, createVolumeMountToken, options).then((request) => request(axios, basePath)); + }, /** * * @summary Delete volume @@ -383,6 +571,26 @@ export const VolumesApiFactory = function (configuration?: Configuration, basePa getVolumeByName(name: string, xDaytonaOrganizationID?: string, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.getVolumeByName(name, xDaytonaOrganizationID, options).then((request) => request(axios, basePath)); }, + /** + * + * @summary List regions where blockmount volumes can be created + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listBlockmountRegions(xDaytonaOrganizationID?: string, options?: RawAxiosRequestConfig): AxiosPromise> { + return localVarFp.listBlockmountRegions(xDaytonaOrganizationID, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary List available hotmount regions + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listHotmountRegions(xDaytonaOrganizationID?: string, options?: RawAxiosRequestConfig): AxiosPromise> { + return localVarFp.listHotmountRegions(xDaytonaOrganizationID, options).then((request) => request(axios, basePath)); + }, /** * * @summary List all volumes @@ -413,6 +621,19 @@ export class VolumesApi extends BaseAPI { return VolumesApiFp(this.configuration).createVolume(createVolume, xDaytonaOrganizationID, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @summary Create a mount token for a hotmount volume + * @param {string} volumeId ID of the volume + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {CreateVolumeMountToken} [createVolumeMountToken] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public createVolumeMountToken(volumeId: string, xDaytonaOrganizationID?: string, createVolumeMountToken?: CreateVolumeMountToken, options?: RawAxiosRequestConfig) { + return VolumesApiFp(this.configuration).createVolumeMountToken(volumeId, xDaytonaOrganizationID, createVolumeMountToken, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @summary Delete volume @@ -449,6 +670,28 @@ export class VolumesApi extends BaseAPI { return VolumesApiFp(this.configuration).getVolumeByName(name, xDaytonaOrganizationID, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @summary List regions where blockmount volumes can be created + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public listBlockmountRegions(xDaytonaOrganizationID?: string, options?: RawAxiosRequestConfig) { + return VolumesApiFp(this.configuration).listBlockmountRegions(xDaytonaOrganizationID, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary List available hotmount regions + * @param {string} [xDaytonaOrganizationID] Use with JWT to specify the organization ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public listHotmountRegions(xDaytonaOrganizationID?: string, options?: RawAxiosRequestConfig) { + return VolumesApiFp(this.configuration).listHotmountRegions(xDaytonaOrganizationID, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @summary List all volumes diff --git a/api-client/src/models/blockmount-conflict.ts b/api-client/src/models/blockmount-conflict.ts new file mode 100644 index 000000000..6720ad17f --- /dev/null +++ b/api-client/src/models/blockmount-conflict.ts @@ -0,0 +1,39 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +export interface BlockmountConflict { + /** + * The path (relative to the volume root) that was concurrently modified + */ + 'path': string; + /** + * Which side won the merge: \"ours\" (the committing writer) or \"theirs\" (the state already in latest) + */ + 'winner': string; + /** + * Why the winner won: \"mtime\" (newer change), \"tie\" (equal mtimes, committer won), \"modify-over-delete\", or \"type\" + */ + 'reason': string; + /** + * Content hash of the committing writer’s version, when both sides were files + */ + 'oursSha'?: string; + /** + * Content hash of the concurrent version found in latest, when both sides were files + */ + 'theirsSha'?: string; +} + diff --git a/api-client/src/models/create-volume-mount-token.ts b/api-client/src/models/create-volume-mount-token.ts new file mode 100644 index 000000000..be73a92fe --- /dev/null +++ b/api-client/src/models/create-volume-mount-token.ts @@ -0,0 +1,32 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +export interface CreateVolumeMountToken { + /** + * The access mode for the mount. Defaults to rw. + */ + 'mode'?: CreateVolumeMountTokenModeEnum; +} + +export const CreateVolumeMountTokenModeEnum = { + RW: 'rw', + RO: 'ro', + UNKNOWN_DEFAULT_OPEN_API: '11184809', +} as const; + +export type CreateVolumeMountTokenModeEnum = typeof CreateVolumeMountTokenModeEnum[keyof typeof CreateVolumeMountTokenModeEnum]; + + diff --git a/api-client/src/models/create-volume.ts b/api-client/src/models/create-volume.ts index 704bf6912..b272a5170 100644 --- a/api-client/src/models/create-volume.ts +++ b/api-client/src/models/create-volume.ts @@ -13,8 +13,21 @@ */ +// May contain unused imports in some cases +// @ts-ignore +import type { VolumeType } from './volume-type'; export interface CreateVolume { 'name': string; + /** + * The type of the volume. Defaults to legacy. + */ + 'type'?: VolumeType; + /** + * The region to create the volume in. For blockmount volumes it selects the region-local CAS store the volume\'s data lives in — a performance/placement knob, not an attach restriction, so sandboxes in any region can attach the volume (colocation is just faster). Optional for blockmount: when omitted it defaults to the organization\'s default region (or the first region that offers blockmount). For hotmount volumes it selects the hotmount deployment region and defaults to an active region. Not allowed for legacy volumes. The volume\'s region is fixed for its lifetime. + */ + 'region'?: string; } + + diff --git a/api-client/src/models/hotmount-region.ts b/api-client/src/models/hotmount-region.ts new file mode 100644 index 000000000..f17b60e3d --- /dev/null +++ b/api-client/src/models/hotmount-region.ts @@ -0,0 +1,31 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +export interface HotmountRegion { + /** + * Stable region id + */ + 'region': string; + /** + * User-facing region name + */ + 'label': string; + /** + * Geo hint used for default region selection + */ + 'geo': string; +} + diff --git a/api-client/src/models/index.ts b/api-client/src/models/index.ts index 861d55a0f..2e170fbff 100644 --- a/api-client/src/models/index.ts +++ b/api-client/src/models/index.ts @@ -7,6 +7,7 @@ export * from './api-key-list'; export * from './api-key-response'; export * from './audit-log'; export * from './available-sandbox-class'; +export * from './blockmount-conflict'; export * from './build-info'; export * from './command'; export * from './completion-context'; @@ -36,6 +37,7 @@ export * from './create-session-request'; export * from './create-snapshot'; export * from './create-user'; export * from './create-volume'; +export * from './create-volume-mount-token'; export * from './date-filter'; export * from './daytona-configuration'; export * from './display-info-response'; @@ -60,6 +62,7 @@ export * from './gpu-type'; export * from './health-controller-check200-response'; export * from './health-controller-check200-response-info-value'; export * from './health-controller-check503-response'; +export * from './hotmount-region'; export * from './int-filter'; export * from './job'; export * from './job-status'; @@ -188,7 +191,9 @@ export * from './user'; export * from './user-home-dir-response'; export * from './user-public-key'; export * from './volume-dto'; +export * from './volume-mount-token-dto'; export * from './volume-state'; +export * from './volume-type'; export * from './webhook-app-portal-access'; export * from './webhook-event'; export * from './webhook-initialization-status'; diff --git a/api-client/src/models/region.ts b/api-client/src/models/region.ts index a69a72d44..589bb5fad 100644 --- a/api-client/src/models/region.ts +++ b/api-client/src/models/region.ts @@ -54,6 +54,10 @@ export interface Region { * Snapshot Manager URL for the region */ 'snapshotManagerUrl'?: string | null; + /** + * Whether blockmount volumes are supported in this region + */ + 'blockmountEnabled': boolean; } diff --git a/api-client/src/models/sandbox-volume.ts b/api-client/src/models/sandbox-volume.ts index 78683c1ab..49e91acd3 100644 --- a/api-client/src/models/sandbox-volume.ts +++ b/api-client/src/models/sandbox-volume.ts @@ -13,6 +13,9 @@ */ +// May contain unused imports in some cases +// @ts-ignore +import type { VolumeType } from './volume-type'; export interface SandboxVolume { /** @@ -27,5 +30,43 @@ export interface SandboxVolume { * Optional subpath within the volume to mount. When specified, only this S3 prefix will be accessible. When omitted, the entire volume is mounted. */ 'subpath'?: string; + /** + * The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy. + */ + 'volumeType'?: VolumeType; + /** + * The organization that owns the volume. Forwarded to the runner to isolate the S3 prefix. Set only for blockmount volumes. + */ + 'organizationId'?: string; + /** + * The logical size of the volume in gigabytes, used by the runner as the per-sandbox scratch quota. Set only for blockmount volumes. + */ + 'sizeInGb'?: number; + /** + * The region the blockmount volume\'s data lives in. Forwarded to the runner so it can fetch the region\'s store credentials over its authenticated channel. Set only for blockmount volumes. + */ + 'region'?: string; + /** + * The S3 endpoint of the CAS store the blockmount volume\'s data lives in, resolved from the volume\'s region. Forwarded to the runner so cross-region attaches reach the right bucket. Omitted when the volume\'s region has no store configured (runner falls back to its env store). Credentials are never sent here — the runner fetches them by region. Set only for blockmount volumes. + */ + 's3Endpoint'?: string; + /** + * The S3 region of the CAS store the blockmount volume\'s data lives in. Set only for blockmount volumes. + */ + 's3Region'?: string; + /** + * The S3 bucket of the CAS store the blockmount volume\'s data lives in. Set only for blockmount volumes. + */ + 's3Bucket'?: string; + /** + * The S3 key prefix of the CAS store the blockmount volume\'s data lives in. Set only for blockmount volumes. + */ + 's3Prefix'?: string; + /** + * Whether the CAS store uses path-style S3 addressing. Set only for blockmount volumes. + */ + 's3PathStyle'?: boolean; } + + diff --git a/api-client/src/models/volume-dto.ts b/api-client/src/models/volume-dto.ts index 59b925803..38be3d76d 100644 --- a/api-client/src/models/volume-dto.ts +++ b/api-client/src/models/volume-dto.ts @@ -13,9 +13,15 @@ */ +// May contain unused imports in some cases +// @ts-ignore +import type { BlockmountConflict } from './blockmount-conflict'; // May contain unused imports in some cases // @ts-ignore import type { VolumeState } from './volume-state'; +// May contain unused imports in some cases +// @ts-ignore +import type { VolumeType } from './volume-type'; export interface VolumeDto { /** @@ -30,6 +36,30 @@ export interface VolumeDto { * Organization ID */ 'organizationId': string; + /** + * Volume type + */ + 'type': VolumeType; + /** + * The per-sandbox scratch quota in GB. Set only for blockmount volumes. + */ + 'sizeInGb'?: number | null; + /** + * The region the volume\'s data lives in. For blockmount volumes this selects the region-local CAS store (a performance/placement knob — sandboxes in any region can attach it, colocation is just faster). For hotmount volumes this is the hotmount deployment region. Set for blockmount and hotmount volumes. + */ + 'region'?: string | null; + /** + * The hotmount sharing mode (false = single-writer write-back, true = multi-writer synchronous). Set only for hotmount volumes. + */ + 'shared'?: boolean | null; + /** + * The id of the most recent committed manifest, read-through from the reconciliation store. Set only for blockmount volumes that have been committed at least once. + */ + 'lastManifestId'?: string | null; + /** + * Conflicts recorded on the latest manifest — concurrent same-path modifications the store resolved (last-change-wins). Read-through from the store. Set only for blockmount volumes. + */ + 'conflicts'?: Array | null; /** * Volume state */ diff --git a/api-client/src/models/volume-mount-token-dto.ts b/api-client/src/models/volume-mount-token-dto.ts new file mode 100644 index 000000000..96389091f --- /dev/null +++ b/api-client/src/models/volume-mount-token-dto.ts @@ -0,0 +1,47 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +export interface VolumeMountTokenDto { + /** + * The short-lived macaroon token the in-sandbox agent uses to mount the volume + */ + 'token': string; + /** + * The token expiration timestamp + */ + 'expiresAt': string; + /** + * The hotmount region the volume lives in + */ + 'region': string; + /** + * The gateway gRPC endpoint (SEAWEED_GATEWAY_GRPC) + */ + 'gatewayGrpc': string; + /** + * The gateway HTTPS endpoint (SEAWEED_GATEWAY_HTTP) + */ + 'gatewayHttp': string; + /** + * The binaries bucket base URL used to bootstrap the mount (SEAWEED_BINARIES_URL) + */ + 'binariesUrl': string; + /** + * The pinned client binary version to use (SEAWEED_VERSION), when the region pins one + */ + 'version'?: string | null; +} + diff --git a/api-client/src/models/volume-type.ts b/api-client/src/models/volume-type.ts new file mode 100644 index 000000000..4ff45773e --- /dev/null +++ b/api-client/src/models/volume-type.ts @@ -0,0 +1,31 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Daytona + * Daytona AI platform API Docs + * + * The version of the OpenAPI document: 1.0 + * Contact: support@daytona.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy. + */ + +export const VolumeType = { + LEGACY: 'legacy', + HOTMOUNT: 'hotmount', + BLOCKMOUNT: 'blockmount', + UNKNOWN_DEFAULT_OPEN_API: '11184809', +} as const; + +export type VolumeType = typeof VolumeType[keyof typeof VolumeType]; + + + diff --git a/cli/cmd/volume/create.go b/cli/cmd/volume/create.go index e7a25a11d..b072586f4 100644 --- a/cli/cmd/volume/create.go +++ b/cli/cmd/volume/create.go @@ -27,9 +27,23 @@ var CreateCmd = &cobra.Command{ return err } - volume, res, err := apiClient.VolumesAPI.CreateVolume(ctx).CreateVolume(apiclient.CreateVolume{ + createVolume := apiclient.CreateVolume{ Name: args[0], - }).Execute() + } + + if typeFlag != "" { + volumeType, err := apiclient.NewVolumeTypeFromValue(typeFlag) + if err != nil { + return fmt.Errorf("invalid volume type %q: must be one of legacy, hotmount, blockmount", typeFlag) + } + createVolume.Type = volumeType + } + + if cmd.Flags().Changed("region") { + createVolume.Region = ®ionFlag + } + + volume, res, err := apiClient.VolumesAPI.CreateVolume(ctx).CreateVolume(createVolume).Execute() if err != nil { return apiclient_cli.HandleErrorResponse(res, err) } @@ -39,8 +53,12 @@ var CreateCmd = &cobra.Command{ }, } -var sizeFlag int32 +var ( + typeFlag string + regionFlag string +) func init() { - CreateCmd.Flags().Int32VarP(&sizeFlag, "size", "s", 10, "Size of the volume in GB") + CreateCmd.Flags().StringVarP(&typeFlag, "type", "t", "", "Volume type (legacy, hotmount, blockmount)") + CreateCmd.Flags().StringVarP(®ionFlag, "region", "r", "", "Region to create the volume in (blockmount: selects the region-local store its data lives in, defaults to the org's default region; sandboxes in any region can attach it. Selects the deployment region for hotmount)") } diff --git a/cli/views/volume/info.go b/cli/views/volume/info.go index 1ccd56829..171ded10a 100644 --- a/cli/views/volume/info.go +++ b/cli/views/volume/info.go @@ -21,6 +21,16 @@ func RenderInfo(volume *apiclient.VolumeDto, forceUnstyled bool) { output += "\n" output += getInfoLine(nameLabel, volume.Name) + "\n" output += getInfoLine("ID", volume.Id) + "\n" + output += getInfoLine("Type", string(volume.Type)) + "\n" + if volume.SizeInGb.IsSet() && volume.SizeInGb.Get() != nil { + output += getInfoLine("Size", fmt.Sprintf("%g GB", *volume.SizeInGb.Get())) + "\n" + } + if volume.Region.IsSet() && volume.Region.Get() != nil { + output += getInfoLine("Region", *volume.Region.Get()) + "\n" + } + if volume.Shared.IsSet() && volume.Shared.Get() != nil { + output += getInfoLine("Shared", fmt.Sprintf("%t", *volume.Shared.Get())) + "\n" + } output += getInfoLine("State", getStateLabel(volume.State)) + "\n" output += getInfoLine("Created", util.GetTimeSinceLabelFromString(volume.CreatedAt)) + "\n" diff --git a/cli/views/volume/list.go b/cli/views/volume/list.go index 1eff4fb6f..7ee902352 100644 --- a/cli/views/volume/list.go +++ b/cli/views/volume/list.go @@ -14,6 +14,7 @@ import ( type RowData struct { Name string + Type string State string Size string Created string @@ -27,7 +28,7 @@ func ListVolumes(volumeList []apiclient.VolumeDto, activeOrganizationName *strin SortVolumes(&volumeList) - headers := []string{"Volume", "State", "Size", "Created"} + headers := []string{"Volume", "Type", "State", "Size", "Created"} data := [][]string{} @@ -60,8 +61,14 @@ func SortVolumes(volumeList *[]apiclient.VolumeDto) { } func getTableRowData(volume apiclient.VolumeDto) *RowData { - rowData := RowData{"", "", "", ""} + rowData := RowData{"", "", "", "", ""} rowData.Name = volume.Name + util.AdditionalPropertyPadding + rowData.Type = string(volume.Type) + if volume.SizeInGb.IsSet() && volume.SizeInGb.Get() != nil { + rowData.Size = fmt.Sprintf("%g GB", *volume.SizeInGb.Get()) + } else { + rowData.Size = "-" + } rowData.State = getStateLabel(volume.State) rowData.Created = util.GetTimeSinceLabelFromString(volume.CreatedAt) return &rowData @@ -80,6 +87,7 @@ func renderUnstyledList(volumeList []apiclient.VolumeDto) { func getRowFromRowData(rowData RowData) []string { row := []string{ common.NameStyle.Render(rowData.Name), + common.DefaultRowDataStyle.Render(rowData.Type), rowData.State, common.DefaultRowDataStyle.Render(rowData.Size), common.DefaultRowDataStyle.Render(rowData.Created), diff --git a/examples/typescript/blockmount/index.ts b/examples/typescript/blockmount/index.ts new file mode 100644 index 000000000..9736e6f3e --- /dev/null +++ b/examples/typescript/blockmount/index.ts @@ -0,0 +1,162 @@ +/** + * BLOCKMOUNT shared performance volume — concurrent two-writer merge demo. + * + * Two sandboxes mount the SAME blockmount volume at the same time (blockmount + * volumes allow concurrent multi-sandbox use): + * + * sandbox A writes: fileA.txt, fileB.txt, fileC.txt + * sandbox B writes: fileB.txt, fileX.txt, fileZ.txt + * + * Each sandbox works on its own runner-local scratch at native disk speed; a + * background loop commits changes to the shared S3 store (plus a final commit + * when the sandbox is deleted). The store delta-merges both trees: + * + * - disjoint files (fileA, fileC, fileX, fileZ) are all kept — the union + * - the overlapping fileB.txt is resolved per-file LAST-CHANGE-WINS by mtime + * (independent of commit order), and the conflict is recorded on the + * manifest with both content hashes — nothing is destroyed + * + * Running sandboxes do NOT see each other's writes automatically — each works + * on its private scratch. `sandbox.pullVolumes()` is the explicit + * synchronization point: it commits the sandbox's local changes and applies the + * volume's latest merged state in place, WITHOUT stopping either sandbox. + */ +import { Daytona, Sandbox, VolumeType } from '@daytona/sdk' + +// Poll until a deleted sandbox is fully destroyed on the runner. Deletion is async; +// the runner flushes its final blockmount commit as part of teardown, so "destroyed" +// is the barrier that guarantees this sandbox's writes are durable in the store. +async function waitUntilDeleted(sandbox: Sandbox, timeoutMs = 90_000) { + const start = Date.now() + while (Date.now() - start < timeoutMs) { + try { + await sandbox.refreshData() + } catch { + return // 404 — the record is gone, so it is fully destroyed + } + if (sandbox.state === 'destroyed') { + return + } + await new Promise((resolve) => setTimeout(resolve, 500)) + } + throw new Error(`Timed out waiting for sandbox ${sandbox.id} to be destroyed`) +} + +async function main() { + // A blockmount volume's data lives in one region (chosen at creation, fixed for its lifetime). + // Region is a performance/placement knob, NOT an attach restriction: sandboxes in any region can + // mount the volume. We colocate the sandboxes with the volume's region below because that gives + // the fastest commit/materialize (cross-region works but pays the distance to the store). + // Discover a region a superadmin has enabled for blockmount. + const bootstrap = new Daytona() + const regions = await bootstrap.volume.listBlockmountRegions() + if (regions.length === 0) { + throw new Error('No region offers blockmount volumes. Ask a superadmin to enable one.') + } + const region = regions[0].id + console.log(`Using blockmount region: ${regions[0].name} (${region})`) + + // Scope the client to that region so the sandboxes below land in it (the SDK takes the target + // region from the client config), colocating them with the volume for best performance. + const daytona = new Daytona({ target: region }) + + // Create the shared performance volume in the chosen region. + const volume = await daytona.volume.get('blockmount-merge-demo', true, { + type: VolumeType.BLOCKMOUNT, + region, + }) + console.log(`Volume ${volume.name} (${volume.id}) type=${volume.type} state=${volume.state}`) + + const mountDir = '/home/daytona/shared' + + // Both sandboxes mount the same volume CONCURRENTLY — no exclusivity error; each gets its own + // local scratch and the store reconciles them. They land in the volume's region via the client. + const [sandboxA, sandboxB] = await Promise.all([ + daytona.create({ language: 'typescript', volumes: [{ volumeId: volume.id, mountPath: mountDir }] }), + daytona.create({ language: 'typescript', volumes: [{ volumeId: volume.id, mountPath: mountDir }] }), + ]) + console.log(`Sandbox A: ${sandboxA.id}`) + console.log(`Sandbox B: ${sandboxB.id}`) + + // Sandbox A writes fileA, fileB, fileC. + await sandboxA.fs.uploadFile(Buffer.from('from-A'), `${mountDir}/fileA.txt`) + await sandboxA.fs.uploadFile(Buffer.from('fileB-written-by-A'), `${mountDir}/fileB.txt`) + await sandboxA.fs.uploadFile(Buffer.from('from-A'), `${mountDir}/fileC.txt`) + + // Small gap so B's fileB.txt edit has a strictly newer mtime than A's (mtime + // resolution is what decides the merge, not commit or pull order). + await new Promise((resolve) => setTimeout(resolve, 1500)) + + // Sandbox B writes fileB (the same path!), fileX, fileZ. B's fileB.txt edit + // happens AFTER A's, so B's version has the newer mtime and will win the merge. + await sandboxB.fs.uploadFile(Buffer.from('fileB-written-by-B-newer'), `${mountDir}/fileB.txt`) + await sandboxB.fs.uploadFile(Buffer.from('from-B'), `${mountDir}/fileX.txt`) + await sandboxB.fs.uploadFile(Buffer.from('from-B'), `${mountDir}/fileZ.txt`) + + // A and B do NOT see each other's writes yet — each works on its private scratch. + console.log( + 'A sees (its own local scratch only):', + (await sandboxA.fs.listFiles(mountDir)).map((f) => f.name), + ) + console.log( + 'B sees (its own local scratch only):', + (await sandboxB.fs.listFiles(mountDir)).map((f) => f.name), + ) + + // EXPLICIT PULL — no restart, both sandboxes keep running. Each pull first + // commits the sandbox's local changes (so they join the merge), then applies + // the latest merged state onto the live mount. Pulls only see what has been + // committed at that moment, so full two-way convergence takes: A pull (commits + // A's writes), B pull (commits B's writes + applies A's), A pull again + // (applies B's). + console.log('A pull #1:', await sandboxA.pullVolumes()) + console.log('B pull: ', await sandboxB.pullVolumes()) + console.log('A pull #2:', await sandboxA.pullVolumes()) + + // Both now see the union of both writers' files. + console.log( + 'A sees after pull:', + (await sandboxA.fs.listFiles(mountDir)) + .map((f) => f.name) + .filter((n) => n !== 'lost+found') + .sort(), + ) + console.log( + 'B sees after pull:', + (await sandboxB.fs.listFiles(mountDir)) + .map((f) => f.name) + .filter((n) => n !== 'lost+found') + .sort(), + ) + // -> both: fileA.txt, fileB.txt, fileC.txt, fileX.txt, fileZ.txt + + // The overlapping fileB.txt converged to B's version (newer mtime) on BOTH sides: + // A's copy was replaced by pull #2; B's copy was preserved (it was the winner). + const fileBonA = await sandboxA.fs.downloadFile(`${mountDir}/fileB.txt`) + const fileBonB = await sandboxB.fs.downloadFile(`${mountDir}/fileB.txt`) + console.log(`fileB.txt on A: "${fileBonA.toString()}"`) + console.log(`fileB.txt on B: "${fileBonB.toString()}"`) + // -> both: "fileB-written-by-B-newer" — the newer mtime won, on both sandboxes + + // The reconciliation evidence is surfaced on the volume itself: the latest + // manifest id and the conflicts the merge resolved (winner, reason, both shas). + const refreshed = await daytona.volume.get(volume.name) + console.log(`lastManifestId: ${refreshed.lastManifestId}`) + for (const conflict of refreshed.conflicts ?? []) { + console.log( + `conflict: path=${conflict.path} winner=${conflict.winner} reason=${conflict.reason}` + + ` oursSha=${conflict.oursSha?.slice(0, 12)} theirsSha=${conflict.theirsSha?.slice(0, 12)}`, + ) + } + // The losing version of fileB.txt is NOT destroyed: both content hashes remain + // in the store's CAS and are recoverable from the previous manifest. + + // Cleanup. Wait for the sandboxes to be fully destroyed before deleting the + // volume, otherwise the volume still counts as in use. + await daytona.delete(sandboxA) + await daytona.delete(sandboxB) + await Promise.all([waitUntilDeleted(sandboxA), waitUntilDeleted(sandboxB)]) + await daytona.volume.delete(volume) +} + +main() diff --git a/examples/typescript/hotmount/index.ts b/examples/typescript/hotmount/index.ts new file mode 100644 index 000000000..1701b9592 --- /dev/null +++ b/examples/typescript/hotmount/index.ts @@ -0,0 +1,100 @@ +import { Daytona, Sandbox, Volume, VolumeType } from '@daytona/sdk' + +// The hotmount bootstrap (init.sh) needs `curl`. Daytona's standard images ship with it; +// this makes the example work on minimal snapshots too. +async function ensureCurl(sandbox: Sandbox): Promise { + const res = await sandbox.process.executeCommand( + 'command -v curl >/dev/null 2>&1 || (command -v apt-get >/dev/null 2>&1 && ' + + 'apt-get update -qq && apt-get install -y -qq curl ca-certificates) || ' + + '(command -v apk >/dev/null 2>&1 && apk add --no-cache curl ca-certificates)', + ) + if (res.exitCode !== 0) { + throw new Error(`Failed to ensure curl is installed: ${res.result}`) + } +} + +// Poll until a freshly created hotmount volume finishes provisioning (org + routing +// created on the region's control-server). Only a `ready` volume can mint mount tokens. +async function waitUntilReady(daytona: Daytona, volume: Volume, timeoutMs = 60_000): Promise { + const start = Date.now() + let current = volume + while (current.state !== 'ready') { + if (current.state === 'error') { + throw new Error(`Volume ${current.name} failed to provision: ${current.errorReason ?? 'unknown error'}`) + } + if (Date.now() - start > timeoutMs) { + throw new Error(`Timed out waiting for volume ${current.name} to become ready (state: ${current.state})`) + } + await new Promise((resolve) => setTimeout(resolve, 1000)) + current = await daytona.volume.get(current.name) + } + return current +} + +async function main() { + const daytona = new Daytona() + + // List the hotmount regions available to your organization. + // A region is fixed for the volume's lifetime, so pick one up front. + const regions = await daytona.volume.listHotmountRegions() + console.log('Available hotmount regions:', regions) + + // Pick a region close to where the workload runs. This dev environment is in + // Europe, so prefer the EU region (fall back to whatever is available). + const region = (regions.find((r) => r.geo === 'eu') ?? regions[0])?.region + console.log('Using hotmount region:', region) + + // Create a hotmount volume (or fetch it if it already exists). + // Hotmount volumes are network-attached POSIX filesystems that are + // mounted into a running sandbox on demand. They support synchronous + // multi-writer access, so several sandboxes can mount the same volume + // concurrently. + const volume = await daytona.volume.get('my-hotmount-shared-demo', true, { + type: VolumeType.HOTMOUNT, + region, + }) + console.log('Volume:', volume.id, 'region:', volume.region, 'shared:', volume.shared) + + // Wait for the volume to finish provisioning before mounting it. + await waitUntilReady(daytona, volume) + console.log('Volume is ready') + + // Hotmount volumes are not attached at create time. Start a sandbox first, + // then mount the volume into it wherever you like. + const mountDir = '/home/daytona/hotmount' + const sandbox1 = await daytona.create({ language: 'typescript' }) + await ensureCurl(sandbox1) + await sandbox1.mountVolume(volume, mountDir) + console.log('Mounted volume in sandbox1') + + // Write a file through the hotmount filesystem. + const newFile = `${mountDir}/hello.txt` + await sandbox1.fs.uploadFile(Buffer.from('Hello from hotmount!'), newFile) + console.log('Wrote hello.txt via sandbox1') + + // Mount the same volume in a second sandbox: it reads the same network filesystem, + // so it sees the file written by sandbox1 (once the write-back has drained to the gateway). + const sandbox2 = await daytona.create({ language: 'typescript' }) + await ensureCurl(sandbox2) + await sandbox2.mountVolume(volume, mountDir) + console.log('Mounted volume in sandbox2') + + let contents: Buffer | undefined + for (let attempt = 0; attempt < 20; attempt++) { + try { + contents = await sandbox2.fs.downloadFile(newFile) + break + } catch { + await new Promise((resolve) => setTimeout(resolve, 1000)) + } + } + console.log('Files visible in sandbox2:', await sandbox2.fs.listFiles(mountDir)) + console.log('File contents read by sandbox2:', contents ? contents.toString() : '') + + // Cleanup + await daytona.delete(sandbox1) + await daytona.delete(sandbox2) + // await daytona.volume.delete(volume) +} + +main() diff --git a/openapi-specs/api.json b/openapi-specs/api.json index 7f7860732..3c7079be3 100644 --- a/openapi-specs/api.json +++ b/openapi-specs/api.json @@ -10910,6 +10910,100 @@ ] } }, + "/volumes/hotmount-regions": { + "get": { + "operationId": "listHotmountRegions", + "parameters": [ + { + "name": "X-Daytona-Organization-ID", + "in": "header", + "description": "Use with JWT to specify the organization ID", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of active hotmount regions selectable at volume creation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HotmountRegion" + } + } + } + } + } + }, + "security": [ + { + "bearer": [] + }, + { + "oauth2": [ + "openid", + "profile", + "email" + ] + } + ], + "summary": "List available hotmount regions", + "tags": [ + "volumes" + ] + } + }, + "/volumes/blockmount-regions": { + "get": { + "operationId": "listBlockmountRegions", + "parameters": [ + { + "name": "X-Daytona-Organization-ID", + "in": "header", + "description": "Use with JWT to specify the organization ID", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of regions that support blockmount volumes", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Region" + } + } + } + } + } + }, + "security": [ + { + "bearer": [] + }, + { + "oauth2": [ + "openid", + "profile", + "email" + ] + } + ], + "summary": "List regions where blockmount volumes can be created", + "tags": [ + "volumes" + ] + } + }, "/volumes/{volumeId}": { "get": { "operationId": "getVolume", @@ -11010,6 +11104,69 @@ ] } }, + "/volumes/{volumeId}/mount-token": { + "post": { + "operationId": "createVolumeMountToken", + "parameters": [ + { + "name": "X-Daytona-Organization-ID", + "in": "header", + "description": "Use with JWT to specify the organization ID", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "volumeId", + "required": true, + "in": "path", + "description": "ID of the volume", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateVolumeMountToken" + } + } + } + }, + "responses": { + "200": { + "description": "The mount token has been successfully created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VolumeMountTokenDto" + } + } + } + } + }, + "security": [ + { + "bearer": [] + }, + { + "oauth2": [ + "openid", + "profile", + "email" + ] + } + ], + "summary": "Create a mount token for a hotmount volume", + "tags": [ + "volumes" + ] + } + }, "/volumes/by-name/{name}": { "get": { "operationId": "getVolumeByName", @@ -15824,6 +15981,11 @@ "description": "Snapshot Manager URL for the region", "example": "http://snapshot-manager.example.com", "nullable": true + }, + "blockmountEnabled": { + "type": "boolean", + "description": "Whether blockmount volumes are supported in this region", + "example": false } }, "required": [ @@ -15831,7 +15993,8 @@ "name", "regionType", "createdAt", - "updatedAt" + "updatedAt", + "blockmountEnabled" ] }, "CreateRegion": { @@ -16305,6 +16468,15 @@ "nextCursor" ] }, + "VolumeType": { + "type": "string", + "enum": [ + "legacy", + "hotmount", + "blockmount" + ], + "description": "The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy." + }, "SandboxVolume": { "type": "object", "properties": { @@ -16322,6 +16494,50 @@ "type": "string", "description": "Optional subpath within the volume to mount. When specified, only this S3 prefix will be accessible. When omitted, the entire volume is mounted.", "example": "users/alice" + }, + "volumeType": { + "description": "The type of the volume. Resolved from the referenced volume on sandbox create; the runner uses it to choose how to mount the volume. Absent values are treated as legacy.", + "example": "legacy", + "allOf": [ + { + "$ref": "#/components/schemas/VolumeType" + } + ] + }, + "organizationId": { + "type": "string", + "description": "The organization that owns the volume. Forwarded to the runner to isolate the S3 prefix. Set only for blockmount volumes.", + "example": "org_123" + }, + "sizeInGb": { + "type": "number", + "description": "The logical size of the volume in gigabytes, used by the runner as the per-sandbox scratch quota. Set only for blockmount volumes.", + "example": 10 + }, + "region": { + "type": "string", + "description": "The region the blockmount volume's data lives in. Forwarded to the runner so it can fetch the region's store credentials over its authenticated channel. Set only for blockmount volumes.", + "example": "us" + }, + "s3Endpoint": { + "type": "string", + "description": "The S3 endpoint of the CAS store the blockmount volume's data lives in, resolved from the volume's region. Forwarded to the runner so cross-region attaches reach the right bucket. Omitted when the volume's region has no store configured (runner falls back to its env store). Credentials are never sent here — the runner fetches them by region. Set only for blockmount volumes." + }, + "s3Region": { + "type": "string", + "description": "The S3 region of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes." + }, + "s3Bucket": { + "type": "string", + "description": "The S3 bucket of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes." + }, + "s3Prefix": { + "type": "string", + "description": "The S3 key prefix of the CAS store the blockmount volume's data lives in. Set only for blockmount volumes." + }, + "s3PathStyle": { + "type": "boolean", + "description": "Whether the CAS store uses path-style S3 addressing. Set only for blockmount volumes." } }, "required": [ @@ -19586,6 +19802,38 @@ "enabled" ] }, + "BlockmountConflict": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "The path (relative to the volume root) that was concurrently modified" + }, + "winner": { + "type": "string", + "description": "Which side won the merge: \"ours\" (the committing writer) or \"theirs\" (the state already in latest)", + "example": "ours" + }, + "reason": { + "type": "string", + "description": "Why the winner won: \"mtime\" (newer change), \"tie\" (equal mtimes, committer won), \"modify-over-delete\", or \"type\"", + "example": "mtime" + }, + "oursSha": { + "type": "string", + "description": "Content hash of the committing writer’s version, when both sides were files" + }, + "theirsSha": { + "type": "string", + "description": "Content hash of the concurrent version found in latest, when both sides were files" + } + }, + "required": [ + "path", + "winner", + "reason" + ] + }, "VolumeState": { "type": "string", "enum": [ @@ -19617,6 +19865,46 @@ "description": "Organization ID", "example": "123e4567-e89b-12d3-a456-426614174000" }, + "type": { + "description": "Volume type", + "example": "legacy", + "allOf": [ + { + "$ref": "#/components/schemas/VolumeType" + } + ] + }, + "sizeInGb": { + "type": "number", + "description": "The per-sandbox scratch quota in GB. Set only for blockmount volumes.", + "example": 10, + "nullable": true + }, + "region": { + "type": "string", + "description": "The region the volume's data lives in. For blockmount volumes this selects the region-local CAS store (a performance/placement knob — sandboxes in any region can attach it, colocation is just faster). For hotmount volumes this is the hotmount deployment region. Set for blockmount and hotmount volumes.", + "example": "us", + "nullable": true + }, + "shared": { + "type": "boolean", + "description": "The hotmount sharing mode (false = single-writer write-back, true = multi-writer synchronous). Set only for hotmount volumes.", + "example": false, + "nullable": true + }, + "lastManifestId": { + "type": "string", + "description": "The id of the most recent committed manifest, read-through from the reconciliation store. Set only for blockmount volumes that have been committed at least once.", + "nullable": true + }, + "conflicts": { + "description": "Conflicts recorded on the latest manifest — concurrent same-path modifications the store resolved (last-change-wins). Read-through from the store. Set only for blockmount volumes.", + "nullable": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/BlockmountConflict" + } + }, "state": { "description": "Volume state", "example": "ready", @@ -19653,6 +19941,7 @@ "id", "name", "organizationId", + "type", "state", "createdAt", "updatedAt", @@ -19664,12 +19953,116 @@ "properties": { "name": { "type": "string" + }, + "type": { + "description": "The type of the volume. Defaults to legacy.", + "default": "legacy", + "example": "legacy", + "allOf": [ + { + "$ref": "#/components/schemas/VolumeType" + } + ] + }, + "region": { + "type": "string", + "description": "The region to create the volume in. For blockmount volumes it selects the region-local CAS store the volume's data lives in — a performance/placement knob, not an attach restriction, so sandboxes in any region can attach the volume (colocation is just faster). Optional for blockmount: when omitted it defaults to the organization's default region (or the first region that offers blockmount). For hotmount volumes it selects the hotmount deployment region and defaults to an active region. Not allowed for legacy volumes. The volume's region is fixed for its lifetime.", + "example": "us" } }, "required": [ "name" ] }, + "HotmountRegion": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "Stable region id", + "example": "oci-us" + }, + "label": { + "type": "string", + "description": "User-facing region name", + "example": "US (OCI, Ashburn)" + }, + "geo": { + "type": "string", + "description": "Geo hint used for default region selection", + "example": "us" + } + }, + "required": [ + "region", + "label", + "geo" + ] + }, + "CreateVolumeMountToken": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "description": "The access mode for the mount. Defaults to rw.", + "enum": [ + "rw", + "ro" + ], + "default": "rw", + "example": "rw" + } + } + }, + "VolumeMountTokenDto": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "The short-lived macaroon token the in-sandbox agent uses to mount the volume", + "example": "BASE64_MACAROON" + }, + "expiresAt": { + "type": "string", + "description": "The token expiration timestamp", + "example": "2023-01-01T00:00:00.000Z" + }, + "region": { + "type": "string", + "description": "The hotmount region the volume lives in", + "example": "oci-us" + }, + "gatewayGrpc": { + "type": "string", + "description": "The gateway gRPC endpoint (SEAWEED_GATEWAY_GRPC)", + "example": "hotmount-gw-oci-us.trydaytona.com:18443" + }, + "gatewayHttp": { + "type": "string", + "description": "The gateway HTTPS endpoint (SEAWEED_GATEWAY_HTTP)", + "example": "https://hotmount-gw-oci-us.trydaytona.com:443" + }, + "binariesUrl": { + "type": "string", + "description": "The binaries bucket base URL used to bootstrap the mount (SEAWEED_BINARIES_URL)", + "example": "https://hotmount-binaries-446539620565.s3.amazonaws.com" + }, + "version": { + "type": "string", + "description": "The pinned client binary version to use (SEAWEED_VERSION), when the region pins one", + "example": "v0.4.65", + "nullable": true + } + }, + "required": [ + "token", + "expiresAt", + "region", + "gatewayGrpc", + "gatewayHttp", + "binariesUrl" + ] + }, "JobStatus": { "type": "string", "enum": [ diff --git a/sdk-go/pkg/daytona/sandbox.go b/sdk-go/pkg/daytona/sandbox.go index 044bff898..9fe3ba2e2 100644 --- a/sdk-go/pkg/daytona/sandbox.go +++ b/sdk-go/pkg/daytona/sandbox.go @@ -4,8 +4,13 @@ package daytona import ( + "bytes" "context" + "encoding/json" "fmt" + "io" + "net/http" + "strings" "time" apiclient "github.com/daytona/clients/api-client-go" @@ -455,6 +460,152 @@ func (s *Sandbox) GetWorkingDir(ctx context.Context) (string, error) { }) } +// MountVolume mounts a hotmount volume into the running sandbox on the fly. +// +// Unlike legacy and blockmount volumes (which are attached at sandbox creation), hotmount +// volumes are mounted at runtime: this method requests a short-lived mount token from the API +// and bootstraps the hotmount agent inside the sandbox (downloading and running the region's +// init.sh), mounting the filesystem at the given path. +// +// The sandbox must have /dev/fuse available, outbound access to the region gateway and binaries +// bucket, and passwordless sudo (or run as root). +// +// Parameters: +// - volume: The hotmount volume to mount. Must be of type [types.VolumeTypeHotmount]. +// - mountPath: The absolute path inside the sandbox to mount the volume at. +// +// Returns an error if the volume is not a hotmount volume or if mounting fails. +func (s *Sandbox) MountVolume(ctx context.Context, volume *types.Volume, mountPath string) error { + return withInstrumentationVoid(ctx, s.otel, "Sandbox", "MountVolume", func(ctx context.Context) error { + if volume.Type != types.VolumeTypeHotmount { + return errors.NewDaytonaError(fmt.Sprintf("only hotmount volumes can be mounted on the fly; volume %q is of type %q", volume.Name, volume.Type), 0, nil) + } + + mountToken, err := s.client.Volume.GetMountToken(ctx, volume) + if err != nil { + return err + } + + command := buildHotmountMountCommand(mountToken, mountPath) + resp, err := s.Process.ExecuteCommand(ctx, command) + if err != nil { + return err + } + if resp.ExitCode != 0 { + return errors.NewDaytonaError(fmt.Sprintf("failed to mount hotmount volume %q: %s", volume.Name, resp.Result), 0, nil) + } + + return nil + }) +} + +// PullVolumes pulls the latest state of the sandbox's blockmount volumes into the running +// sandbox. +// +// Blockmount volumes reconcile in the background: each sandbox writes to a private scratch +// that is committed to the shared store periodically, but other sandboxes' commits only +// appear locally on re-materialize. This method makes them appear immediately, without +// stopping the sandbox: it commits this sandbox's local changes (so they participate in the +// merge), then applies the volume's latest merged state in place. Files modified locally +// after another sandbox's change keep the local version (last-change-wins by mtime). +// +// Parameters: +// - volume: The blockmount volume to pull. Pass nil to pull every blockmount volume +// attached to the sandbox. +// +// Returns per-volume pull results. +func (s *Sandbox) PullVolumes(ctx context.Context, volume *types.Volume) ([]types.VolumePullResult, error) { + return withInstrumentation(ctx, s.otel, "Sandbox", "PullVolumes", func(ctx context.Context) ([]types.VolumePullResult, error) { + if volume != nil && volume.Type != types.VolumeTypeBlockmount { + return nil, errors.NewDaytonaError(fmt.Sprintf("only blockmount volumes can be pulled; volume %q is of type %q", volume.Name, volume.Type), 0, nil) + } + + payload := map[string]string{} + if volume != nil { + payload["volumeId"] = volume.ID + } + body, err := json.Marshal(payload) + if err != nil { + return nil, errors.NewDaytonaError(fmt.Sprintf("failed to serialize pull request: %s", err), 0, nil) + } + + cfg := s.ToolboxClient.GetConfig() + req, err := http.NewRequestWithContext(ctx, http.MethodPost, strings.TrimRight(cfg.Servers[0].URL, "/")+"/volumes/pull", bytes.NewReader(body)) + if err != nil { + return nil, errors.NewDaytonaError(fmt.Sprintf("failed to build pull request: %s", err), 0, nil) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + for k, v := range cfg.DefaultHeader { + req.Header.Set(k, v) + } + + httpClient := cfg.HTTPClient + if httpClient == nil { + httpClient = http.DefaultClient + } + resp, err := httpClient.Do(req) + if err != nil { + return nil, errors.NewDaytonaError(fmt.Sprintf("failed to pull volumes: %s", err), 0, nil) + } + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errors.NewDaytonaError(fmt.Sprintf("failed to read pull response: %s", err), 0, nil) + } + if resp.StatusCode != http.StatusOK { + return nil, errors.NewDaytonaError(fmt.Sprintf("failed to pull volumes: %s", strings.TrimSpace(string(respBody))), resp.StatusCode, nil) + } + + var parsed struct { + Results []types.VolumePullResult `json:"results"` + } + if err := json.Unmarshal(respBody, &parsed); err != nil { + return nil, errors.NewDaytonaError(fmt.Sprintf("failed to parse pull response: %s", err), 0, nil) + } + return parsed.Results, nil + }) +} + +// buildHotmountMountCommand builds the shell command that bootstraps the hotmount agent and +// mounts the volume inside a sandbox. It exports the SEAWEED_* environment contract and runs the +// region's init.sh, using passwordless sudo when not already root (sudo strips env, so the vars +// are passed via env). +func buildHotmountMountCommand(mountToken *types.VolumeMountToken, mountPath string) string { + envVars := [][2]string{ + {"SEAWEED_TOKEN", mountToken.Token}, + {"SEAWEED_GATEWAY_GRPC", mountToken.GatewayGrpc}, + {"SEAWEED_GATEWAY_HTTP", mountToken.GatewayHTTP}, + {"SEAWEED_BINARIES_URL", mountToken.BinariesURL}, + {"SEAWEED_MOUNT_DIR", mountPath}, + {"SEAWEED_VERSION", mountToken.Version}, + } + assignments := make([]string, 0, len(envVars)) + for _, kv := range envVars { + if kv[1] == "" { + continue + } + assignments = append(assignments, fmt.Sprintf("%s='%s'", kv[0], kv[1])) + } + + // The bootstrap (and init.sh itself) requires curl. Fail loudly if it is missing or the + // download fails, rather than letting a broken `curl ... | bash` pipe exit 0 and mount nothing. + inner := strings.Join([]string{ + "set -e", + `if ! command -v curl >/dev/null 2>&1; then echo "hotmount: curl is required to bootstrap the agent but was not found in the sandbox" >&2; exit 1; fi`, + `mkdir -p "$SEAWEED_MOUNT_DIR"`, + `init_script="$(curl -fsSL "$SEAWEED_BINARIES_URL/init.sh")"`, + `printf %s "$init_script" | bash`, + }, "; ") + + return fmt.Sprintf( + `if [ "$(id -u)" != 0 ]; then SUDO="sudo -n"; else SUDO=""; fi; `+ + `$SUDO env %s bash -c '%s'`, + strings.Join(assignments, " "), + inner, + ) +} + // CreateLspServer creates a Language Server Protocol (LSP) server scoped to a // language and project path within the sandbox. // diff --git a/sdk-go/pkg/daytona/volume.go b/sdk-go/pkg/daytona/volume.go index 7efce4f69..367dca43a 100644 --- a/sdk-go/pkg/daytona/volume.go +++ b/sdk-go/pkg/daytona/volume.go @@ -115,6 +115,7 @@ func (v *VolumeService) Get(ctx context.Context, name string) (*types.Volume, er // // Parameters: // - name: Unique name for the volume +// - opts: Optional volume type and hotmount region. // // Example: // @@ -126,12 +127,29 @@ func (v *VolumeService) Get(ctx context.Context, name string) (*types.Volume, er // // Wait for volume to be ready // volume, err = client.Volumes.WaitForReady(ctx, volume, 60*time.Second) // +// // Create a shared high-performance block volume +// volume, err = client.Volumes.Create(ctx, "fast-data", types.CreateVolumeOptions{ +// Type: types.VolumeTypeBlockmount, +// }) +// // Returns the created [types.Volume] or an error. -func (v *VolumeService) Create(ctx context.Context, name string) (*types.Volume, error) { +func (v *VolumeService) Create(ctx context.Context, name string, opts ...types.CreateVolumeOptions) (*types.Volume, error) { return withInstrumentation(ctx, v.otel, "Volume", "Create", func(ctx context.Context) (*types.Volume, error) { authCtx := v.client.getAuthContext(ctx) req := apiclient.NewCreateVolume(name) + if len(opts) > 0 { + if opts[0].Type != "" { + volumeType, err := apiclient.NewVolumeTypeFromValue(opts[0].Type) + if err != nil { + return nil, err + } + req.SetType(*volumeType) + } + if opts[0].Region != "" { + req.SetRegion(opts[0].Region) + } + } volumeDto, httpResp, err := v.client.apiClient.VolumesAPI.CreateVolume(authCtx).CreateVolume(*req).Execute() if err != nil { return nil, errors.ConvertAPIError(err, httpResp) @@ -141,6 +159,87 @@ func (v *VolumeService) Create(ctx context.Context, name string) (*types.Volume, }) } +// GetMountToken creates a short-lived mount token for a hotmount volume. +// +// The token, together with the returned region gateway/binaries endpoints, is used to bootstrap +// the hotmount agent (inside a sandbox or on customer infrastructure) and mount the volume on the +// fly. Only hotmount volumes support this. +// +// Parameters: +// - volume: The hotmount volume to obtain a mount token for +// +// Returns the [types.VolumeMountToken] or an error. +func (v *VolumeService) GetMountToken(ctx context.Context, volume *types.Volume) (*types.VolumeMountToken, error) { + return withInstrumentation(ctx, v.otel, "Volume", "GetMountToken", func(ctx context.Context) (*types.VolumeMountToken, error) { + authCtx := v.client.getAuthContext(ctx) + tokenDto, httpResp, err := v.client.apiClient.VolumesAPI.CreateVolumeMountToken(authCtx, volume.ID).Execute() + if err != nil { + return nil, errors.ConvertAPIError(err, httpResp) + } + + return &types.VolumeMountToken{ + Token: tokenDto.GetToken(), + ExpiresAt: tokenDto.GetExpiresAt(), + Region: tokenDto.GetRegion(), + GatewayGrpc: tokenDto.GetGatewayGrpc(), + GatewayHTTP: tokenDto.GetGatewayHttp(), + BinariesURL: tokenDto.GetBinariesUrl(), + Version: tokenDto.GetVersion(), + }, nil + }) +} + +// ListHotmountRegions returns the hotmount regions available for volume creation. +// +// Returns a slice of [types.HotmountRegion] or an error if the request fails. +func (v *VolumeService) ListHotmountRegions(ctx context.Context) ([]*types.HotmountRegion, error) { + return withInstrumentation(ctx, v.otel, "Volume", "ListHotmountRegions", func(ctx context.Context) ([]*types.HotmountRegion, error) { + authCtx := v.client.getAuthContext(ctx) + regionDtos, httpResp, err := v.client.apiClient.VolumesAPI.ListHotmountRegions(authCtx).Execute() + if err != nil { + return nil, errors.ConvertAPIError(err, httpResp) + } + + regions := make([]*types.HotmountRegion, len(regionDtos)) + for i, dto := range regionDtos { + regions[i] = &types.HotmountRegion{ + Region: dto.GetRegion(), + Label: dto.GetLabel(), + Geo: dto.GetGeo(), + } + } + + return regions, nil + }) +} + +// ListBlockmountRegions returns the regions where blockmount volumes can be created. +// +// A blockmount volume's data lives in the region it is created in (a performance/placement knob — +// sandboxes in any region can attach it, colocation is just faster). Only regions a superadmin has +// enabled for blockmount are returned. +// +// Returns a slice of [types.BlockmountRegion] or an error if the request fails. +func (v *VolumeService) ListBlockmountRegions(ctx context.Context) ([]*types.BlockmountRegion, error) { + return withInstrumentation(ctx, v.otel, "Volume", "ListBlockmountRegions", func(ctx context.Context) ([]*types.BlockmountRegion, error) { + authCtx := v.client.getAuthContext(ctx) + regionDtos, httpResp, err := v.client.apiClient.VolumesAPI.ListBlockmountRegions(authCtx).Execute() + if err != nil { + return nil, errors.ConvertAPIError(err, httpResp) + } + + regions := make([]*types.BlockmountRegion, len(regionDtos)) + for i, dto := range regionDtos { + regions[i] = &types.BlockmountRegion{ + ID: dto.GetId(), + Name: dto.GetName(), + } + } + + return regions, nil + }) +} + // Delete permanently removes a volume and all its data. // // This operation is irreversible. Ensure no sandboxes are using the volume @@ -246,11 +345,25 @@ func volumeDtoToVolume(dto *apiclient.VolumeDto) *types.Volume { ID: dto.GetId(), Name: dto.GetName(), OrganizationID: dto.GetOrganizationId(), + Type: string(dto.GetType()), // Convert VolumeType enum to string State: string(dto.GetState()), // Convert VolumeState enum to string CreatedAt: createdAt, UpdatedAt: updatedAt, } + // Handle nullable SizeInGb + if dto.SizeInGb.IsSet() { + volume.SizeInGb = dto.SizeInGb.Get() + } + + // Handle hotmount-only Region/Shared + if dto.Region.IsSet() { + volume.Region = dto.Region.Get() + } + if dto.Shared.IsSet() { + volume.Shared = dto.Shared.Get() + } + // Handle nullable LastUsedAt if dto.HasLastUsedAt() { lastUsedAt, _ := time.Parse(time.RFC3339, dto.GetLastUsedAt()) diff --git a/sdk-go/pkg/types/types.go b/sdk-go/pkg/types/types.go index ce7dc78bf..840db27dd 100644 --- a/sdk-go/pkg/types/types.go +++ b/sdk-go/pkg/types/types.go @@ -139,14 +139,84 @@ type PaginatedSnapshots struct { // Volume represents a Daytona volume type Volume struct { - ID string `json:"id"` - Name string `json:"name"` - OrganizationID string `json:"organizationId"` - State string `json:"state"` - ErrorReason *string `json:"errorReason,omitempty"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - LastUsedAt time.Time `json:"lastUsedAt,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + OrganizationID string `json:"organizationId"` + Type string `json:"type"` + SizeInGb *float32 `json:"sizeInGb,omitempty"` + // Region is the hotmount region the volume lives in. Set only for hotmount volumes. + Region *string `json:"region,omitempty"` + // Shared is the hotmount sharing mode. Set only for hotmount volumes. + Shared *bool `json:"shared,omitempty"` + State string `json:"state"` + ErrorReason *string `json:"errorReason,omitempty"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + LastUsedAt time.Time `json:"lastUsedAt,omitempty"` +} + +// Volume type constants mirror the API's volume types. +const ( + VolumeTypeLegacy = "legacy" + VolumeTypeHotmount = "hotmount" + VolumeTypeBlockmount = "blockmount" +) + +// CreateVolumeOptions contains optional parameters for creating a volume. +type CreateVolumeOptions struct { + // Type is the volume type. Empty defaults to legacy. + Type string + // Region is the region to create the volume in. For blockmount volumes it selects the + // region-local store the volume's data lives in — a performance/placement knob, not an attach + // restriction, so sandboxes in any region can attach it (colocation is just faster); optional + // for blockmount, defaulting to the organization's default region (or the first region offering + // blockmount) when omitted. For hotmount volumes it selects the deployment region (defaults to + // an active region). Not allowed for legacy volumes. The region is fixed for the volume's + // lifetime. + Region string +} + +// VolumeMountToken is a short-lived token used to bootstrap the hotmount agent and mount a +// hotmount volume on the fly. It carries the region gateway/binaries endpoints (the SEAWEED_* +// bootstrap contract) alongside the macaroon token. +type VolumeMountToken struct { + Token string `json:"token"` + ExpiresAt string `json:"expiresAt"` + Region string `json:"region"` + GatewayGrpc string `json:"gatewayGrpc"` + GatewayHTTP string `json:"gatewayHttp"` + BinariesURL string `json:"binariesUrl"` + Version string `json:"version,omitempty"` +} + +// HotmountRegion is a hotmount region selectable at volume creation. +type HotmountRegion struct { + Region string `json:"region"` + Label string `json:"label"` + Geo string `json:"geo"` +} + +// BlockmountRegion is a region where blockmount volumes can be created. A blockmount volume's data +// lives in its region (a performance/placement knob — sandboxes in any region can attach it, +// colocation is just faster). +type BlockmountRegion struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// VolumePullResult reports what an explicit blockmount volume pull did for one volume of a +// running sandbox. +type VolumePullResult struct { + VolumeID string `json:"volumeId"` + ManifestID string `json:"manifestId,omitempty"` + // UpToDate is true when the sandbox already reflected the volume's latest merged state. + UpToDate bool `json:"upToDate"` + FilesWritten int `json:"filesWritten"` + Deleted int `json:"deleted"` + // SkippedLocalNewer counts paths left untouched because the sandbox has a strictly newer + // local modification; the next commit's last-change-wins merge resolves them. + SkippedLocalNewer int `json:"skippedLocalNewer"` + BytesFetched int64 `json:"bytesFetched"` } // Secret represents an organization-scoped secret. diff --git a/sdk-java/src/main/java/io/daytona/sdk/Sandbox.java b/sdk-java/src/main/java/io/daytona/sdk/Sandbox.java index 27b9644b2..1b7989b70 100644 --- a/sdk-java/src/main/java/io/daytona/sdk/Sandbox.java +++ b/sdk-java/src/main/java/io/daytona/sdk/Sandbox.java @@ -4,6 +4,7 @@ package io.daytona.sdk; import io.daytona.api.client.api.SandboxApi; +import io.daytona.api.client.api.VolumesApi; import io.daytona.api.client.model.BuildInfo; import io.daytona.api.client.model.CreateSandboxSnapshot; import io.daytona.api.client.model.ForkSandbox; @@ -13,7 +14,11 @@ import io.daytona.api.client.model.ToolboxProxyUrl; import io.daytona.api.client.model.UpdateSandboxNetworkSettings; import io.daytona.api.client.model.UpdateSandboxSecrets; +import io.daytona.api.client.model.VolumeMountTokenDto; import io.daytona.sdk.exception.DaytonaException; +import io.daytona.sdk.model.ExecuteResponse; +import io.daytona.sdk.model.Volume; +import io.daytona.sdk.model.VolumePullResult; import java.math.BigDecimal; import java.util.ArrayList; @@ -34,6 +39,7 @@ public class Sandbox { private final io.daytona.toolbox.client.ApiClient toolboxApiClient; private final io.daytona.toolbox.client.api.InfoApi infoApi; private final io.daytona.toolbox.client.api.ServerApi serverApi; + private final VolumeService volumeService; private final String apiKey; // Fields shared by both io.daytona.api.client.model.Sandbox and SandboxListItem. @@ -90,6 +96,7 @@ public class Sandbox { this.toolboxApiClient = buildToolboxApiClient(sandboxApi, config); this.infoApi = new io.daytona.toolbox.client.api.InfoApi(toolboxApiClient); this.serverApi = new io.daytona.toolbox.client.api.ServerApi(toolboxApiClient); + this.volumeService = new VolumeService(new VolumesApi(sandboxApi.getApiClient())); this.process = new Process(new io.daytona.toolbox.client.api.ProcessApi(toolboxApiClient), this); this.fs = new FileSystem(new io.daytona.toolbox.client.api.FileSystemApi(toolboxApiClient)); this.git = new Git(new io.daytona.toolbox.client.api.GitApi(toolboxApiClient)); @@ -105,6 +112,7 @@ public class Sandbox { this.toolboxApiClient = buildToolboxApiClient(sandboxApi, config); this.infoApi = new io.daytona.toolbox.client.api.InfoApi(toolboxApiClient); this.serverApi = new io.daytona.toolbox.client.api.ServerApi(toolboxApiClient); + this.volumeService = new VolumeService(new VolumesApi(sandboxApi.getApiClient())); this.process = new Process(new io.daytona.toolbox.client.api.ProcessApi(toolboxApiClient), this); this.fs = new FileSystem(new io.daytona.toolbox.client.api.FileSystemApi(toolboxApiClient)); this.git = new Git(new io.daytona.toolbox.client.api.GitApi(toolboxApiClient)); @@ -363,6 +371,127 @@ public String getWorkDir() { return value == null ? "" : asString(value.getDir()); } + /** + * Mounts a hotmount volume into this Sandbox on the fly. + * + *

Requests a short-lived mount token from the API and bootstraps the hotmount agent inside + * the Sandbox (downloading and running the region's {@code init.sh}), mounting the collaborative + * realtime filesystem at {@code mountPath}. Only hotmount volumes can be mounted this way; legacy + * and blockmount volumes are attached at Sandbox creation instead. + * + *

The Sandbox must have {@code /dev/fuse} available, outbound access to the region gateway + * and binaries bucket, and passwordless sudo (or run as root). + * + * @param volume the hotmount volume to mount + * @param mountPath absolute path inside the Sandbox to mount the volume at + * @throws DaytonaException if the volume is not a hotmount volume or the mount command fails + */ + public void mountVolume(Volume volume, String mountPath) { + if (volume == null) { + throw new DaytonaException("Volume must not be null"); + } + if (!"hotmount".equals(volume.getType())) { + throw new DaytonaException( + "Only hotmount volumes can be mounted at runtime; volume '" + volume.getName() + + "' is of type '" + volume.getType() + "'"); + } + VolumeMountTokenDto mountToken = volumeService.getMountToken(volume.getId()); + String command = buildHotmountMountCommand(mountToken, mountPath); + ExecuteResponse response = process.executeCommand(command); + Integer exitCode = response == null ? null : response.getExitCode(); + if (exitCode == null || exitCode != 0) { + throw new DaytonaException( + "Failed to mount hotmount volume '" + volume.getName() + "': " + + (response == null ? "" : response.getResult())); + } + } + + /** + * Pulls the latest state of the Sandbox's blockmount volumes into the running Sandbox. + * + *

Blockmount volumes reconcile in the background: each sandbox writes to a private scratch + * that is committed to the shared store periodically, but other sandboxes' commits only appear + * locally on re-materialize. This method makes them appear immediately, without stopping the + * Sandbox: it commits this Sandbox's local changes (so they participate in the merge), then + * applies the volume's latest merged state in place. Files modified locally after another + * sandbox's change keep the local version (last-change-wins by mtime). + * + * @param volume the blockmount volume to pull; {@code null} pulls every blockmount volume + * attached to the Sandbox + * @return per-volume pull results + * @throws DaytonaException if the volume is not a blockmount volume or the pull fails + */ + public List pullVolumes(Volume volume) { + if (volume != null && !"blockmount".equals(volume.getType())) { + throw new DaytonaException( + "Only blockmount volumes can be pulled; volume '" + volume.getName() + + "' is of type '" + volume.getType() + "'"); + } + Map payload = new HashMap<>(); + if (volume != null) { + payload.put("volumeId", volume.getId()); + } + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + headers.put("Accept", "application/json"); + try { + okhttp3.Call call = toolboxApiClient.buildCall( + null, + "/volumes/pull", + "POST", + new java.util.ArrayList<>(), + new java.util.ArrayList<>(), + payload, + headers, + new HashMap<>(), + new HashMap<>(), + new String[0], + null); + io.daytona.toolbox.client.ApiResponse response = + toolboxApiClient.execute(call, PullVolumesResponse.class); + PullVolumesResponse data = response.getData(); + if (data == null || data.results == null) { + return Collections.emptyList(); + } + return data.results; + } catch (io.daytona.toolbox.client.ApiException e) { + throw new DaytonaException("Failed to pull volumes: " + e.getResponseBody(), e); + } + } + + /** Wire shape of the toolbox volumes/pull response. */ + private static final class PullVolumesResponse { + private List results; + } + + private static String buildHotmountMountCommand(VolumeMountTokenDto mountToken, String mountPath) { + StringBuilder env = new StringBuilder(); + appendEnvVar(env, "SEAWEED_TOKEN", mountToken.getToken()); + appendEnvVar(env, "SEAWEED_GATEWAY_GRPC", mountToken.getGatewayGrpc()); + appendEnvVar(env, "SEAWEED_GATEWAY_HTTP", mountToken.getGatewayHttp()); + appendEnvVar(env, "SEAWEED_BINARIES_URL", mountToken.getBinariesUrl()); + appendEnvVar(env, "SEAWEED_MOUNT_DIR", mountPath); + appendEnvVar(env, "SEAWEED_VERSION", mountToken.getVersion()); + // The bootstrap (and init.sh itself) requires curl. Fail loudly if it is missing or the + // download fails, rather than letting a broken `curl ... | bash` pipe exit 0 and mount nothing. + String inner = "set -e; " + + "if ! command -v curl >/dev/null 2>&1; then echo \"hotmount: curl is required to " + + "bootstrap the agent but was not found in the sandbox\" >&2; exit 1; fi; " + + "mkdir -p \"$SEAWEED_MOUNT_DIR\"; " + + "init_script=\"$(curl -fsSL \"$SEAWEED_BINARIES_URL/init.sh\")\"; " + + "printf %s \"$init_script\" | bash"; + return "if [ \"$(id -u)\" != 0 ]; then SUDO=\"sudo -n\"; else SUDO=\"\"; fi; " + + "$SUDO env " + env.toString().trim() + " " + + "bash -c '" + inner + "'"; + } + + private static void appendEnvVar(StringBuilder builder, String key, String value) { + if (value == null || value.isEmpty()) { + return; + } + builder.append(key).append("='").append(value).append("' "); + } + /** * Updates the Sandbox daemon's process environment. * diff --git a/sdk-java/src/main/java/io/daytona/sdk/VolumeService.java b/sdk-java/src/main/java/io/daytona/sdk/VolumeService.java index 8babb8868..9253e3c00 100644 --- a/sdk-java/src/main/java/io/daytona/sdk/VolumeService.java +++ b/sdk-java/src/main/java/io/daytona/sdk/VolumeService.java @@ -5,6 +5,10 @@ import io.daytona.api.client.api.VolumesApi; import io.daytona.api.client.model.CreateVolume; +import io.daytona.api.client.model.HotmountRegion; +import io.daytona.api.client.model.Region; +import io.daytona.api.client.model.VolumeMountTokenDto; +import io.daytona.api.client.model.VolumeType; import io.daytona.sdk.model.Volume; import java.util.List; @@ -23,19 +27,89 @@ public class VolumeService { } /** - * Creates a new volume. + * Creates a new legacy volume. * * @param name volume name * @return created {@link Volume} * @throws io.daytona.sdk.exception.DaytonaException if creation fails */ public Volume create(String name) { + return create(name, null, null); + } + + /** + * Creates a new volume of the given type. + * + * @param name volume name + * @param type volume type, or null to default to legacy + * @return created {@link Volume} + * @throws io.daytona.sdk.exception.DaytonaException if creation fails + */ + public Volume create(String name, VolumeType type) { + return create(name, type, (String) null); + } + + /** + * Creates a new volume of the given type, with a region. + * + * @param name volume name + * @param type volume type, or null to default to legacy + * @param region region to create the volume in; for blockmount volumes it selects the region-local + * store the volume's data lives in — a performance/placement knob, sandboxes in any region can + * attach it (optional for blockmount, defaults to the organization's default region when omitted), + * selects the deployment region for hotmount volumes, not allowed for legacy + * @return created {@link Volume} + * @throws io.daytona.sdk.exception.DaytonaException if creation fails + */ + public Volume create(String name, VolumeType type, String region) { io.daytona.api.client.model.VolumeDto volumeDto = ExceptionMapper.callMain( - () -> volumesApi.createVolume(new CreateVolume().name(name), null) + () -> volumesApi.createVolume( + new CreateVolume().name(name).type(type).region(region), null) ); return toVolume(volumeDto); } + /** + * Creates a short-lived mount token for a hotmount volume. + * + *

The token, together with the returned region gateway/binaries endpoints, is used to + * bootstrap the hotmount agent (inside a Sandbox or on customer infrastructure) and mount the + * volume on the fly. Only hotmount volumes support this. + * + * @param volumeId the hotmount volume identifier + * @return the mount token, region endpoints, and expiration + * @throws io.daytona.sdk.exception.DaytonaException if the request fails + */ + public VolumeMountTokenDto getMountToken(String volumeId) { + return ExceptionMapper.callMain(() -> volumesApi.createVolumeMountToken(volumeId, null, null)); + } + + /** + * Lists the hotmount regions available for volume creation. + * + * @return list of active hotmount regions + * @throws io.daytona.sdk.exception.DaytonaException if the request fails + */ + public List listHotmountRegions() { + List regions = ExceptionMapper.callMain(() -> volumesApi.listHotmountRegions(null)); + return regions != null ? regions : new ArrayList(); + } + + /** + * Lists the regions where blockmount volumes can be created. + * + *

A blockmount volume's data lives in the region it is created in (a performance/placement knob — + * sandboxes in any region can attach it, colocation is just faster). Only regions a superadmin has + * enabled for blockmount are returned. + * + * @return list of regions that support blockmount volumes + * @throws io.daytona.sdk.exception.DaytonaException if the request fails + */ + public List listBlockmountRegions() { + List regions = ExceptionMapper.callMain(() -> volumesApi.listBlockmountRegions(null)); + return regions != null ? regions : new ArrayList(); + } + /** * Lists all accessible volumes. * @@ -80,6 +154,10 @@ private Volume toVolume(io.daytona.api.client.model.VolumeDto source) { if (source != null) { volume.setId(source.getId()); volume.setName(source.getName()); + volume.setType(source.getType() == null ? null : source.getType().getValue()); + volume.setSizeInGb(source.getSizeInGb()); + volume.setRegion(source.getRegion()); + volume.setShared(source.getShared()); volume.setState(source.getState() == null ? null : source.getState().getValue()); } return volume; diff --git a/sdk-java/src/main/java/io/daytona/sdk/model/Volume.java b/sdk-java/src/main/java/io/daytona/sdk/model/Volume.java index e255525ca..fcff25a0d 100644 --- a/sdk-java/src/main/java/io/daytona/sdk/model/Volume.java +++ b/sdk-java/src/main/java/io/daytona/sdk/model/Volume.java @@ -15,6 +15,14 @@ public class Volume { private String id; @JsonProperty("name") private String name; + @JsonProperty("type") + private String type; + @JsonProperty("sizeInGb") + private java.math.BigDecimal sizeInGb; + @JsonProperty("region") + private String region; + @JsonProperty("shared") + private Boolean shared; @JsonProperty("state") private String state; @@ -46,6 +54,62 @@ public class Volume { */ public void setName(String name) { this.name = name; } + /** + * Returns volume type. + * + * @return volume type (legacy, hotmount, or blockmount) + */ + public String getType() { return type; } + + /** + * Sets volume type. + * + * @param type volume type + */ + public void setType(String type) { this.type = type; } + + /** + * Returns the per-sandbox scratch quota in GB. + * + * @return size in GB, or null for volume types that do not use it + */ + public java.math.BigDecimal getSizeInGb() { return sizeInGb; } + + /** + * Sets the per-sandbox scratch quota in GB. + * + * @param sizeInGb size in GB + */ + public void setSizeInGb(java.math.BigDecimal sizeInGb) { this.sizeInGb = sizeInGb; } + + /** + * Returns the hotmount region the volume lives in. + * + * @return region id, or null for non-hotmount volumes + */ + public String getRegion() { return region; } + + /** + * Sets the hotmount region the volume lives in. + * + * @param region region id + */ + public void setRegion(String region) { this.region = region; } + + /** + * Returns the hotmount sharing mode. + * + * @return sharing mode, or null for non-hotmount volumes + */ + public Boolean getShared() { return shared; } + + /** + * Sets the hotmount sharing mode. + * + * @param shared sharing mode + */ + public void setShared(Boolean shared) { this.shared = shared; } + /** * Returns volume state. * diff --git a/sdk-java/src/main/java/io/daytona/sdk/model/VolumePullResult.java b/sdk-java/src/main/java/io/daytona/sdk/model/VolumePullResult.java new file mode 100644 index 000000000..9cf545284 --- /dev/null +++ b/sdk-java/src/main/java/io/daytona/sdk/model/VolumePullResult.java @@ -0,0 +1,155 @@ +// Copyright Daytona Platforms Inc. +// SPDX-License-Identifier: Apache-2.0 + +package io.daytona.sdk.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +/** + * Result of an explicit blockmount volume pull into a running Sandbox. + */ +public class VolumePullResult { + @JsonProperty("volumeId") + private String volumeId; + @JsonProperty("manifestId") + private String manifestId; + @JsonProperty("upToDate") + private boolean upToDate; + @JsonProperty("filesWritten") + private int filesWritten; + @JsonProperty("deleted") + private int deleted; + @JsonProperty("skippedLocalNewer") + private int skippedLocalNewer; + @JsonProperty("bytesFetched") + private long bytesFetched; + + /** + * Returns the identifier of the volume that was pulled. + * + * @return volume identifier + */ + public String getVolumeId() { + return volumeId; + } + + /** + * Sets the identifier of the volume that was pulled. + * + * @param volumeId volume identifier + */ + public void setVolumeId(String volumeId) { + this.volumeId = volumeId; + } + + /** + * Returns the merged manifest the Sandbox's scratch was advanced to. + * + * @return manifest identifier, or {@code null} when the volume had no commits + */ + public String getManifestId() { + return manifestId; + } + + /** + * Sets the merged manifest identifier. + * + * @param manifestId manifest identifier + */ + public void setManifestId(String manifestId) { + this.manifestId = manifestId; + } + + /** + * Returns whether the Sandbox already reflected the latest merged state. + * + * @return {@code true} when nothing had to be pulled + */ + public boolean isUpToDate() { + return upToDate; + } + + /** + * Sets whether the Sandbox already reflected the latest merged state. + * + * @param upToDate up-to-date flag + */ + public void setUpToDate(boolean upToDate) { + this.upToDate = upToDate; + } + + /** + * Returns the number of files and symlinks written into the Sandbox by the pull. + * + * @return written file count + */ + public int getFilesWritten() { + return filesWritten; + } + + /** + * Sets the number of files and symlinks written into the Sandbox by the pull. + * + * @param filesWritten written file count + */ + public void setFilesWritten(int filesWritten) { + this.filesWritten = filesWritten; + } + + /** + * Returns the number of paths removed because they were deleted in the merged state. + * + * @return deleted path count + */ + public int getDeleted() { + return deleted; + } + + /** + * Sets the number of paths removed because they were deleted in the merged state. + * + * @param deleted deleted path count + */ + public void setDeleted(int deleted) { + this.deleted = deleted; + } + + /** + * Returns the number of paths left untouched because the Sandbox has a strictly newer local + * modification (the next commit's last-change-wins merge resolves them). + * + * @return skipped path count + */ + public int getSkippedLocalNewer() { + return skippedLocalNewer; + } + + /** + * Sets the number of paths skipped as locally newer. + * + * @param skippedLocalNewer skipped path count + */ + public void setSkippedLocalNewer(int skippedLocalNewer) { + this.skippedLocalNewer = skippedLocalNewer; + } + + /** + * Returns the number of content bytes downloaded from the store. + * + * @return downloaded byte count + */ + public long getBytesFetched() { + return bytesFetched; + } + + /** + * Sets the number of content bytes downloaded from the store. + * + * @param bytesFetched downloaded byte count + */ + public void setBytesFetched(long bytesFetched) { + this.bytesFetched = bytesFetched; + } +} diff --git a/sdk-python/src/daytona/__init__.py b/sdk-python/src/daytona/__init__.py index 6c1af0b3b..a207a7312 100644 --- a/sdk-python/src/daytona/__init__.py +++ b/sdk-python/src/daytona/__init__.py @@ -73,7 +73,7 @@ from .common.sandbox import ListSandboxesQuery, Resources from .common.secret import CreateSecretParams, ListSecretsResponse, Secret, UpdateSecretParams from .common.snapshot import CreateSnapshotParams - from .common.volume import VolumeMount + from .common.volume import Volume, VolumeMount, VolumeMountTokenDto, VolumePullResult, VolumeType __all__ = [ "Daytona", @@ -108,7 +108,11 @@ "UploadProgress", "CancelEvent", "FileUpload", + "Volume", "VolumeMount", + "VolumeMountTokenDto", + "VolumePullResult", + "VolumeType", "Secret", "CreateSecretParams", "UpdateSecretParams", @@ -229,7 +233,11 @@ # common.snapshot "CreateSnapshotParams": "common.snapshot", # common.volume + "Volume": "common.volume", "VolumeMount": "common.volume", + "VolumeMountTokenDto": "common.volume", + "VolumePullResult": "common.volume", + "VolumeType": "common.volume", # common.secret "Secret": "common.secret", "CreateSecretParams": "common.secret", diff --git a/sdk-python/src/daytona/_async/daytona.py b/sdk-python/src/daytona/_async/daytona.py index 2300d74cb..ae79d9f09 100644 --- a/sdk-python/src/daytona/_async/daytona.py +++ b/sdk-python/src/daytona/_async/daytona.py @@ -622,6 +622,7 @@ async def should_terminate(): self._sandbox_api, validated_language.value, self._pool_tracker, + volume_service=self.volume, ) if sandbox.state != SandboxState.STARTED: @@ -684,6 +685,7 @@ async def get(self, sandbox_id_or_name: str) -> AsyncSandbox: self._sandbox_api, language, self._pool_tracker, + volume_service=self.volume, ) @intercept_errors(message_prefix="Failed to list sandboxes: ") @@ -727,6 +729,7 @@ async def list( self._sandbox_api, language, self._pool_tracker, + volume_service=self.volume, ) cursor = response.next_cursor or None diff --git a/sdk-python/src/daytona/_async/sandbox.py b/sdk-python/src/daytona/_async/sandbox.py index a63a9d1ac..fb3f86539 100644 --- a/sdk-python/src/daytona/_async/sandbox.py +++ b/sdk-python/src/daytona/_async/sandbox.py @@ -3,7 +3,9 @@ from __future__ import annotations import asyncio +import json import time +from typing import TYPE_CHECKING, Optional from deprecated import deprecated from pydantic import ConfigDict, PrivateAttr @@ -42,6 +44,7 @@ from ..common.errors import DaytonaError, DaytonaNotFoundError, DaytonaValidationError from ..common.lsp_server import LspLanguageId, LspLanguageIdLiteral from ..common.sandbox import Resources +from ..common.volume import Volume, VolumePullResult, VolumeType, build_hotmount_mount_command from ..internal.pool_tracker import AsyncPoolSaturationTracker from ..internal.toolbox_api_client_proxy import ToolboxApiClientProxy from .code_interpreter import AsyncCodeInterpreter @@ -51,6 +54,9 @@ from .lsp_server import AsyncLspServer from .process import AsyncProcess +if TYPE_CHECKING: + from .volume import AsyncVolumeService + class AsyncSandbox(SandboxDto): """Represents a Daytona Sandbox. @@ -109,6 +115,7 @@ class AsyncSandbox(SandboxDto): _process: AsyncProcess = PrivateAttr() _computer_use: AsyncComputerUse = PrivateAttr() _code_interpreter: AsyncCodeInterpreter = PrivateAttr() + _volume_service: Optional["AsyncVolumeService"] = PrivateAttr(default=None) # TODO: Remove model_config once everything is migrated to pydantic # pylint: disable=fixme model_config: ConfigDict = ConfigDict(arbitrary_types_allowed=True) @@ -120,10 +127,12 @@ def __init__( sandbox_api: SandboxApi, language: str, pool_tracker: AsyncPoolSaturationTracker | None = None, + volume_service: Optional["AsyncVolumeService"] = None, ): super().__init__(**sandbox_dto.model_dump()) self.__process_sandbox_dto(sandbox_dto) self._sandbox_api: SandboxApi = sandbox_api + self._volume_service = volume_service # Wrap the toolbox API client to inject the sandbox ID into the resource path self._toolbox_api: ToolboxApiClientProxy[ApiClient] = ToolboxApiClientProxy( toolbox_api, self.id, self.toolbox_proxy_url, pool_tracker @@ -217,6 +226,85 @@ async def get_work_dir(self) -> str: response = await self._info_api.get_work_dir() return response.dir + @with_instrumentation() + async def mount_volume(self, volume: Volume, mount_path: str) -> None: + """Mounts a hotmount Volume into the running Sandbox on the fly. + + Unlike legacy and blockmount volumes (which are attached at Sandbox creation), hotmount + volumes are mounted at runtime: this method requests a short-lived mount token from the API + and bootstraps the hotmount agent inside the Sandbox (downloading and running the region's + ``init.sh``), mounting the filesystem at the given path. + + The Sandbox must have ``/dev/fuse`` available, outbound access to the region gateway and + binaries bucket, and passwordless sudo (or run as root). + + Args: + volume (Volume): The hotmount Volume to mount. Must be of type ``VolumeType.HOTMOUNT``. + mount_path (str): The absolute path inside the Sandbox to mount the Volume at. + + Example: + ```python + volume = await daytona.volume.get("shared-fs") + await sandbox.mount_volume(volume, "/mnt/shared") + ``` + """ + if volume.type != VolumeType.HOTMOUNT: + raise DaytonaValidationError( + f"Only hotmount volumes can be mounted on the fly. Volume '{volume.name}' is of type '{volume.type}'." + ) + + if self._volume_service is None: + raise DaytonaError("Volume service is not available for this Sandbox instance.") + + mount_token = await self._volume_service.get_mount_token(volume) + command = build_hotmount_mount_command(mount_token, mount_path) + response = await self._process.exec(command) + if response.exit_code != 0: + raise DaytonaError(f"Failed to mount hotmount volume '{volume.name}': {response.result}") + + @with_instrumentation() + async def pull_volumes(self, volume: Volume | None = None) -> list[VolumePullResult]: + """Pulls the latest state of the Sandbox's blockmount Volumes into the running Sandbox. + + Blockmount volumes reconcile in the background: each sandbox writes to a private scratch + that is committed to the shared store periodically, but other sandboxes' commits only + appear locally on re-materialize. This method makes them appear immediately, without + stopping the Sandbox: it commits this Sandbox's local changes (so they participate in the + merge), then applies the volume's latest merged state in place. Files modified locally + after another sandbox's change keep the local version (last-change-wins by mtime). + + Args: + volume (Volume | None): The blockmount Volume to pull. Omit to pull every blockmount + Volume attached to the Sandbox. + + Returns: + list[VolumePullResult]: Per-volume pull results. + + Example: + ```python + # sandbox B picks up what sandbox A committed, while both keep running + results = await sandbox.pull_volumes() + ``` + """ + if volume is not None and volume.type != VolumeType.BLOCKMOUNT: + raise DaytonaValidationError( + f"Only blockmount volumes can be pulled. Volume '{volume.name}' is of type '{volume.type}'." + ) + + payload: dict[str, str] = {"volumeId": volume.id} if volume is not None else {} + method, url, header_params, body, _ = self._toolbox_api.param_serialize( + method="POST", + resource_path="/volumes/pull", + header_params={"Content-Type": "application/json", "Accept": "application/json"}, + body=payload, + ) + response = await self._toolbox_api.call_api(method, url, header_params=header_params, body=body) + await response.read() + if response.status != 200: + raise DaytonaError(f"Failed to pull volumes: {response.data.decode('utf-8', errors='replace')}") + parsed: dict[str, list[dict[str, object]]] = json.loads(response.data) + return [VolumePullResult.model_validate(item) for item in parsed.get("results") or []] + @with_instrumentation() def create_lsp_server( self, language_id: LspLanguageId | LspLanguageIdLiteral, path_to_project: str diff --git a/sdk-python/src/daytona/_async/volume.py b/sdk-python/src/daytona/_async/volume.py index da298aef0..c35928241 100644 --- a/sdk-python/src/daytona/_async/volume.py +++ b/sdk-python/src/daytona/_async/volume.py @@ -3,7 +3,9 @@ from __future__ import annotations -from daytona_api_client_async import CreateVolume, VolumesApi +from typing import Optional + +from daytona_api_client_async import CreateVolume, HotmountRegion, Region, VolumeMountTokenDto, VolumesApi, VolumeType from daytona_api_client_async.exceptions import NotFoundException from .._utils.otel_decorator import with_instrumentation @@ -33,12 +35,21 @@ async def list(self) -> list[Volume]: return [Volume.from_dto(volume) for volume in await self.__volumes_api.list_volumes()] @with_instrumentation() - async def get(self, name: str, create: bool = False) -> Volume: + async def get( + self, + name: str, + create: bool = False, + type: Optional[VolumeType] = None, # pylint: disable=redefined-builtin + region: Optional[str] = None, + ) -> Volume: """Get a Volume by name. Args: name (str): Name of the Volume to get. create (bool): If True, create a new Volume if it doesn't exist. + type (Optional[VolumeType]): Type of the Volume to create (only used if create is True). + region (Optional[str]): Region to create the Volume in. Required for blockmount volumes + (pins the Volume to that region); selects the deployment region for hotmount volumes. Returns: Volume: The Volume object. @@ -54,15 +65,28 @@ async def get(self, name: str, create: bool = False) -> Volume: return Volume.from_dto(await self.__volumes_api.get_volume_by_name(name)) except NotFoundException as e: if create: - return await self.create(name) + return await self.create(name, type=type, region=region) raise e @with_instrumentation() - async def create(self, name: str) -> Volume: + async def create( + self, + name: str, + type: Optional[VolumeType] = None, # pylint: disable=redefined-builtin + region: Optional[str] = None, + ) -> Volume: """Create a new Volume. Args: name (str): Name of the Volume to create. + type (Optional[VolumeType]): Type of the Volume. Defaults to legacy. + region (Optional[str]): Region to create the Volume in. For blockmount volumes it selects + the region-local store the Volume's data lives in — a performance/placement knob, not an + attach restriction, so Sandboxes in any region can attach it (colocation is just faster); + optional for blockmount, defaulting to the organization's default region (or the first + region offering blockmount) when omitted. For hotmount volumes it selects the deployment + region (defaults to an active region). Not allowed for legacy volumes. The Volume's region + is fixed for its lifetime. Returns: Volume: The Volume object. @@ -74,7 +98,68 @@ async def create(self, name: str) -> Volume: print(f"{volume.name} ({volume.id}); state: {volume.state}") ``` """ - return Volume.from_dto(await self.__volumes_api.create_volume(CreateVolume(name=name))) + return Volume.from_dto( + await self.__volumes_api.create_volume(CreateVolume(name=name, type=type, region=region)) + ) + + @with_instrumentation() + async def list_hotmount_regions(self) -> list[HotmountRegion]: + """List the hotmount regions available for volume creation. + + Returns: + list[HotmountRegion]: The active hotmount regions (id, label, geo). + + Example: + ```python + async with AsyncDaytona() as daytona: + for region in await daytona.volume.list_hotmount_regions(): + print(f"{region.region} - {region.label}") + ``` + """ + return await self.__volumes_api.list_hotmount_regions() + + @with_instrumentation() + async def list_blockmount_regions(self) -> list[Region]: + """List the regions where blockmount Volumes can be created. + + A blockmount Volume's data lives in the region it is created in (a performance/placement knob — + Sandboxes in any region can attach it, colocation is just faster). Only regions a superadmin has + enabled for blockmount are returned. + + Returns: + list[Region]: The regions that support blockmount volumes. + + Example: + ```python + async with AsyncDaytona() as daytona: + for region in await daytona.volume.list_blockmount_regions(): + print(f"{region.id} - {region.name}") + ``` + """ + return await self.__volumes_api.list_blockmount_regions() + + @with_instrumentation() + async def get_mount_token(self, volume: Volume) -> VolumeMountTokenDto: + """Create a short-lived mount token for a hotmount Volume. + + The token, together with the returned region gateway/binaries endpoints, is used to + bootstrap the hotmount agent (inside a Sandbox or on customer infrastructure) and mount + the Volume on the fly. Only hotmount volumes support this. + + Args: + volume (Volume): The hotmount Volume to obtain a mount token for. + + Returns: + VolumeMountTokenDto: The mount token, region endpoints, and expiration. + + Example: + ```python + async with AsyncDaytona() as daytona: + volume = await daytona.volume.get("shared-fs") + token = await daytona.volume.get_mount_token(volume) + ``` + """ + return await self.__volumes_api.create_volume_mount_token(volume.id) @with_instrumentation() async def delete(self, volume: Volume) -> None: diff --git a/sdk-python/src/daytona/_sync/daytona.py b/sdk-python/src/daytona/_sync/daytona.py index 5917be839..a9c44d3bb 100644 --- a/sdk-python/src/daytona/_sync/daytona.py +++ b/sdk-python/src/daytona/_sync/daytona.py @@ -506,6 +506,7 @@ def should_terminate(): self._sandbox_api, validated_language.value, http_client=self._http_client, + volume_service=self.volume, ) if sandbox.state != SandboxState.STARTED: @@ -568,6 +569,7 @@ def get(self, sandbox_id_or_name: str) -> Sandbox: self._sandbox_api, language, http_client=self._http_client, + volume_service=self.volume, ) @intercept_errors(message_prefix="Failed to list sandboxes: ") @@ -611,6 +613,7 @@ def list( self._sandbox_api, language, http_client=self._http_client, + volume_service=self.volume, ) cursor = response.next_cursor or None diff --git a/sdk-python/src/daytona/_sync/sandbox.py b/sdk-python/src/daytona/_sync/sandbox.py index 3f3615079..90dca6cd1 100644 --- a/sdk-python/src/daytona/_sync/sandbox.py +++ b/sdk-python/src/daytona/_sync/sandbox.py @@ -3,7 +3,9 @@ from __future__ import annotations +import json import time +from typing import TYPE_CHECKING, Optional import httpx from deprecated import deprecated @@ -43,6 +45,7 @@ from ..common.errors import DaytonaError, DaytonaNotFoundError, DaytonaValidationError from ..common.lsp_server import LspLanguageId, LspLanguageIdLiteral from ..common.sandbox import Resources +from ..common.volume import Volume, VolumePullResult, VolumeType, build_hotmount_mount_command from ..internal.toolbox_api_client_proxy import ToolboxApiClientProxy from .code_interpreter import CodeInterpreter from .computer_use import ComputerUse @@ -51,6 +54,9 @@ from .lsp_server import LspServer from .process import Process +if TYPE_CHECKING: + from .volume import VolumeService + class Sandbox(SandboxDto): """Represents a Daytona Sandbox. @@ -109,6 +115,7 @@ class Sandbox(SandboxDto): _process: Process = PrivateAttr() _computer_use: ComputerUse = PrivateAttr() _code_interpreter: CodeInterpreter = PrivateAttr() + _volume_service: Optional["VolumeService"] = PrivateAttr(default=None) # TODO: Remove model_config once everything is migrated to pydantic # pylint: disable=fixme model_config: ConfigDict = ConfigDict(arbitrary_types_allowed=True) @@ -120,6 +127,7 @@ def __init__( sandbox_api: SandboxApi, language: str, http_client: httpx.Client, + volume_service: Optional["VolumeService"] = None, ): """Initialize a new Sandbox instance. @@ -132,6 +140,7 @@ def __init__( super().__init__(**sandbox_dto.model_dump()) self.__process_sandbox_dto(sandbox_dto) self._sandbox_api: SandboxApi = sandbox_api + self._volume_service = volume_service self._http_client: httpx.Client = http_client # Wrap the toolbox API client to inject the sandbox ID into the resource path self._toolbox_api: ToolboxApiClientProxy[ApiClient] = ToolboxApiClientProxy( @@ -233,6 +242,85 @@ def get_work_dir(self) -> str: response = self._info_api.get_work_dir() return response.dir + @with_instrumentation() + def mount_volume(self, volume: Volume, mount_path: str) -> None: + """Mounts a hotmount Volume into the running Sandbox on the fly. + + Unlike legacy and blockmount volumes (which are attached at Sandbox creation), hotmount + volumes are mounted at runtime: this method requests a short-lived mount token from the API + and bootstraps the hotmount agent inside the Sandbox (downloading and running the region's + ``init.sh``), mounting the filesystem at the given path. + + The Sandbox must have ``/dev/fuse`` available, outbound access to the region gateway and + binaries bucket, and passwordless sudo (or run as root). + + Args: + volume (Volume): The hotmount Volume to mount. Must be of type ``VolumeType.HOTMOUNT``. + mount_path (str): The absolute path inside the Sandbox to mount the Volume at. + + Example: + ```python + volume = daytona.volume.get("shared-fs") + sandbox.mount_volume(volume, "/mnt/shared") + ``` + """ + if volume.type != VolumeType.HOTMOUNT: + raise DaytonaValidationError( + f"Only hotmount volumes can be mounted on the fly. Volume '{volume.name}' is of type '{volume.type}'." + ) + + if self._volume_service is None: + raise DaytonaError("Volume service is not available for this Sandbox instance.") + + mount_token = self._volume_service.get_mount_token(volume) + command = build_hotmount_mount_command(mount_token, mount_path) + response = self._process.exec(command) + if response.exit_code != 0: + raise DaytonaError(f"Failed to mount hotmount volume '{volume.name}': {response.result}") + + @with_instrumentation() + def pull_volumes(self, volume: Volume | None = None) -> list[VolumePullResult]: + """Pulls the latest state of the Sandbox's blockmount Volumes into the running Sandbox. + + Blockmount volumes reconcile in the background: each sandbox writes to a private scratch + that is committed to the shared store periodically, but other sandboxes' commits only + appear locally on re-materialize. This method makes them appear immediately, without + stopping the Sandbox: it commits this Sandbox's local changes (so they participate in the + merge), then applies the volume's latest merged state in place. Files modified locally + after another sandbox's change keep the local version (last-change-wins by mtime). + + Args: + volume (Volume | None): The blockmount Volume to pull. Omit to pull every blockmount + Volume attached to the Sandbox. + + Returns: + list[VolumePullResult]: Per-volume pull results. + + Example: + ```python + # sandbox B picks up what sandbox A committed, while both keep running + results = sandbox.pull_volumes() + ``` + """ + if volume is not None and volume.type != VolumeType.BLOCKMOUNT: + raise DaytonaValidationError( + f"Only blockmount volumes can be pulled. Volume '{volume.name}' is of type '{volume.type}'." + ) + + payload: dict[str, str] = {"volumeId": volume.id} if volume is not None else {} + method, url, header_params, body, _ = self._toolbox_api.param_serialize( + method="POST", + resource_path="/volumes/pull", + header_params={"Content-Type": "application/json", "Accept": "application/json"}, + body=payload, + ) + response = self._toolbox_api.call_api(method, url, header_params=header_params, body=body) + response.read() + if response.status != 200: + raise DaytonaError(f"Failed to pull volumes: {response.data.decode('utf-8', errors='replace')}") + parsed: dict[str, list[dict[str, object]]] = json.loads(response.data) + return [VolumePullResult.model_validate(item) for item in parsed.get("results") or []] + @with_instrumentation() def create_lsp_server(self, language_id: LspLanguageId | LspLanguageIdLiteral, path_to_project: str) -> LspServer: """Creates a new Language Server Protocol (LSP) server instance. diff --git a/sdk-python/src/daytona/_sync/volume.py b/sdk-python/src/daytona/_sync/volume.py index debfcf57f..46cc86625 100644 --- a/sdk-python/src/daytona/_sync/volume.py +++ b/sdk-python/src/daytona/_sync/volume.py @@ -3,7 +3,9 @@ from __future__ import annotations -from daytona_api_client import CreateVolume, VolumesApi +from typing import Optional + +from daytona_api_client import CreateVolume, HotmountRegion, Region, VolumeMountTokenDto, VolumesApi, VolumeType from daytona_api_client.exceptions import NotFoundException from .._utils.otel_decorator import with_instrumentation @@ -33,12 +35,21 @@ def list(self) -> list[Volume]: return [Volume.from_dto(volume) for volume in self.__volumes_api.list_volumes()] @with_instrumentation() - def get(self, name: str, create: bool = False) -> Volume: + def get( + self, + name: str, + create: bool = False, + type: Optional[VolumeType] = None, # pylint: disable=redefined-builtin + region: Optional[str] = None, + ) -> Volume: """Get a Volume by name. Args: name (str): Name of the Volume to get. create (bool): If True, create a new Volume if it doesn't exist. + type (Optional[VolumeType]): Type of the Volume to create (only used if create is True). + region (Optional[str]): Region to create the Volume in. Required for blockmount volumes + (pins the Volume to that region); selects the deployment region for hotmount volumes. Returns: Volume: The Volume object. @@ -54,15 +65,28 @@ def get(self, name: str, create: bool = False) -> Volume: return Volume.from_dto(self.__volumes_api.get_volume_by_name(name)) except NotFoundException as e: if create: - return self.create(name) + return self.create(name, type=type, region=region) raise e @with_instrumentation() - def create(self, name: str) -> Volume: + def create( + self, + name: str, + type: Optional[VolumeType] = None, # pylint: disable=redefined-builtin + region: Optional[str] = None, + ) -> Volume: """Create a new Volume. Args: name (str): Name of the Volume to create. + type (Optional[VolumeType]): Type of the Volume. Defaults to legacy. + region (Optional[str]): Region to create the Volume in. For blockmount volumes it selects + the region-local store the Volume's data lives in — a performance/placement knob, not an + attach restriction, so Sandboxes in any region can attach it (colocation is just faster); + optional for blockmount, defaulting to the organization's default region (or the first + region offering blockmount) when omitted. For hotmount volumes it selects the deployment + region (defaults to an active region). Not allowed for legacy volumes. The Volume's region + is fixed for its lifetime. Returns: Volume: The Volume object. @@ -74,7 +98,66 @@ def create(self, name: str) -> Volume: print(f"{volume.name} ({volume.id}); state: {volume.state}") ``` """ - return Volume.from_dto(self.__volumes_api.create_volume(CreateVolume(name=name))) + return Volume.from_dto(self.__volumes_api.create_volume(CreateVolume(name=name, type=type, region=region))) + + @with_instrumentation() + def list_hotmount_regions(self) -> list[HotmountRegion]: + """List the hotmount regions available for volume creation. + + Returns: + list[HotmountRegion]: The active hotmount regions (id, label, geo). + + Example: + ```python + daytona = Daytona() + for region in daytona.volume.list_hotmount_regions(): + print(f"{region.region} - {region.label}") + ``` + """ + return self.__volumes_api.list_hotmount_regions() + + @with_instrumentation() + def list_blockmount_regions(self) -> list[Region]: + """List the regions where blockmount Volumes can be created. + + A blockmount Volume's data lives in the region it is created in (a performance/placement knob — + Sandboxes in any region can attach it, colocation is just faster). Only regions a superadmin has + enabled for blockmount are returned. + + Returns: + list[Region]: The regions that support blockmount volumes. + + Example: + ```python + daytona = Daytona() + for region in daytona.volume.list_blockmount_regions(): + print(f"{region.id} - {region.name}") + ``` + """ + return self.__volumes_api.list_blockmount_regions() + + @with_instrumentation() + def get_mount_token(self, volume: Volume) -> VolumeMountTokenDto: + """Create a short-lived mount token for a hotmount Volume. + + The token, together with the returned region gateway/binaries endpoints, is used to + bootstrap the hotmount agent (inside a Sandbox or on customer infrastructure) and mount + the Volume on the fly. Only hotmount volumes support this. + + Args: + volume (Volume): The hotmount Volume to obtain a mount token for. + + Returns: + VolumeMountTokenDto: The mount token, region endpoints, and expiration. + + Example: + ```python + daytona = Daytona() + volume = daytona.volume.get("shared-fs") + token = daytona.volume.get_mount_token(volume) + ``` + """ + return self.__volumes_api.create_volume_mount_token(volume.id) @with_instrumentation() def delete(self, volume: Volume) -> None: diff --git a/sdk-python/src/daytona/common/volume.py b/sdk-python/src/daytona/common/volume.py index 16b7bc1f0..595635996 100644 --- a/sdk-python/src/daytona/common/volume.py +++ b/sdk-python/src/daytona/common/volume.py @@ -3,13 +3,63 @@ from __future__ import annotations +from typing import ClassVar + +from pydantic import BaseModel, ConfigDict, Field + from daytona_api_client import SandboxVolume as ApiVolumeMount -from daytona_api_client import VolumeDto +from daytona_api_client import VolumeDto, VolumeMountTokenDto, VolumeType from daytona_api_client_async import SandboxVolume as AsyncApiVolumeMount from daytona_api_client_async import VolumeDto as AsyncVolumeDto +from daytona_api_client_async import VolumeMountTokenDto as AsyncVolumeMountTokenDto + +__all__ = [ + "Volume", + "VolumeMount", + "VolumeMountTokenDto", + "VolumePullResult", + "VolumeType", + "build_hotmount_mount_command", +] + + +def build_hotmount_mount_command(mount_token: VolumeMountTokenDto | AsyncVolumeMountTokenDto, mount_path: str) -> str: + """Build the shell command that bootstraps the hotmount agent and mounts the volume. + + It exports the SEAWEED_* environment contract and runs the region's ``init.sh``, using + passwordless sudo when not already root (sudo strips env, so the vars are passed via ``env``). + """ + env_vars = { + "SEAWEED_TOKEN": mount_token.token, + "SEAWEED_GATEWAY_GRPC": mount_token.gateway_grpc, + "SEAWEED_GATEWAY_HTTP": mount_token.gateway_http, + "SEAWEED_BINARIES_URL": mount_token.binaries_url, + "SEAWEED_MOUNT_DIR": mount_path, + "SEAWEED_VERSION": mount_token.version, + } + env_assignments = " ".join(f"{key}='{value}'" for key, value in env_vars.items() if value) + # The bootstrap (and init.sh itself) requires curl. Fail loudly if it is missing or the + # download fails, rather than letting a broken ``curl ... | bash`` pipe exit 0 and mount nothing. + inner = "; ".join( + [ + "set -e", + ( + 'if ! command -v curl >/dev/null 2>&1; then echo "hotmount: curl is required to ' + + 'bootstrap the agent but was not found in the sandbox" >&2; exit 1; fi' + ), + 'mkdir -p "$SEAWEED_MOUNT_DIR"', + 'init_script="$(curl -fsSL "$SEAWEED_BINARIES_URL/init.sh")"', + 'printf %s "$init_script" | bash', + ] + ) + return ( + 'if [ "$(id -u)" != 0 ]; then SUDO="sudo -n"; else SUDO=""; fi; ' + f"$SUDO env {env_assignments} " + f"bash -c '{inner}'" + ) -class VolumeMount(ApiVolumeMount, AsyncApiVolumeMount): +class VolumeMount(ApiVolumeMount, AsyncApiVolumeMount): # pyright: ignore[reportIncompatibleVariableOverride] """Represents a Volume mount configuration for a Sandbox. Attributes: @@ -37,3 +87,28 @@ class Volume(VolumeDto): @classmethod def from_dto(cls, dto: VolumeDto | AsyncVolumeDto) -> "Volume": return cls.model_validate(dto.model_dump()) + + +class VolumePullResult(BaseModel): + """Result of an explicit blockmount Volume pull into a running Sandbox. + + Attributes: + volume_id (str): The Volume that was pulled. + manifest_id (str | None): The merged manifest the Sandbox's scratch was advanced to. + up_to_date (bool): True when the Sandbox already reflected the latest merged state. + files_written (int): Files and symlinks written into the Sandbox by the pull. + deleted (int): Paths removed because they were deleted in the merged state. + skipped_local_newer (int): Paths left untouched because the Sandbox has a strictly newer + local modification (the next commit's last-change-wins merge resolves them). + bytes_fetched (int): Content bytes downloaded from the store. + """ + + model_config: ClassVar[ConfigDict] = ConfigDict(populate_by_name=True) + + volume_id: str = Field(alias="volumeId") + manifest_id: str | None = Field(default=None, alias="manifestId") + up_to_date: bool = Field(default=False, alias="upToDate") + files_written: int = Field(default=0, alias="filesWritten") + deleted: int = 0 + skipped_local_newer: int = Field(default=0, alias="skippedLocalNewer") + bytes_fetched: int = Field(default=0, alias="bytesFetched") diff --git a/sdk-ruby/lib/daytona/daytona.rb b/sdk-ruby/lib/daytona/daytona.rb index 28075ce31..4bdc01cb9 100644 --- a/sdk-ruby/lib/daytona/daytona.rb +++ b/sdk-ruby/lib/daytona/daytona.rb @@ -322,7 +322,8 @@ def to_sandbox(sandbox_dto:) sandbox_dto:, config:, sandbox_api:, - otel_state: @otel_state + otel_state: @otel_state, + volume_service: @volume ) end diff --git a/sdk-ruby/lib/daytona/sandbox.rb b/sdk-ruby/lib/daytona/sandbox.rb index e8a021792..44cc437ca 100644 --- a/sdk-ruby/lib/daytona/sandbox.rb +++ b/sdk-ruby/lib/daytona/sandbox.rb @@ -132,11 +132,12 @@ class Sandbox # rubocop:disable Metrics/ClassLength # @params sandbox_api [DaytonaApiClient::SandboxApi] # @params sandbox_dto [DaytonaApiClient::Sandbox, DaytonaApiClient::SandboxListItem] # @params otel_state [Daytona::OtelState, nil] - def initialize(sandbox_dto:, config:, sandbox_api:, otel_state: nil) # rubocop:disable Metrics/MethodLength + def initialize(sandbox_dto:, config:, sandbox_api:, otel_state: nil, volume_service: nil) # rubocop:disable Metrics/MethodLength process_response(sandbox_dto) @config = config @sandbox_api = sandbox_api @otel_state = otel_state + @volume_service = volume_service # Create toolbox API clients with dynamic configuration toolbox_api_config = build_toolbox_api_config @@ -180,6 +181,7 @@ def initialize(sandbox_dto:, config:, sandbox_api:, otel_state: nil) # rubocop:d @lsp_api = lsp_api @info_api = info_api @server_api = server_api + @toolbox_client = create_authenticated_client.call end # Archives the sandbox, making it inactive and preserving its state. When sandboxes are @@ -338,6 +340,72 @@ def update_env(env: nil, unset: nil) raise Sdk::Error, "Failed to update environment: #{e.message}" end + # Mounts a hotmount Volume into the running Sandbox on the fly. + # + # Unlike legacy and blockmount volumes (which are attached at Sandbox creation), hotmount + # volumes are mounted at runtime: this method requests a short-lived mount token from the API + # and bootstraps the hotmount agent inside the Sandbox (downloading and running the region's + # +init.sh+), mounting the filesystem at the given path. + # + # The Sandbox must have +/dev/fuse+ available, outbound access to the region gateway and + # binaries bucket, and passwordless sudo (or run as root). + # + # @param volume [Daytona::Volume] The hotmount Volume to mount. Must be of type hotmount. + # @param mount_path [String] The absolute path inside the Sandbox to mount the Volume at. + # @return [void] + # + # @example + # volume = daytona.volume.get('shared-fs') + # sandbox.mount_volume(volume, '/mnt/shared') + def mount_volume(volume, mount_path) + unless volume.type == DaytonaApiClient::VolumeType::HOTMOUNT + raise Sdk::Error, + "Only hotmount volumes can be mounted on the fly. Volume '#{volume.name}' is of type '#{volume.type}'." + end + + raise Sdk::Error, 'Volume service is not available for this Sandbox instance.' if @volume_service.nil? + + mount_token = @volume_service.get_mount_token(volume) + command = build_hotmount_mount_command(mount_token, mount_path) + response = process.exec(command:) + return if response.exit_code.zero? + + raise Sdk::Error, "Failed to mount hotmount volume '#{volume.name}': #{response.result}" + end + + # Pulls the latest state of the Sandbox's blockmount Volumes into the running Sandbox. + # + # Blockmount volumes reconcile in the background: each sandbox writes to a private scratch + # that is committed to the shared store periodically, but other sandboxes' commits only + # appear locally on re-materialize. This method makes them appear immediately, without + # stopping the Sandbox: it commits this Sandbox's local changes (so they participate in the + # merge), then applies the volume's latest merged state in place. Files modified locally + # after another sandbox's change keep the local version (last-change-wins by mtime). + # + # @param volume [Daytona::Volume, nil] The blockmount Volume to pull; nil pulls every + # blockmount Volume attached to the Sandbox. + # @return [Array] per-volume pull results + # + # @example + # # sandbox B picks up what sandbox A committed, while both keep running + # results = sandbox.pull_volumes + def pull_volumes(volume = nil) + if volume && volume.type != DaytonaApiClient::VolumeType::BLOCKMOUNT + raise Sdk::Error, + "Only blockmount volumes can be pulled. Volume '#{volume.name}' is of type '#{volume.type}'." + end + + payload = volume ? { volumeId: volume.id } : {} + data, _status, _headers = @toolbox_client.call_api( + :POST, '/volumes/pull', + header_params: { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }, + body: payload.to_json, + return_type: 'Object' + ) + results = data.is_a?(Hash) ? (data[:results] || data['results']) : nil + results || [] + end + # Sets labels for the Sandbox. # # @param labels [Hash] @@ -624,6 +692,40 @@ def pause(timeout: DEFAULT_TIMEOUT) # @return [Daytona::OtelState, nil] attr_reader :otel_state + # Build the shell command that bootstraps the hotmount agent and mounts the volume inside a + # Sandbox. It exports the SEAWEED_* environment contract and runs the region's +init.sh+, using + # passwordless sudo when not already root (sudo strips env, so the vars are passed via +env+). + # + # @param mount_token [DaytonaApiClient::VolumeMountTokenDto] + # @param mount_path [String] + # @return [String] + def build_hotmount_mount_command(mount_token, mount_path) + env_vars = { + 'SEAWEED_TOKEN' => mount_token.token, + 'SEAWEED_GATEWAY_GRPC' => mount_token.gateway_grpc, + 'SEAWEED_GATEWAY_HTTP' => mount_token.gateway_http, + 'SEAWEED_BINARIES_URL' => mount_token.binaries_url, + 'SEAWEED_MOUNT_DIR' => mount_path, + 'SEAWEED_VERSION' => mount_token.version + } + env_assignments = env_vars.reject { |_, value| value.nil? || value.empty? } + .map { |key, value| "#{key}='#{value}'" } + .join(' ') + # The bootstrap (and init.sh itself) requires curl. Fail loudly if it is missing or the + # download fails, rather than letting a broken `curl ... | bash` pipe exit 0 and mount nothing. + inner = [ + 'set -e', + 'if ! command -v curl >/dev/null 2>&1; then echo "hotmount: curl is required to ' \ + 'bootstrap the agent but was not found in the sandbox" >&2; exit 1; fi', + 'mkdir -p "$SEAWEED_MOUNT_DIR"', + 'init_script="$(curl -fsSL "$SEAWEED_BINARIES_URL/init.sh")"', + 'printf %s "$init_script" | bash' + ].join('; ') + 'if [ "$(id -u)" != 0 ]; then SUDO="sudo -n"; else SUDO=""; fi; ' \ + "$SUDO env #{env_assignments} " \ + "bash -c '#{inner}'" + end + # Build toolbox API configuration with dynamic base URL from preview link # @return [DaytonaToolboxApiClient::Configuration] def build_toolbox_api_config diff --git a/sdk-ruby/lib/daytona/volume.rb b/sdk-ruby/lib/daytona/volume.rb index 6b7ea5289..735a38f23 100644 --- a/sdk-ruby/lib/daytona/volume.rb +++ b/sdk-ruby/lib/daytona/volume.rb @@ -14,6 +14,12 @@ class Volume # @return [String] attr_reader :organization_id + # @return [String] + attr_reader :type + + # @return [Integer, nil] + attr_reader :size_in_gb + # @return [String] attr_reader :state @@ -36,6 +42,8 @@ def initialize(volume_dto) @id = volume_dto.id @name = volume_dto.name @organization_id = volume_dto.organization_id + @type = volume_dto.type + @size_in_gb = volume_dto.size_in_gb @state = volume_dto.state @created_at = volume_dto.created_at @updated_at = volume_dto.updated_at diff --git a/sdk-ruby/lib/daytona/volume_service.rb b/sdk-ruby/lib/daytona/volume_service.rb index b13a62767..c8294b7ba 100644 --- a/sdk-ruby/lib/daytona/volume_service.rb +++ b/sdk-ruby/lib/daytona/volume_service.rb @@ -19,8 +19,15 @@ def initialize(volumes_api, otel_state: nil) # Create new Volume. # # @param name [String] + # @param type [String, nil] The volume type. Defaults to legacy. + # @param region [String, nil] The region to create the Volume in. For blockmount volumes it selects + # the region-local store the Volume's data lives in — a performance/placement knob; Sandboxes in any + # region can attach it. Optional for blockmount, defaulting to the organization's default region when + # omitted. Selects the deployment region for hotmount volumes. # @return [Daytona::Volume] - def create(name) = Volume.new(volumes_api.create_volume(DaytonaApiClient::CreateVolume.new(name:))) + def create(name, type: nil, region: nil) + Volume.new(volumes_api.create_volume(DaytonaApiClient::CreateVolume.new(name:, type:, region:))) + end # Delete a Volume. # @@ -32,15 +39,42 @@ def delete(volume) = volumes_api.delete_volume(volume.id) # # @param name [String] # @param create [Boolean] + # @param type [String, nil] The volume type to create (only used if create is true). + # @param region [String, nil] The region to create the Volume in. For blockmount volumes it selects + # the region-local store the Volume's data lives in — a performance/placement knob; Sandboxes in any + # region can attach it. Optional for blockmount, defaulting to the organization's default region when + # omitted. Selects the deployment region for hotmount volumes. # @return [Daytona::Volume] - def get(name, create: false) + def get(name, create: false, type: nil, region: nil) Volume.new(volumes_api.get_volume_by_name(name)) rescue DaytonaApiClient::ApiError => e raise unless create && e.code == 404 && e.message.include?("Volume with name #{name} not found") - create(name) + create(name, type:, region:) end + # Create a short-lived mount token for a hotmount Volume. + # + # The token, together with the returned region gateway/binaries endpoints, is used to + # bootstrap the hotmount agent (inside a Sandbox or on customer infrastructure) and mount + # the Volume on the fly. Only hotmount volumes support this. + # + # @param volume [Daytona::Volume] + # @return [DaytonaApiClient::VolumeMountTokenDto] + def get_mount_token(volume) = volumes_api.create_volume_mount_token(volume.id) + + # List the hotmount regions available for volume creation. + # + # @return [Array] + def list_hotmount_regions = volumes_api.list_hotmount_regions + + # List the regions where blockmount Volumes can be created. A blockmount Volume's data lives in + # the region it is created in (a performance/placement knob — Sandboxes in any region can attach + # it, colocation is just faster). + # + # @return [Array] + def list_blockmount_regions = volumes_api.list_blockmount_regions + # List all Volumes. # # @return [Array] @@ -48,7 +82,8 @@ def list volumes_api.list_volumes.map { |volume| Volume.new(volume) } end - instrument :create, :delete, :get, :list, component: 'VolumeService' + instrument :create, :delete, :get, :get_mount_token, :list_hotmount_regions, :list_blockmount_regions, :list, + component: 'VolumeService' private diff --git a/sdk-typescript/src/Daytona.ts b/sdk-typescript/src/Daytona.ts index 515402183..c90280001 100644 --- a/sdk-typescript/src/Daytona.ts +++ b/sdk-typescript/src/Daytona.ts @@ -633,6 +633,7 @@ export class Daytona implements AsyncDisposable { new Configuration(structuredClone(this.clientConfig)), Daytona.createAxiosInstance(), this.sandboxApi, + this.volume, ) if (sandbox.state !== 'started') { @@ -673,6 +674,7 @@ export class Daytona implements AsyncDisposable { structuredClone(this.clientConfig), Daytona.createAxiosInstance(), this.sandboxApi, + this.volume, ) } @@ -688,7 +690,7 @@ export class Daytona implements AsyncDisposable { * } */ public list(query?: ListSandboxesQuery): AsyncIterableIterator { - const { sandboxApi, clientConfig } = this + const { sandboxApi, clientConfig, volume } = this const tracer = trace.getTracer('') async function* generator(): AsyncGenerator { @@ -759,7 +761,7 @@ export class Daytona implements AsyncDisposable { for (const sandbox of response.data.items) { // Sandbox ctor mutates clientConfig.basePath — clone per item. - yield new Sandbox(sandbox, structuredClone(clientConfig), Daytona.createAxiosInstance(), sandboxApi) + yield new Sandbox(sandbox, structuredClone(clientConfig), Daytona.createAxiosInstance(), sandboxApi, volume) } cursor = response.data.nextCursor ?? undefined diff --git a/sdk-typescript/src/Sandbox.ts b/sdk-typescript/src/Sandbox.ts index 7c8032bb5..81612964f 100644 --- a/sdk-typescript/src/Sandbox.ts +++ b/sdk-typescript/src/Sandbox.ts @@ -41,6 +41,43 @@ import { ComputerUse } from './ComputerUse' import type { AxiosInstance } from 'axios' import { CodeInterpreter } from './CodeInterpreter' import { WithInstrumentation } from './utils/otel.decorator' +import { VolumeType } from '@daytona/api-client' +import type { VolumeMountTokenDto } from '@daytona/api-client' +import type { Volume, VolumePullResult, VolumeService } from './Volume' + +/** + * Builds the shell command that bootstraps the hotmount agent and mounts the volume inside a + * Sandbox. It exports the SEAWEED_* environment contract and runs the region's `init.sh`, using + * passwordless sudo when not already root (sudo strips env, so the vars are passed via `env`). + */ +function buildHotmountMountCommand(mountToken: VolumeMountTokenDto, mountPath: string): string { + const envVars: Record = { + SEAWEED_TOKEN: mountToken.token, + SEAWEED_GATEWAY_GRPC: mountToken.gatewayGrpc, + SEAWEED_GATEWAY_HTTP: mountToken.gatewayHttp, + SEAWEED_BINARIES_URL: mountToken.binariesUrl, + SEAWEED_MOUNT_DIR: mountPath, + SEAWEED_VERSION: mountToken.version, + } + const envAssignments = Object.entries(envVars) + .filter(([, value]) => value !== undefined && value !== '') + .map(([key, value]) => `${key}='${value}'`) + .join(' ') + + // The bootstrap (and init.sh itself) requires curl. Fail loudly if it is missing or the + // download fails, rather than letting a broken `curl … | bash` pipe exit 0 and mount nothing. + const inner = [ + 'set -e', + 'if ! command -v curl >/dev/null 2>&1; then echo "hotmount: curl is required to bootstrap the agent but was not found in the sandbox" >&2; exit 1; fi', + 'mkdir -p "$SEAWEED_MOUNT_DIR"', + 'init_script="$(curl -fsSL "$SEAWEED_BINARIES_URL/init.sh")"', + 'printf %s "$init_script" | bash', + ].join('; ') + + return ( + `if [ "$(id -u)" != 0 ]; then SUDO="sudo -n"; else SUDO=""; fi; ` + `$SUDO env ${envAssignments} bash -c '${inner}'` + ) +} /** * Represents a Daytona Sandbox. @@ -143,6 +180,7 @@ export class Sandbox { private readonly clientConfig: Configuration, private readonly axiosInstance: AxiosInstance, private readonly sandboxApi: SandboxApi, + private readonly volumeService?: VolumeService, ) { this.processSandboxDto(sandboxDto) @@ -215,6 +253,86 @@ export class Sandbox { return response.data.dir } + /** + * Mounts a hotmount Volume into the running Sandbox on the fly. + * + * Unlike legacy and blockmount volumes (which are attached at Sandbox creation), hotmount + * volumes are mounted at runtime: this method requests a short-lived mount token from the API + * and bootstraps the hotmount agent inside the Sandbox (downloading and running the region's + * `init.sh`), mounting the filesystem at the given path. + * + * The Sandbox must have `/dev/fuse` available, outbound access to the region gateway (ports + * 443 and 18443) and binaries bucket, and passwordless sudo (or run as root). + * + * @param {Volume} volume - The hotmount Volume to mount. Must be of type `VolumeType.HOTMOUNT`. + * @param {string} mountPath - The absolute path inside the Sandbox to mount the Volume at + * @returns {Promise} + * @throws {DaytonaError} If the Volume is not a hotmount volume, or if mounting fails + * + * @example + * const volume = await daytona.volume.get("shared-fs"); + * await sandbox.mountVolume(volume, "/mnt/shared"); + */ + @WithInstrumentation() + public async mountVolume(volume: Volume, mountPath: string): Promise { + if (volume.type !== VolumeType.HOTMOUNT) { + throw new DaytonaValidationError( + `Only hotmount volumes can be mounted on the fly. Volume '${volume.name}' is of type '${volume.type}'.`, + ) + } + + if (!this.volumeService) { + throw new DaytonaError('Volume service is not available for this Sandbox instance.') + } + + const mountToken = await this.volumeService.getMountToken(volume) + + const command = buildHotmountMountCommand(mountToken, mountPath) + const response = await this.process.executeCommand(command) + if (response.exitCode !== 0) { + throw new DaytonaError(`Failed to mount hotmount volume '${volume.name}': ${response.result}`) + } + } + + /** + * Pulls the latest state of the Sandbox's blockmount Volumes into the running Sandbox. + * + * Blockmount volumes reconcile in the background: each sandbox writes to a private scratch + * that is committed to the shared store periodically, but other sandboxes' commits only + * appear locally on re-materialize. This method makes them appear immediately, without + * stopping the Sandbox: it commits this Sandbox's local changes (so they participate in the + * merge), then applies the volume's latest merged state in place. Files modified locally + * after another sandbox's change keep the local version (last-change-wins by mtime). + * + * @param {Volume} [volume] - The blockmount Volume to pull. Omit to pull every blockmount + * Volume attached to the Sandbox. + * @returns {Promise} Per-volume pull results + * @throws {DaytonaError} If the Sandbox has no matching mounted blockmount Volume + * + * @example + * // sandbox B picks up what sandbox A committed, while both keep running + * const results = await sandbox.pullVolumes(); + * console.log(results); // [{ volumeId, upToDate, filesWritten, deleted, ... }] + */ + @WithInstrumentation() + public async pullVolumes(volume?: Volume): Promise { + if (volume && volume.type !== VolumeType.BLOCKMOUNT) { + throw new DaytonaValidationError( + `Only blockmount volumes can be pulled. Volume '${volume.name}' is of type '${volume.type}'.`, + ) + } + // Raw toolbox call (the daemon's generated client has no pull op — the runner intercepts + // this path), so replicate what the generated clients do: resolve against this sandbox's + // clientConfig.basePath (the shared axios instance's default baseURL is mutated by every + // Sandbox construction and may point at another sandbox) and inject the auth headers. + const response = await this.axiosInstance.post( + `${this.clientConfig.basePath}/volumes/pull`, + volume ? { volumeId: volume.id } : {}, + { headers: this.clientConfig.baseOptions?.headers }, + ) + return response.data.results ?? [] + } + /** * Creates a new Language Server Protocol (LSP) server instance. * diff --git a/sdk-typescript/src/Volume.ts b/sdk-typescript/src/Volume.ts index e974ccf19..d597f559e 100644 --- a/sdk-typescript/src/Volume.ts +++ b/sdk-typescript/src/Volume.ts @@ -3,11 +3,30 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { VolumesApi } from '@daytona/api-client' -import type { VolumeDto } from '@daytona/api-client' +import { VolumesApi, VolumeType } from '@daytona/api-client' +import type { VolumeDto, VolumeMountTokenDto, HotmountRegion, Region } from '@daytona/api-client' import { DaytonaNotFoundError } from './errors/DaytonaError' import { WithInstrumentation } from './utils/otel.decorator' +export { VolumeType } +export type { VolumeMountTokenDto, HotmountRegion, Region } + +/** + * Options for creating a Volume. + * + * @property {VolumeType} [type] - The type of the Volume. Defaults to `VolumeType.LEGACY`. + * @property {string} [region] - The region to create the Volume in. For `VolumeType.BLOCKMOUNT` volumes it + * selects the region-local store the Volume's data lives in — a performance/placement knob, not an attach + * restriction, so Sandboxes in any region can attach the Volume (colocation is just faster). Optional for + * blockmount: when omitted it defaults to the organization's default region (or the first region offering + * blockmount). For `VolumeType.HOTMOUNT` volumes it selects the hotmount deployment region (defaults to an + * active region). Not allowed for legacy volumes. The Volume's region is fixed for its lifetime. + */ +export interface CreateVolumeOptions { + type?: VolumeType + region?: string +} + /** * Represents a Daytona Volume which is a shared storage volume for Sandboxes. * @@ -21,6 +40,28 @@ import { WithInstrumentation } from './utils/otel.decorator' */ export type Volume = VolumeDto & { __brand: 'Volume' } +/** + * Result of an explicit blockmount Volume pull into a running Sandbox. + * + * @property {string} volumeId - The Volume that was pulled + * @property {string} [manifestId] - The merged manifest the Sandbox's scratch was advanced to + * @property {boolean} upToDate - True when the Sandbox already reflected the latest merged state + * @property {number} filesWritten - Files and symlinks written into the Sandbox by the pull + * @property {number} deleted - Paths removed because they were deleted in the merged state + * @property {number} skippedLocalNewer - Paths left untouched because the Sandbox has a strictly + * newer local modification (the next commit's last-change-wins merge resolves them) + * @property {number} bytesFetched - Content bytes downloaded from the store + */ +export interface VolumePullResult { + volumeId: string + manifestId?: string + upToDate: boolean + filesWritten: number + deleted: number + skippedLocalNewer: number + bytesFetched: number +} + /** * Service for managing Daytona Volumes. * @@ -56,6 +97,7 @@ export class VolumeService { * * @param {string} name - Name of the Volume to retrieve * @param {boolean} create - Whether to create the Volume if it does not exist + * @param {CreateVolumeOptions} [options] - Options used when creating the Volume (only applied if `create` is true) * @returns {Promise} The requested Volume * @throws {Error} If the Volume does not exist or cannot be accessed * @@ -65,13 +107,13 @@ export class VolumeService { * console.log(`Volume ${volume.name} is in state ${volume.state}`); */ @WithInstrumentation() - async get(name: string, create = false): Promise { + async get(name: string, create = false, options: CreateVolumeOptions = {}): Promise { try { const response = await this.volumesApi.getVolumeByName(name) return response.data as Volume } catch (error) { if (error instanceof DaytonaNotFoundError && create) { - return await this.create(name) + return await this.create(name, options) } throw error } @@ -81,6 +123,7 @@ export class VolumeService { * Creates a new Volume with the specified name. * * @param {string} name - Name for the new Volume + * @param {CreateVolumeOptions} [options] - Options for the new Volume, such as its type and size * @returns {Promise} The newly created Volume * @throws {Error} If the Volume cannot be created * @@ -88,13 +131,79 @@ export class VolumeService { * const daytona = new Daytona(); * const volume = await daytona.volume.create("my-data-volume"); * console.log(`Created volume ${volume.name} with ID ${volume.id}`); + * + * @example + * // Create a shared high-performance volume (local-first, reconciled through S3) + * const volume = await daytona.volume.create("shared-cache", { type: VolumeType.BLOCKMOUNT }); */ @WithInstrumentation() - async create(name: string): Promise { - const response = await this.volumesApi.createVolume({ name }) + async create(name: string, options: CreateVolumeOptions = {}): Promise { + const response = await this.volumesApi.createVolume({ + name, + type: options.type, + region: options.region, + }) return response.data as Volume } + /** + * Lists the hotmount regions available for volume creation. + * + * @returns {Promise} The active hotmount regions (id, label, geo) + * + * @example + * const daytona = new Daytona(); + * const regions = await daytona.volume.listHotmountRegions(); + * regions.forEach(r => console.log(`${r.region} - ${r.label}`)); + */ + @WithInstrumentation() + async listHotmountRegions(): Promise { + const response = await this.volumesApi.listHotmountRegions() + return response.data + } + + /** + * Lists the regions where blockmount Volumes can be created. + * + * A blockmount Volume's data lives in the region it is created in (a performance/placement knob — + * Sandboxes in any region can attach it, colocation is just faster). Only regions a superadmin has + * enabled for blockmount are returned. + * + * @returns {Promise} The regions that support blockmount volumes + * + * @example + * const daytona = new Daytona(); + * const regions = await daytona.volume.listBlockmountRegions(); + * regions.forEach(r => console.log(`${r.id} - ${r.name}`)); + */ + @WithInstrumentation() + async listBlockmountRegions(): Promise { + const response = await this.volumesApi.listBlockmountRegions() + return response.data + } + + /** + * Creates a short-lived mount token for a hotmount Volume. + * + * The token, together with the returned region gateway/binaries endpoints, is used to bootstrap + * the hotmount agent (inside a Sandbox or on customer infrastructure) and mount the Volume on the + * fly. Only `VolumeType.HOTMOUNT` volumes support this. + * + * @param {Volume} volume - The hotmount Volume to obtain a mount token for + * @returns {Promise} The mount token, region endpoints, and expiration + * @throws {Error} If the Volume is not a hotmount volume, is not ready, or cannot be accessed + * + * @example + * const daytona = new Daytona(); + * const volume = await daytona.volume.get("shared-fs"); + * const token = await daytona.volume.getMountToken(volume); + */ + @WithInstrumentation() + async getMountToken(volume: Volume): Promise { + const response = await this.volumesApi.createVolumeMountToken(volume.id) + return response.data + } + /** * Deletes a Volume. * diff --git a/sdk-typescript/src/index.ts b/sdk-typescript/src/index.ts index 4bacbb32c..42838c527 100644 --- a/sdk-typescript/src/index.ts +++ b/sdk-typescript/src/index.ts @@ -45,6 +45,15 @@ export { export { Image } from './Image' export { Sandbox } from './Sandbox' export type { ListSandboxesQuery } from './Sandbox' +export { VolumeType } from './Volume' +export type { + Volume, + CreateVolumeOptions, + VolumeMountTokenDto, + VolumePullResult, + HotmountRegion, + Region, +} from './Volume' export type { Secret, CreateSecretParams, UpdateSecretParams, ListSecretsQuery, ListSecretsResponse } from './Secret' export type { CreateSnapshotParams } from './Snapshot' export { ComputerUse, Mouse, Keyboard, Screenshot, Display, Accessibility } from './ComputerUse'