feat(proxy): proxy app and refactor

- added a centralized proxy app
- proxy supports auth via query param, header or JWT cookie
- added a proxy to the daemon
- runner now proxies everything to daemon instead of directly to container port

Signed-off-by: Toma Puljak <toma.puljak@hotmail.com>
This commit is contained in:
Toma Puljak
2025-06-18 17:09:54 +02:00
parent fc479a9536
commit a708b15a82
81 changed files with 4855 additions and 242 deletions
+16
View File
@@ -16,6 +16,12 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "yarn serve:skip-proxy",
"name": "Debug - Skip Proxy",
"request": "launch",
"type": "node-terminal"
},
{
"name": "Runner",
"type": "go",
@@ -35,6 +41,16 @@
"program": "${workspaceFolder}/apps/daemon/cmd/daemon",
"console": "integratedTerminal",
"output": "${workspaceFolder}/dist/apps/daemon"
},
{
"name": "Proxy",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/apps/proxy/cmd/proxy",
"console": "integratedTerminal",
"envFile": "${workspaceFolder}/apps/proxy/.env",
"output": "${workspaceFolder}/dist/apps/proxy"
}
]
}
+4
View File
@@ -58,3 +58,7 @@ OTEL_ENABLED=true
OTEL_COLLECTOR_URL=http://jaeger:4318/v1/traces
MAINTENANCE_MODE=false
PROXY_DOMAIN=proxy.localhost:4000
PROXY_PROTOCOL=http
PROXY_API_KEY=super_secret_key
+12 -1
View File
@@ -9,6 +9,8 @@ import { Strategy } from 'passport-http-bearer'
import { ApiKeyService } from '../api-key/api-key.service'
import { UserService } from '../user/user.service'
import { AuthContext } from '../common/interfaces/auth-context.interface'
import { TypedConfigService } from '../config/typed-config.service'
import { ProxyContext } from '../common/interfaces/proxy-context.interface'
@Injectable()
export class ApiKeyStrategy extends PassportStrategy(Strategy, 'api-key') implements OnModuleInit {
@@ -17,6 +19,7 @@ export class ApiKeyStrategy extends PassportStrategy(Strategy, 'api-key') implem
constructor(
private readonly apiKeyService: ApiKeyService,
private readonly userService: UserService,
private readonly configService: TypedConfigService,
) {
super()
this.logger.log('ApiKeyStrategy constructor called')
@@ -26,10 +29,18 @@ export class ApiKeyStrategy extends PassportStrategy(Strategy, 'api-key') implem
this.logger.log('ApiKeyStrategy initialized')
}
async validate(token: string): Promise<AuthContext> {
async validate(token: string): Promise<AuthContext | ProxyContext> {
this.logger.debug('Validate method called')
this.logger.debug(`Validating API key: ${token.substring(0, 8)}...`)
const proxyApiKey = this.configService.getOrThrow('proxy.apiKey')
if (proxyApiKey === token) {
return {
proxy: true,
role: 'admin',
}
}
try {
const apiKey = await this.apiKeyService.getApiKeyByValue(token)
this.logger.debug(`API key found for userId: ${apiKey.userId}`)
-1
View File
@@ -16,7 +16,6 @@ import { firstValueFrom } from 'rxjs'
import { UserService } from '../user/user.service'
import { TypedConfigModule } from '../config/typed-config.module'
import { catchError, map } from 'rxjs/operators'
@Module({
imports: [
PassportModule.register({
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2025 Daytona Platforms Inc.
* SPDX-License-Identifier: AGPL-3.0
*/
import { ExecutionContext, UnauthorizedException } from '@nestjs/common'
import { IAuthContext } from '../common/interfaces/auth-context.interface'
export function getAuthContext<T extends IAuthContext>(
context: ExecutionContext,
isFunction: (user: IAuthContext) => user is T,
): T {
const request = context.switchToHttp().getRequest()
if (request.user && isFunction(request.user)) {
return request.user
}
throw new UnauthorizedException('Unauthorized')
}
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2025 Daytona Platforms Inc.
* SPDX-License-Identifier: AGPL-3.0
*/
import { Injectable, ExecutionContext, Logger, CanActivate } from '@nestjs/common'
import { getAuthContext } from './get-auth-context'
import { isProxyContext } from '../common/interfaces/proxy-context.interface'
@Injectable()
export class ProxyGuard implements CanActivate {
protected readonly logger = new Logger(ProxyGuard.name)
async canActivate(context: ExecutionContext): Promise<boolean> {
// Throws if not proxy context
getAuthContext(context, isProxyContext)
return true
}
}
@@ -8,7 +8,11 @@ import { OrganizationUser } from '../../organization/entities/organization-user.
import { Organization } from '../../organization/entities/organization.entity'
import { SystemRole } from '../../user/enums/system-role.enum'
export interface AuthContext {
export interface IAuthContext {
role: string
}
export interface AuthContext extends IAuthContext {
userId: string
email: string
role: SystemRole
@@ -16,8 +20,16 @@ export interface AuthContext {
organizationId?: string
}
export function isAuthContext(user: IAuthContext): user is AuthContext {
return 'userId' in user
}
export interface OrganizationAuthContext extends AuthContext {
organizationId: string
organization: Organization
organizationUser?: OrganizationUser
}
export function isOrganizationAuthContext(user: IAuthContext): user is OrganizationAuthContext {
return 'organizationId' in user
}
@@ -0,0 +1,14 @@
/*
* Copyright 2025 Daytona Platforms Inc.
* SPDX-License-Identifier: AGPL-3.0
*/
import { IAuthContext } from './auth-context.interface'
export interface ProxyContext extends IAuthContext {
proxy: boolean
}
export function isProxyContext(user: IAuthContext): user is ProxyContext {
return 'proxy' in user
}
+5
View File
@@ -70,6 +70,11 @@ const configuration = {
skipConnections: process.env.SKIP_CONNECTIONS === 'true',
maxAutoArchiveInterval: parseInt(process.env.MAX_AUTO_ARCHIVE_INTERVAL || '43200', 10),
maintananceMode: process.env.MAINTENANCE_MODE === 'true',
proxy: {
domain: process.env.PROXY_DOMAIN,
protocol: process.env.PROXY_PROTOCOL,
apiKey: process.env.PROXY_API_KEY,
},
}
export { configuration }
@@ -25,9 +25,7 @@ export class OrganizationResourceActionGuard extends OrganizationAccessGuard {
super(organizationService, organizationUserService)
}
async canActivate(context: ExecutionContext): Promise<boolean> {
if (!(await super.canActivate(context))) {
return false
}
const canActivate = await super.canActivate(context)
const request = context.switchToHttp().getRequest()
// TODO: initialize authContext safely
@@ -37,6 +35,10 @@ export class OrganizationResourceActionGuard extends OrganizationAccessGuard {
return true
}
if (!canActivate) {
return false
}
if (!authContext.organizationUser) {
return false
}
@@ -7,15 +7,17 @@ import { Body, Controller, Get, Post, Param, Patch, UseGuards } from '@nestjs/co
import { CreateRunnerDto } from '../dto/create-runner.dto'
import { Runner } from '../entities/runner.entity'
import { RunnerService } from '../services/runner.service'
import { AuthGuard } from '@nestjs/passport'
import { ApiOAuth2, ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'
import { ApiOAuth2, ApiTags, ApiOperation, ApiBearerAuth, ApiResponse } from '@nestjs/swagger'
import { SystemActionGuard } from '../../auth/system-action.guard'
import { RequiredSystemRole } from '../../common/decorators/required-system-role.decorator'
import { SystemRole } from '../../user/enums/system-role.enum'
import { ProxyGuard } from '../../auth/proxy.guard'
import { RunnerDto } from '../dto/runner.dto'
import { CombinedAuthGuard } from '../../auth/combined-auth.guard'
@ApiTags('runners')
@Controller('runners')
@UseGuards(AuthGuard('jwt'), SystemActionGuard)
@UseGuards(CombinedAuthGuard, SystemActionGuard, ProxyGuard)
@RequiredSystemRole(SystemRole.ADMIN)
@ApiOAuth2(['openid', 'profile', 'email'])
@ApiBearerAuth()
@@ -51,4 +53,19 @@ export class RunnerController {
): Promise<Runner> {
return this.runnerService.updateSchedulingStatus(id, unschedulable)
}
@Get('/by-sandbox/:sandboxId')
@ApiOperation({
summary: 'Get runner by sandbox ID',
operationId: 'getRunnerBySandboxId',
})
@ApiResponse({
status: 200,
description: 'Runner found',
type: RunnerDto,
})
async getRunnerBySandboxId(@Param('sandboxId') sandboxId: string): Promise<RunnerDto> {
const runner = await this.runnerService.findBySandboxId(sandboxId)
return RunnerDto.fromRunner(runner)
}
}
@@ -58,6 +58,7 @@ import { IncomingMessage, ServerResponse } from 'http'
import { NextFunction } from 'http-proxy-middleware/dist/types'
import { LogProxy } from '../proxy/log-proxy'
import { BadRequestError } from '../../exceptions/bad-request.exception'
import { TypedConfigService } from '../../config/typed-config.service'
@ApiTags('sandbox')
@Controller('sandbox')
@@ -72,6 +73,7 @@ export class SandboxController {
@InjectRedis() private readonly redis: Redis,
private readonly runnerService: RunnerService,
private readonly sandboxService: SandboxService,
private readonly configService: TypedConfigService,
) {}
@Get()
@@ -426,6 +428,27 @@ export class SandboxController {
@Param('sandboxId') sandboxId: string,
@Param('port') port: number,
): Promise<PortPreviewUrlDto> {
if (port < 1 || port > 65535) {
throw new BadRequestError('Invalid port')
}
const proxyDomain = this.configService.get('proxy.domain')
const proxyProtocol = this.configService.get('proxy.protocol')
if (proxyDomain && proxyProtocol) {
const sandbox = await this.sandboxService.findOne(sandboxId)
if (!sandbox) {
throw new NotFoundException(`Sandbox with ID ${sandboxId} not found`)
}
// Return new preview url only for updated sandboxes
if (sandbox.daemonVersion) {
return {
url: `${proxyProtocol}://${port}-${sandboxId}.${proxyDomain}`,
token: sandbox.authToken,
}
}
}
return this.sandboxService.getPortPreviewUrl(sandboxId, port)
}
@@ -59,6 +59,8 @@ import { IncomingMessage, ServerResponse } from 'http'
import { NextFunction } from 'http-proxy-middleware/dist/types'
import { LogProxy } from '../proxy/log-proxy'
import { CreateWorkspaceDto } from '../dto/create-workspace.deprecated.dto'
import { TypedConfigService } from '../../config/typed-config.service'
import { BadRequestError } from '../../exceptions/bad-request.exception'
@ApiTags('workspace')
@Controller('workspace')
@@ -73,6 +75,7 @@ export class WorkspaceController {
@InjectRedis() private readonly redis: Redis,
private readonly runnerService: RunnerService,
private readonly workspaceService: WorkspaceService,
private readonly configService: TypedConfigService,
) {}
@Get()
@@ -448,6 +451,27 @@ export class WorkspaceController {
@Param('workspaceId') workspaceId: string,
@Param('port') port: number,
): Promise<PortPreviewUrlDto> {
if (port < 1 || port > 65535) {
throw new BadRequestError('Invalid port')
}
const proxyDomain = this.configService.get('proxy.domain')
const proxyProtocol = this.configService.get('proxy.protocol')
if (proxyDomain && proxyProtocol) {
const workspace = await this.workspaceService.findOne(workspaceId)
if (!workspace) {
throw new NotFoundException(`Workspace with ID ${workspaceId} not found`)
}
// Return new preview url only for updated workspaces/sandboxes
if (workspace.daemonVersion) {
return {
url: `${proxyProtocol}://${port}-${workspaceId}.${proxyDomain}`,
token: workspace.authToken,
}
}
}
return this.workspaceService.getPortPreviewUrl(workspaceId, port)
}
+155
View File
@@ -0,0 +1,155 @@
/*
* Copyright 2025 Daytona Platforms Inc.
* SPDX-License-Identifier: AGPL-3.0
*/
import { ApiProperty, ApiPropertyOptional, ApiSchema } from '@nestjs/swagger'
import { IsEnum, IsOptional } from 'class-validator'
import { Runner } from '../entities/runner.entity'
import { RunnerRegion } from '../enums/runner-region.enum'
import { SandboxClass } from '../enums/sandbox-class.enum'
import { RunnerState } from '../enums/runner-state.enum'
@ApiSchema({ name: 'Runner' })
export class RunnerDto {
@ApiProperty({
description: 'The ID of the runner',
example: 'runner123',
})
id: string
@ApiProperty({
description: 'The domain of the runner',
example: 'runner1.example.com',
})
domain: string
@ApiProperty({
description: 'The API URL of the runner',
example: 'https://api.runner1.example.com',
})
apiUrl: string
@ApiProperty({
description: 'The API key for the runner',
example: 'api-key-123',
})
apiKey: string
@ApiProperty({
description: 'The CPU capacity of the runner',
example: 8,
})
cpu: number
@ApiProperty({
description: 'The memory capacity of the runner in GB',
example: 16,
})
memory: number
@ApiProperty({
description: 'The disk capacity of the runner in GB',
example: 100,
})
disk: number
@ApiProperty({
description: 'The GPU capacity of the runner',
example: 1,
})
gpu: number
@ApiProperty({
description: 'The type of GPU',
})
gpuType: string
@ApiProperty({
description: 'The class of the runner',
enum: SandboxClass,
enumName: 'SandboxClass',
example: SandboxClass.SMALL,
})
@IsEnum(SandboxClass)
class: SandboxClass
@ApiProperty({
description: 'The current usage of the runner',
example: 2,
})
used: number
@ApiProperty({
description: 'The capacity of the runner',
example: 10,
})
capacity: number
@ApiProperty({
description: 'The region of the runner',
enum: RunnerRegion,
enumName: 'RunnerRegion',
example: RunnerRegion.EU,
})
@IsEnum(RunnerRegion)
region: RunnerRegion
@ApiProperty({
description: 'The state of the runner',
enum: RunnerState,
enumName: 'RunnerState',
example: RunnerState.INITIALIZING,
})
@IsEnum(RunnerState)
state: RunnerState
@ApiPropertyOptional({
description: 'The last time the runner was checked',
example: '2024-10-01T12:00:00Z',
required: false,
})
@IsOptional()
lastChecked?: string
@ApiProperty({
description: 'Whether the runner is unschedulable',
example: false,
})
unschedulable: boolean
@ApiProperty({
description: 'The creation timestamp of the runner',
example: '2023-10-01T12:00:00Z',
})
createdAt: string
@ApiProperty({
description: 'The last update timestamp of the runner',
example: '2023-10-01T12:00:00Z',
})
updatedAt: string
static fromRunner(runner: Runner): RunnerDto {
return {
id: runner.id,
domain: runner.domain,
apiUrl: runner.apiUrl,
apiKey: runner.apiKey,
cpu: runner.cpu,
memory: runner.memory,
disk: runner.disk,
gpu: runner.gpu,
gpuType: runner.gpuType,
class: runner.class,
used: runner.used,
capacity: runner.capacity,
region: runner.region,
state: runner.state,
lastChecked: runner.lastChecked?.toISOString(),
unschedulable: runner.unschedulable,
createdAt: runner.createdAt.toISOString(),
updatedAt: runner.updatedAt.toISOString(),
}
}
}
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0
*/
import { Injectable, Logger } from '@nestjs/common'
import { Injectable, Logger, NotFoundException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Cron } from '@nestjs/schedule'
import { FindOptionsWhere, In, Not, Raw, Repository } from 'typeorm'
@@ -67,10 +67,22 @@ export class RunnerService {
return this.runnerRepository.find()
}
findOne(id: string): Promise<Runner | null> {
async findOne(id: string): Promise<Runner | null> {
return this.runnerRepository.findOneBy({ id })
}
async findBySandboxId(sandboxId: string): Promise<Runner | null> {
const sandbox = await this.sandboxRepository.findOneBy({ id: sandboxId })
if (!sandbox) {
throw new NotFoundException(`Sandbox with ID ${sandboxId} not found`)
}
if (!sandbox.runnerId) {
throw new NotFoundException(`Sandbox with ID ${sandboxId} does not have a runner`)
}
return this.runnerRepository.findOneBy({ id: sandbox.runnerId })
}
async findAvailableRunners(params: GetRunnerParams): Promise<Runner[]> {
const runnerFilter: FindOptionsWhere<Runner> = {
state: RunnerState.READY,
+7 -2
View File
@@ -18,7 +18,7 @@ replace github.com/daytonaio/daytona/daytonaapiclient => ../../libs/api-client-g
require (
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/catppuccin/go v0.2.0 // indirect
@@ -27,6 +27,7 @@ require (
github.com/charmbracelet/x/term v0.2.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/creack/pty v1.1.23 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
@@ -41,11 +42,13 @@ require (
github.com/goccy/go-json v0.10.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/minio/crc64nvme v1.0.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
@@ -59,6 +62,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
@@ -70,6 +74,7 @@ require (
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
@@ -85,7 +90,7 @@ require (
github.com/coreos/go-oidc/v3 v3.12.0
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/joho/godotenv v1.5.1
github.com/minio/minio-go/v7 v7.0.90
github.com/minio/minio-go/v7 v7.0.91
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/spf13/pflag v1.0.6 // indirect
golang.org/x/term v0.30.0
+11 -25
View File
@@ -2,8 +2,7 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
@@ -36,8 +35,7 @@ github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -81,10 +79,10 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -101,10 +99,10 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.90 h1:TmSj1083wtAD0kEYTx7a5pFsv3iRYMsOJ6A4crjA1lE=
github.com/minio/minio-go/v7 v7.0.90/go.mod h1:uvMUcGrpgeSAAI6+sD3818508nUyMULw94j2Nxku/Go=
github.com/minio/minio-go/v7 v7.0.91 h1:tWLZnEfo3OZl5PoXQwcwTAPNNrjyWwOh6cbZitW5JQc=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
@@ -125,7 +123,6 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -136,11 +133,11 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
@@ -149,8 +146,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
@@ -179,8 +174,7 @@ go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -188,37 +182,29 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+18 -17
View File
@@ -10,19 +10,19 @@ replace github.com/samber/lo => github.com/samber/lo v1.39.0
require (
github.com/creack/pty v1.1.23
github.com/gin-gonic/gin v1.9.1
github.com/gin-gonic/gin v1.10.1
github.com/gliderlabs/ssh v0.3.7
github.com/go-git/go-git/v5 v5.12.1-0.20240617075238-c127d1b35535
github.com/go-playground/validator/v10 v10.19.0
github.com/go-playground/validator/v10 v10.24.0
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.1
github.com/gorilla/websocket v1.5.3
github.com/kelseyhightower/envconfig v1.4.0
github.com/orcaman/concurrent-map/v2 v2.0.1
github.com/pkg/sftp v1.13.6
github.com/sirupsen/logrus v1.9.3
github.com/sourcegraph/jsonrpc2 v0.2.0
github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.31.0
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.36.0
gopkg.in/ini.v1 v1.67.0
)
@@ -31,24 +31,25 @@ require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/bytedance/sonic v1.11.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/bytedance/sonic v1.12.6 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.1-0.20240427054813-8453aa90c6ec // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -57,22 +58,22 @@ require (
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/arch v0.7.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
golang.org/x/arch v0.12.0 // indirect
google.golang.org/protobuf v1.36.3 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
require (
github.com/emirpasic/gods v1.18.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.28.0
golang.org/x/text v0.21.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0
golang.org/x/text v0.23.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+19 -43
View File
@@ -10,20 +10,13 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
@@ -36,12 +29,10 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -58,10 +49,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -70,8 +59,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -81,8 +69,7 @@ github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -117,8 +104,7 @@ github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Q
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -138,8 +124,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
@@ -147,17 +132,14 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -168,8 +150,7 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -187,16 +168,14 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -204,15 +183,13 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -227,4 +204,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: AGPL-3.0
package proxy
import (
"errors"
"fmt"
"net/url"
"strings"
common_errors "github.com/daytonaio/common-go/pkg/errors"
"github.com/gin-gonic/gin"
)
func GetProxyTarget(ctx *gin.Context) (*url.URL, string, map[string]string, error) {
targetPort := ctx.Param("port")
if targetPort == "" {
ctx.Error(common_errors.NewBadRequestError(errors.New("target port is required")))
return nil, "", nil, errors.New("target port is required")
}
// Build the target URL
targetURL := fmt.Sprintf("http://localhost:%s", targetPort)
target, err := url.Parse(targetURL)
if err != nil {
ctx.Error(common_errors.NewBadRequestError(fmt.Errorf("failed to parse target URL: %w", err)))
return nil, "", nil, fmt.Errorf("failed to parse target URL: %w", err)
}
// Get the wildcard path and normalize it
path := ctx.Param("path")
// Ensure path always has a leading slash but not duplicate slashes
if path == "" {
path = "/"
} else if !strings.HasPrefix(path, "/") {
path = "/" + path
}
// Create the complete target URL with path
fullTargetURL := fmt.Sprintf("%s%s", targetURL, path)
if ctx.Request.URL.RawQuery != "" {
fullTargetURL = fmt.Sprintf("%s?%s", fullTargetURL, ctx.Request.URL.RawQuery)
}
return target, fullTargetURL, nil, nil
}
+7
View File
@@ -11,6 +11,7 @@ import (
"os"
"path"
common_proxy "github.com/daytonaio/common-go/pkg/proxy"
"github.com/daytonaio/daemon/pkg/toolbox/config"
"github.com/daytonaio/daemon/pkg/toolbox/fs"
"github.com/daytonaio/daemon/pkg/toolbox/git"
@@ -19,6 +20,7 @@ import (
"github.com/daytonaio/daemon/pkg/toolbox/port"
"github.com/daytonaio/daemon/pkg/toolbox/process"
"github.com/daytonaio/daemon/pkg/toolbox/process/session"
"github.com/daytonaio/daemon/pkg/toolbox/proxy"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
@@ -146,6 +148,11 @@ func (s *Server) Start() error {
portController.GET("/:port/in-use", portDetector.IsPortInUse)
}
proxyController := r.Group("/proxy")
{
proxyController.Any("/:port/*path", common_proxy.NewProxyRequestHandler(proxy.GetProxyTarget))
}
go portDetector.Start(context.Background())
httpServer := &http.Server{
+2
View File
@@ -13,3 +13,5 @@ VITE_ANNOUNCEMENT_BANNER_TEXT=
VITE_ANNOUNCEMENT_BANNER_LEARN_MORE_URL=
VITE_PYLON_APP_ID=
VITE_PROXY_TEMPLATE_URL=http://{{PORT}}-{{sandboxId}}.proxy.localhost:4000
+10 -6
View File
@@ -584,13 +584,17 @@ const getColumns = ({
id: 'access',
header: 'Access',
cell: ({ row }) => {
if (!row.original.runnerDomain || row.original.state !== SandboxState.STARTED) return ''
if (row.original.state !== SandboxState.STARTED) return ''
const terminalUrl = import.meta.env.VITE_PROXY_TEMPLATE_URL?.replace('{{PORT}}', '22222').replace(
'{{sandboxId}}',
row.original.id,
)
if (!terminalUrl) {
return null
}
return (
<a
href={`https://22222-${row.original.id}.${row.original.runnerDomain}`}
target="_blank"
rel="noopener noreferrer"
>
<a href={terminalUrl} target="_blank" rel="noopener noreferrer">
<Terminal className="w-4 h-4" />
</a>
)
+1
View File
@@ -22,6 +22,7 @@ interface ImportMetaEnv {
readonly VITE_ANNOUNCEMENT_BANNER_TEXT: string | undefined
readonly VITE_ANNOUNCEMENT_BANNER_LEARN_MORE_URL: string | undefined
readonly VITE_PYLON_APP_ID: string | undefined
readonly VITE_PROXY_TEMPLATE_URL: string | undefined
}
interface ImportMeta {
+13
View File
@@ -0,0 +1,13 @@
DAYTONA_API_URL=http://localhost:3001/api
PROXY_PORT=4000
PROXY_DOMAIN=proxy.localhost:4000
PROXY_API_KEY=super_secret_key
OIDC_CLIENT_ID=daytona
OIDC_CLIENT_SECRET=
OIDC_DOMAIN=http://localhost:5556/dex
OIDC_AUDIENCE=daytona
REDIS_HOST=redis
REDIS_PORT=6379
+79
View File
@@ -0,0 +1,79 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: AGPL-3.0
package config
import (
"log"
"github.com/go-playground/validator/v10"
"github.com/joho/godotenv"
"github.com/kelseyhightower/envconfig"
)
type Config struct {
ProxyPort int `envconfig:"PROXY_PORT" validate:"required"`
ProxyDomain string `envconfig:"PROXY_DOMAIN" validate:"required"`
ProxyApiKey string `envconfig:"PROXY_API_KEY" validate:"required"`
TLSCertFile string `envconfig:"TLS_CERT_FILE"`
TLSKeyFile string `envconfig:"TLS_KEY_FILE"`
EnableTLS bool `envconfig:"ENABLE_TLS"`
DaytonaApiUrl string `envconfig:"DAYTONA_API_URL" validate:"required"`
Oidc OidcConfig `envconfig:"OIDC"`
Redis *RedisConfig `envconfig:"REDIS"`
}
type OidcConfig struct {
ClientId string `envconfig:"CLIENT_ID" validate:"required"`
ClientSecret string `envconfig:"CLIENT_SECRET"`
Domain string `envconfig:"DOMAIN" validate:"required"`
Audience string `envconfig:"AUDIENCE" validate:"required"`
}
type RedisConfig struct {
Host *string `envconfig:"HOST"`
Port *int `envconfig:"PORT"`
Password *string `envconfig:"PASSWORD"`
}
var DEFAULT_PROXY_PORT int = 4000
var config *Config
func GetConfig() (*Config, error) {
if config != nil {
return config, nil
}
config = &Config{}
// Load .env files
err := godotenv.Overload(".env", ".env.local", ".env.production")
if err != nil {
log.Println("Warning: Error loading .env file:", err)
// Continue anyway, as environment variables might be set directly
}
err = envconfig.Process("", config)
if err != nil {
return nil, err
}
var validate = validator.New()
err = validate.Struct(config)
if err != nil {
return nil, err
}
if config.ProxyPort == 0 {
config.ProxyPort = DEFAULT_PROXY_PORT
}
if config.Redis != nil {
if config.Redis.Host == nil && config.Redis.Port == nil && config.Redis.Password == nil {
config.Redis = nil
}
}
return config, nil
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: AGPL-3.0
package main
import (
"github.com/daytonaio/proxy/cmd/proxy/config"
"github.com/daytonaio/proxy/pkg/proxy"
log "github.com/sirupsen/logrus"
)
func main() {
config, err := config.GetConfig()
if err != nil {
log.Fatal(err)
}
proxy.StartProxy(config)
}
+49
View File
@@ -0,0 +1,49 @@
module github.com/daytonaio/proxy
go 1.23.0
require github.com/gin-gonic/gin v1.10.1
require (
github.com/bytedance/sonic v1.13.2 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/cors v1.7.5 // indirect
github.com/gin-contrib/sessions v1.0.4 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/redis/go-redis/v9 v9.10.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.16.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+133
View File
@@ -0,0 +1,133 @@
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/cors v1.7.5 h1:cXC9SmofOrRg0w9PigwGlHG3ztswH6bqq4vJVXnvYMk=
github.com/gin-contrib/cors v1.7.5/go.mod h1:4q3yi7xBEDDWKapjT2o1V7mScKDDr8k+jZ0fSquGoy0=
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: AGPL-3.0
package cache
import (
"context"
"time"
)
type ICache[T any] interface {
Get(ctx context.Context, key string) (*T, error)
Set(ctx context.Context, key string, value T, expiration time.Duration) error
Delete(ctx context.Context, key string) error
Has(ctx context.Context, key string) (bool, error)
}
+45
View File
@@ -0,0 +1,45 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: AGPL-3.0
package cache
import (
"context"
"errors"
"time"
cmap "github.com/orcaman/concurrent-map/v2"
)
type MapCache[T any] struct {
cacheMap cmap.ConcurrentMap[string, T]
}
func (c *MapCache[T]) Set(ctx context.Context, key string, value T, expiration time.Duration) error {
c.cacheMap.Set(key, value)
return nil
}
func (c *MapCache[T]) Has(ctx context.Context, key string) (bool, error) {
_, ok := c.cacheMap.Get(key)
return ok, nil
}
func (c *MapCache[T]) Get(ctx context.Context, key string) (*T, error) {
value, ok := c.cacheMap.Get(key)
if !ok {
return nil, errors.New("key not found")
}
return &value, nil
}
func (c *MapCache[T]) Delete(ctx context.Context, key string) error {
c.cacheMap.Remove(key)
return nil
}
func NewMapCache[T any]() *MapCache[T] {
return &MapCache[T]{
cacheMap: cmap.New[T](),
}
}
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: AGPL-3.0
package cache
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/daytonaio/proxy/cmd/proxy/config"
"github.com/redis/go-redis/v9"
)
type RedisCache[T any] struct {
redis *redis.Client
keyPrefix string
}
type ValueObject[T any] struct {
Value T `json:"value"`
}
var client *redis.Client
func (c *RedisCache[T]) Set(ctx context.Context, key string, value T, expiration time.Duration) error {
jsonValue, err := json.Marshal(ValueObject[T]{Value: value})
if err != nil {
return err
}
return c.redis.Set(ctx, c.keyPrefix+key, string(jsonValue), expiration).Err()
}
func (c *RedisCache[T]) Has(ctx context.Context, key string) (bool, error) {
err := c.redis.Get(ctx, c.keyPrefix+key).Err()
if err == nil {
return true, nil
}
if err == redis.Nil {
return false, nil
}
return false, err
}
func (c *RedisCache[T]) Get(ctx context.Context, key string) (*T, error) {
value, err := c.redis.Get(ctx, c.keyPrefix+key).Result()
if err != nil {
return nil, err
}
var result ValueObject[T]
err = json.Unmarshal([]byte(value), &result)
if err != nil {
return nil, err
}
return &result.Value, nil
}
func (c *RedisCache[T]) Delete(ctx context.Context, key string) error {
return c.redis.Del(ctx, c.keyPrefix+key).Err()
}
func NewRedisCache[T any](config *config.RedisConfig, keyPrefix string) (*RedisCache[T], error) {
if config.Host == nil || config.Port == nil {
return nil, errors.New("host and port are required")
}
password := ""
if config.Password != nil {
password = *config.Password
}
if client == nil {
client = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", *config.Host, *config.Port),
Password: password,
})
}
return &RedisCache[T]{
redis: client,
keyPrefix: keyPrefix,
}, nil
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: AGPL-3.0
package proxy
import (
"errors"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func (p *Proxy) Authenticate(ctx *gin.Context, sandboxId string) (err error, didRedirect bool) {
authKey := ctx.Request.Header.Get(DAYTONA_SANDBOX_AUTH_KEY_HEADER)
if authKey == "" {
if ctx.Query(DAYTONA_SANDBOX_AUTH_KEY_QUERY_PARAM) != "" {
authKey = ctx.Query(DAYTONA_SANDBOX_AUTH_KEY_QUERY_PARAM)
newQuery := ctx.Request.URL.Query()
newQuery.Del(DAYTONA_SANDBOX_AUTH_KEY_QUERY_PARAM)
ctx.Request.URL.RawQuery = newQuery.Encode()
} else {
// Check for cookie
cookieSandboxId, err := ctx.Cookie(DAYTONA_SANDBOX_AUTH_COOKIE_NAME + sandboxId)
if err == nil && cookieSandboxId != "" {
decodedValue := ""
err = p.secureCookie.Decode(DAYTONA_SANDBOX_AUTH_COOKIE_NAME+sandboxId, cookieSandboxId, &decodedValue)
if err != nil {
return errors.New("sandbox not found"), false
}
if decodedValue != sandboxId {
return errors.New("sandbox not found"), false
} else {
return nil, false
}
} else {
authUrl, err := p.getAuthUrl(ctx, sandboxId)
if err != nil {
return fmt.Errorf("failed to get auth URL: %w", err), false
}
ctx.Redirect(http.StatusTemporaryRedirect, authUrl)
return errors.New("auth key is required"), true
}
}
}
if authKey != "" {
isValid, err := p.getSandboxAuthKeyValid(ctx, sandboxId, authKey)
if err != nil {
return fmt.Errorf("failed to get sandbox auth key valid status: %w", err), false
}
if !*isValid {
return errors.New("invalid auth key"), false
} else {
return nil, false
}
}
return errors.New("auth key is required. Authenticate via a Header, Query Param or Cookie"), false
}
+184
View File
@@ -0,0 +1,184 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: AGPL-3.0
package proxy
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin"
"golang.org/x/oauth2"
common_errors "github.com/daytonaio/common-go/pkg/errors"
"github.com/daytonaio/daytona/daytonaapiclient"
)
func (p *Proxy) AuthCallback(ctx *gin.Context) {
if ctx.Query("error") != "" {
err := ctx.Query("error")
if ctx.Query("error_description") != "" {
err = ctx.Query("error_description")
}
ctx.Error(common_errors.NewUnauthorizedError(errors.New(err)))
return
}
code := ctx.Query("code")
if code == "" {
ctx.Error(common_errors.NewBadRequestError(errors.New("no code in callback")))
return
}
state := ctx.Query("state")
if state == "" {
ctx.Error(common_errors.NewBadRequestError(errors.New("no state in callback")))
return
}
// Decode state
stateJson, err := base64.URLEncoding.DecodeString(state)
if err != nil {
ctx.Error(common_errors.NewBadRequestError(fmt.Errorf("failed to decode state: %w", err)))
return
}
var stateData map[string]string
err = json.Unmarshal(stateJson, &stateData)
if err != nil {
ctx.Error(common_errors.NewBadRequestError(fmt.Errorf("failed to unmarshal state: %w", err)))
return
}
returnTo := stateData["returnTo"]
if returnTo == "" {
ctx.Error(common_errors.NewBadRequestError(errors.New("no returnTo in state")))
return
}
sandboxId := stateData["sandboxId"]
if sandboxId == "" {
ctx.Error(common_errors.NewBadRequestError(errors.New("no sandboxId in state")))
return
}
// Exchange code for token
provider, err := oidc.NewProvider(ctx, p.config.Oidc.Domain)
if err != nil {
ctx.Error(common_errors.NewBadRequestError(fmt.Errorf("failed to initialize OIDC provider: %w", err)))
return
}
oauth2Config := oauth2.Config{
ClientID: p.config.Oidc.ClientId,
ClientSecret: p.config.Oidc.ClientSecret,
RedirectURL: fmt.Sprintf("http://%s/callback", ctx.Request.Host),
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile"},
}
token, err := oauth2Config.Exchange(ctx, code)
if err != nil {
ctx.Error(common_errors.NewBadRequestError(fmt.Errorf("failed to exchange token: %w", err)))
return
}
hasAccess := p.hasSandboxAccess(ctx, sandboxId, token.AccessToken)
if !hasAccess {
ctx.Error(common_errors.NewUnauthorizedError(errors.New("sandbox not found")))
return
}
// Set the sandboxId in a cookie for cached access
cookieDomain := p.config.ProxyDomain
cookieDomain = strings.Split(cookieDomain, ":")[0]
cookieDomain = fmt.Sprintf(".%s", cookieDomain)
encoded, err := p.secureCookie.Encode(DAYTONA_SANDBOX_AUTH_COOKIE_NAME+sandboxId, sandboxId)
if err != nil {
ctx.Error(common_errors.NewBadRequestError(fmt.Errorf("failed to encode cookie: %w", err)))
return
}
ctx.SetCookie(DAYTONA_SANDBOX_AUTH_COOKIE_NAME+sandboxId, encoded, 3600, "/", cookieDomain, p.config.EnableTLS, true)
// Redirect back to the original URL
ctx.Redirect(http.StatusFound, returnTo)
}
func (p *Proxy) getAuthUrl(ctx *gin.Context, sandboxId string) (string, error) {
provider, err := oidc.NewProvider(ctx, p.config.Oidc.Domain)
if err != nil {
return "", fmt.Errorf("failed to initialize OIDC provider: %w", err)
}
scheme := "http"
if p.config.EnableTLS {
scheme = "https"
}
oauth2Config := oauth2.Config{
ClientID: p.config.Oidc.ClientId,
ClientSecret: p.config.Oidc.ClientSecret,
RedirectURL: fmt.Sprintf("%s://%s/callback", scheme, p.config.ProxyDomain),
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile"},
}
state, err := GenerateRandomState()
if err != nil {
return "", fmt.Errorf("failed to generate random state: %w", err)
}
// Store the original request URL in the state
stateData := map[string]string{
"state": state,
"returnTo": fmt.Sprintf("%s://%s%s", scheme, ctx.Request.Host, ctx.Request.URL.String()),
"sandboxId": sandboxId,
}
stateJson, err := json.Marshal(stateData)
if err != nil {
return "", fmt.Errorf("failed to marshal state: %w", err)
}
encodedState := base64.URLEncoding.EncodeToString(stateJson)
authURL := oauth2Config.AuthCodeURL(
encodedState,
oauth2.SetAuthURLParam("audience", p.config.Oidc.Audience),
)
return authURL, nil
}
func (p *Proxy) hasSandboxAccess(ctx context.Context, sandboxId string, authToken string) bool {
clientConfig := daytonaapiclient.NewConfiguration()
clientConfig.Servers = daytonaapiclient.ServerConfigurations{
{
URL: p.config.DaytonaApiUrl,
},
}
clientConfig.AddDefaultHeader("Authorization", "Bearer "+authToken)
apiClient := daytonaapiclient.NewAPIClient(clientConfig)
res, _ := apiClient.PreviewAPI.HasSandboxAccess(ctx, sandboxId).Execute()
return res != nil && res.StatusCode == http.StatusOK
}
func GenerateRandomState() (string, error) {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
+172
View File
@@ -0,0 +1,172 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: AGPL-3.0
package proxy
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
common_errors "github.com/daytonaio/common-go/pkg/errors"
"github.com/gin-gonic/gin"
)
func (p *Proxy) GetProxyTarget(ctx *gin.Context) (*url.URL, string, map[string]string, error) {
// Extract port and sandbox ID from the host header
// Expected format: 1234-some-id-uuid.proxy.domain
host := ctx.Request.Host
if host == "" {
ctx.Error(common_errors.NewBadRequestError(errors.New("host header is required")))
return nil, "", nil, errors.New("host header is required")
}
// Split the host to extract the port and sandbox ID
parts := strings.Split(host, ".")
if len(parts) == 0 {
ctx.Error(common_errors.NewBadRequestError(errors.New("invalid host format")))
return nil, "", nil, errors.New("invalid host format")
}
// Extract port from the first part (e.g., "1234-some-id-uuid")
hostPrefix := parts[0]
dashIndex := strings.Index(hostPrefix, "-")
if dashIndex == -1 {
ctx.Error(common_errors.NewBadRequestError(errors.New("invalid host format: port and sandbox ID not found")))
return nil, "", nil, errors.New("invalid host format: port and sandbox ID not found")
}
targetPort := hostPrefix[:dashIndex]
sandboxID := hostPrefix[dashIndex+1:]
if targetPort == "" {
ctx.Error(common_errors.NewBadRequestError(errors.New("target port is required")))
return nil, "", nil, errors.New("target port is required")
}
if sandboxID == "" {
ctx.Error(common_errors.NewBadRequestError(errors.New("sandbox ID is required")))
return nil, "", nil, errors.New("sandbox ID is required")
}
isPublic, err := p.getSandboxPublic(ctx, sandboxID)
if err != nil {
ctx.Error(common_errors.NewBadRequestError(fmt.Errorf("failed to get sandbox public status: %w", err)))
return nil, "", nil, fmt.Errorf("failed to get sandbox public status: %w", err)
}
if !*isPublic || targetPort == "22222" {
err, didRedirect := p.Authenticate(ctx, sandboxID)
if err != nil {
if !didRedirect {
ctx.Error(common_errors.NewUnauthorizedError(err))
}
return nil, "", nil, err
}
}
runnerInfo, err := p.getRunnerInfo(ctx, sandboxID)
if err != nil {
ctx.Error(common_errors.NewBadRequestError(fmt.Errorf("failed to get runner info: %w", err)))
return nil, "", nil, fmt.Errorf("failed to get runner info: %w", err)
}
// Build the target URL
targetURL := fmt.Sprintf("%s/sandboxes/%s/toolbox/proxy/%s", runnerInfo.ApiUrl, sandboxID, targetPort)
target, err := url.Parse(targetURL)
if err != nil {
ctx.Error(common_errors.NewBadRequestError(fmt.Errorf("failed to parse target URL: %w", err)))
return nil, "", nil, fmt.Errorf("failed to parse target URL: %w", err)
}
// Get the wildcard path and normalize it
path := ctx.Param("path")
// Ensure path always has a leading slash but not duplicate slashes
if path == "" {
path = "/"
} else if !strings.HasPrefix(path, "/") {
path = "/" + path
}
// Create the complete target URL with path
fullTargetURL := fmt.Sprintf("%s%s", targetURL, path)
if ctx.Request.URL.RawQuery != "" {
fullTargetURL = fmt.Sprintf("%s?%s", fullTargetURL, ctx.Request.URL.RawQuery)
}
return target, fullTargetURL, map[string]string{
"X-Daytona-Authorization": fmt.Sprintf("Bearer %s", runnerInfo.ApiKey),
}, nil
}
func (p *Proxy) getRunnerInfo(ctx context.Context, sandboxId string) (*RunnerInfo, error) {
has, err := p.runnerCache.Has(ctx, sandboxId)
if err != nil {
return nil, err
}
if has {
return p.runnerCache.Get(ctx, sandboxId)
}
runner, _, err := p.daytonaApiClient.RunnersAPI.GetRunnerBySandboxId(context.Background(), sandboxId).Execute()
if err != nil {
return nil, err
}
info := RunnerInfo{
ApiUrl: runner.ApiUrl,
ApiKey: runner.ApiKey,
}
p.runnerCache.Set(ctx, sandboxId, info, 1*time.Hour)
return &info, nil
}
func (p *Proxy) getSandboxPublic(ctx context.Context, sandboxId string) (*bool, error) {
has, err := p.sandboxPublicCache.Has(ctx, sandboxId)
if err != nil {
return nil, err
}
if has {
return p.sandboxPublicCache.Get(ctx, sandboxId)
}
isPublic := false
_, resp, _ := p.daytonaApiClient.PreviewAPI.IsSandboxPublic(context.Background(), sandboxId).Execute()
if resp != nil && resp.StatusCode == http.StatusOK {
isPublic = true
}
p.sandboxPublicCache.Set(ctx, sandboxId, isPublic, 2*time.Minute)
return &isPublic, nil
}
func (p *Proxy) getSandboxAuthKeyValid(ctx context.Context, sandboxId string, authKey string) (*bool, error) {
has, err := p.sandboxAuthKeyValidCache.Has(ctx, authKey)
if err != nil {
return nil, err
}
if has {
return p.sandboxAuthKeyValidCache.Get(ctx, authKey)
}
isValid := false
_, resp, _ := p.daytonaApiClient.PreviewAPI.IsValidAuthToken(context.Background(), sandboxId, authKey).Execute()
if resp != nil && resp.StatusCode == http.StatusOK {
isValid = true
}
p.sandboxAuthKeyValidCache.Set(ctx, authKey, isValid, 2*time.Minute)
return &isValid, nil
}
+128
View File
@@ -0,0 +1,128 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: AGPL-3.0
package proxy
import (
"fmt"
"net"
"net/http"
"github.com/daytonaio/daytona/daytonaapiclient"
"github.com/daytonaio/proxy/cmd/proxy/config"
"github.com/daytonaio/proxy/pkg/cache"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/gorilla/securecookie"
common_errors "github.com/daytonaio/common-go/pkg/errors"
common_proxy "github.com/daytonaio/common-go/pkg/proxy"
log "github.com/sirupsen/logrus"
)
type RunnerInfo struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
}
const DAYTONA_SANDBOX_AUTH_KEY_HEADER = "X-Daytona-Preview-Token"
const DAYTONA_SANDBOX_AUTH_KEY_QUERY_PARAM = "DAYTONA_SANDBOX_AUTH_KEY"
const DAYTONA_SANDBOX_AUTH_COOKIE_NAME = "daytona-sandbox-auth-"
type Proxy struct {
config *config.Config
secureCookie *securecookie.SecureCookie
daytonaApiClient *daytonaapiclient.APIClient
runnerCache cache.ICache[RunnerInfo]
sandboxPublicCache cache.ICache[bool]
sandboxAuthKeyValidCache cache.ICache[bool]
}
func StartProxy(config *config.Config) error {
proxy := &Proxy{
config: config,
}
proxy.secureCookie = securecookie.New([]byte(config.ProxyApiKey), nil)
clientConfig := daytonaapiclient.NewConfiguration()
clientConfig.Servers = daytonaapiclient.ServerConfigurations{
{
URL: config.DaytonaApiUrl,
},
}
clientConfig.AddDefaultHeader("Authorization", "Bearer "+config.ProxyApiKey)
proxy.daytonaApiClient = daytonaapiclient.NewAPIClient(clientConfig)
proxy.daytonaApiClient.GetConfig().HTTPClient = &http.Client{
Transport: http.DefaultTransport,
}
if config.Redis != nil {
var err error
proxy.runnerCache, err = cache.NewRedisCache[RunnerInfo](config.Redis, "proxy:sandbox-runner-info:")
if err != nil {
return err
}
proxy.sandboxPublicCache, err = cache.NewRedisCache[bool](config.Redis, "proxy:sandbox-public:")
if err != nil {
return err
}
proxy.sandboxAuthKeyValidCache, err = cache.NewRedisCache[bool](config.Redis, "proxy:sandbox-auth-key-valid:")
if err != nil {
return err
}
} else {
proxy.runnerCache = cache.NewMapCache[RunnerInfo]()
proxy.sandboxPublicCache = cache.NewMapCache[bool]()
proxy.sandboxAuthKeyValidCache = cache.NewMapCache[bool]()
}
router := gin.New()
router.Use(gin.Recovery())
corsConfig := cors.DefaultConfig()
corsConfig.AllowAllOrigins = true
corsConfig.AllowCredentials = true
router.Use(cors.New(corsConfig))
router.Use(common_errors.NewErrorMiddleware(func(ctx *gin.Context, err error) common_errors.ErrorResponse {
return common_errors.ErrorResponse{
StatusCode: http.StatusInternalServerError,
Message: err.Error(),
}
}))
router.Any("/*path", func(ctx *gin.Context) {
if ctx.Request.Host == config.ProxyDomain && ctx.Request.Method == "GET" && ctx.Request.URL.Path == "/callback" {
proxy.AuthCallback(ctx)
return
}
common_proxy.NewProxyRequestHandler(proxy.GetProxyTarget)(ctx)
})
httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", config.ProxyPort),
Handler: router,
}
listener, err := net.Listen("tcp", httpServer.Addr)
if err != nil {
return err
}
log.Infof("Proxy server is running on port %d", config.ProxyPort)
if config.EnableTLS {
err = httpServer.ServeTLS(listener, config.TLSCertFile, config.TLSKeyFile)
} else {
err = httpServer.Serve(listener)
}
return err
}
+42
View File
@@ -0,0 +1,42 @@
{
"name": "proxy",
"$schema": "../../runner_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "apps/proxy",
"tags": [],
"targets": {
"build": {
"executor": "@nx-go/nx-go:build",
"options": {
"main": "{projectRoot}/cmd/proxy/main.go",
"outputPath": "dist/apps/proxy"
},
"configurations": {
"production": {}
}
},
"serve": {
"executor": "@nx-go/nx-go:serve",
"options": {
"cmd": "gow",
"cwd": ".",
"main": "{projectRoot}/cmd/proxy/main.go"
},
"configurations": {
"production": {}
}
},
"format": {
"executor": "nx:run-commands",
"options": {
"command": "cd {projectRoot} && go fmt ./..."
}
},
"test": {
"executor": "@nx-go/nx-go:test"
},
"lint": {
"executor": "@nx-go/nx-go:lint"
}
}
}
+7 -4
View File
@@ -4,7 +4,7 @@ go 1.23.2
require (
github.com/docker/docker v27.5.1+incompatible
github.com/gin-gonic/gin v1.10.0
github.com/gin-gonic/gin v1.10.1
github.com/go-playground/validator/v10 v10.24.0
github.com/gorilla/websocket v1.5.3
github.com/joho/godotenv v1.5.1
@@ -19,7 +19,7 @@ require (
)
require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
@@ -29,6 +29,8 @@ require (
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/creack/pty v1.1.23 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
@@ -59,7 +61,7 @@ require (
github.com/minio/crc64nvme v1.0.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
@@ -68,6 +70,7 @@ require (
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
@@ -75,7 +78,7 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
+7 -12
View File
@@ -1,5 +1,4 @@
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
@@ -22,11 +21,10 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8=
@@ -45,8 +43,7 @@ github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -125,8 +122,7 @@ github.com/minio/minio-go/v7 v7.0.91 h1:tWLZnEfo3OZl5PoXQwcwTAPNNrjyWwOh6cbZitW5
github.com/minio/minio-go/v7 v7.0.91/go.mod h1:uvMUcGrpgeSAAI6+sD3818508nUyMULw94j2Nxku/Go=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -144,8 +140,8 @@ github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNH
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
@@ -188,8 +184,7 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0 h1:cEPbyTSEHlQR89XVlyo78gqluF8Y3oMeBkXGWzQsfXY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0/go.mod h1:DKdbWcT4GH1D0Y3Sqt/PFXt2naRKDWtU+eE6oLdFNA8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
+2
View File
@@ -6,3 +6,5 @@ package constants
const BEARER_AUTH_HEADER = "Bearer"
const AUTHORIZATION_HEADER = "Authorization"
const DAYTONA_AUTHORIZATION_HEADER = "X-Daytona-Authorization"
@@ -5,21 +5,36 @@ package controllers
import (
"fmt"
"net/url"
"strings"
"github.com/daytonaio/runner/pkg/common"
"github.com/daytonaio/common-go/pkg/errors"
"github.com/daytonaio/common-go/pkg/proxy"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"github.com/gorilla/websocket"
)
func ProxyCommandLogsStream(ctx *gin.Context, fullTargetURL string) {
func ProxyCommandLogsStream(ctx *gin.Context) {
targetURL, fullTargetURL, extraHeaders, err := getProxyTarget(ctx)
if err != nil {
// Error already sent to the context
return
}
if ctx.Query("follow") != "true" {
proxy.NewProxyRequestHandler(func(ctx *gin.Context) (*url.URL, string, map[string]string, error) {
return targetURL, fullTargetURL, extraHeaders, nil
})(ctx)
return
}
fullTargetURL = strings.Replace(fullTargetURL, "http://", "ws://", 1)
ws, _, err := websocket.DefaultDialer.DialContext(ctx, fullTargetURL, nil)
if err != nil {
ctx.Error(common.NewBadRequestError(fmt.Errorf("failed to create outgoing request: %w", err)))
ctx.Error(errors.NewBadRequestError(fmt.Errorf("failed to create outgoing request: %w", err)))
return
}
+10 -107
View File
@@ -6,57 +6,16 @@ package controllers
import (
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"regexp"
"strings"
"time"
proxy "github.com/daytonaio/common-go/pkg/proxy"
"github.com/daytonaio/runner/pkg/common"
"github.com/daytonaio/runner/pkg/runner"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
var proxyTransport = &http.Transport{
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
MaxIdleConnsPerHost: 100,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
}
// Custom HTTP client that follows redirects while maintaining original headers
var proxyClient = &http.Client{
Transport: proxyTransport,
// Create a custom redirect policy
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// Copy headers from original request
if len(via) > 0 {
// Copy the headers from the original request
for key, values := range via[0].Header {
// Skip certain headers that shouldn't be copied
if key != "Authorization" && key != "Cookie" {
for _, value := range values {
req.Header.Add(key, value)
}
}
}
}
// Limit the number of redirects to prevent infinite loops
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
},
}
// ProxyRequest handles proxying requests to a sandbox's container
//
// @Tags toolbox
@@ -72,86 +31,30 @@ var proxyClient = &http.Client{
// @Failure 500 {object} string "Internal server error"
// @Router /sandboxes/{sandboxId}/toolbox/{path} [get]
func ProxyRequest(ctx *gin.Context) {
target, fullTargetURL, err := getProxyTarget(ctx)
if err != nil {
// Error already sent to the context
return
}
fmt.Println(ctx.Param("path"))
if regexp.MustCompile(`^/process/session/.+/command/.+/logs$`).MatchString(ctx.Param("path")) {
if ctx.Query("follow") == "true" {
ProxyCommandLogsStream(ctx, fullTargetURL)
ProxyCommandLogsStream(ctx)
return
}
}
// Create a new outgoing request
outReq, err := http.NewRequestWithContext(
ctx.Request.Context(),
ctx.Request.Method,
fullTargetURL,
ctx.Request.Body,
)
if err != nil {
ctx.Error(common.NewBadRequestError(fmt.Errorf("failed to create outgoing request: %w", err)))
return
}
// Copy headers from original request
for key, values := range ctx.Request.Header {
// Skip the Connection header
if key != "Connection" {
for _, value := range values {
outReq.Header.Add(key, value)
}
}
}
// Set the Host header to the target
outReq.Host = target.Host
outReq.Header.Set("Connection", "keep-alive")
// Execute the request with our custom client that handles redirects
resp, err := proxyClient.Do(outReq)
if err != nil {
ctx.Error(fmt.Errorf("proxy request failed: %w", err))
return
}
defer resp.Body.Close()
// Copy response headers
for key, values := range resp.Header {
for _, value := range values {
ctx.Writer.Header().Add(key, value)
}
}
// Set the status code
ctx.Writer.WriteHeader(resp.StatusCode)
// Copy the response body
if _, err := io.Copy(ctx.Writer, resp.Body); err != nil {
log.Errorf("Error copying response body: %v", err)
// Error already sent to client, just log here
}
proxy.NewProxyRequestHandler(getProxyTarget)(ctx)
}
func getProxyTarget(ctx *gin.Context) (*url.URL, string, error) {
func getProxyTarget(ctx *gin.Context) (*url.URL, string, map[string]string, error) {
runner := runner.GetInstance(nil)
sandboxId := ctx.Param("sandboxId")
if sandboxId == "" {
ctx.Error(common.NewBadRequestError(errors.New("sandbox ID is required")))
return nil, "", errors.New("sandbox ID is required")
return nil, "", nil, errors.New("sandbox ID is required")
}
// Get container details
container, err := runner.Docker.ContainerInspect(ctx.Request.Context(), sandboxId)
if err != nil {
ctx.Error(common.NewNotFoundError(fmt.Errorf("sandbox container not found: %w", err)))
return nil, "", fmt.Errorf("sandbox container not found: %w", err)
return nil, "", nil, fmt.Errorf("sandbox container not found: %w", err)
}
var containerIP string
@@ -161,8 +64,8 @@ func getProxyTarget(ctx *gin.Context) (*url.URL, string, error) {
}
if containerIP == "" {
ctx.Error(errors.New("container has no IP address, it might not be running"))
return nil, "", errors.New("container has no IP address, it might not be running")
ctx.Error(common.NewBadRequestError(errors.New("container has no IP address, it might not be running")))
return nil, "", nil, errors.New("container has no IP address, it might not be running")
}
// Build the target URL
@@ -170,7 +73,7 @@ func getProxyTarget(ctx *gin.Context) (*url.URL, string, error) {
target, err := url.Parse(targetURL)
if err != nil {
ctx.Error(common.NewBadRequestError(fmt.Errorf("failed to parse target URL: %w", err)))
return nil, "", fmt.Errorf("failed to parse target URL: %w", err)
return nil, "", nil, fmt.Errorf("failed to parse target URL: %w", err)
}
// Get the wildcard path and normalize it
@@ -189,5 +92,5 @@ func getProxyTarget(ctx *gin.Context) (*url.URL, string, error) {
fullTargetURL = fmt.Sprintf("%s?%s", fullTargetURL, ctx.Request.URL.RawQuery)
}
return target, fullTargetURL, nil
return target, fullTargetURL, nil, nil
}
+4
View File
@@ -16,6 +16,10 @@ import (
func AuthMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
authHeader := ctx.GetHeader(constants.AUTHORIZATION_HEADER)
if authHeader == "" {
authHeader = ctx.GetHeader(constants.DAYTONA_AUTHORIZATION_HEADER)
}
if authHeader == "" {
ctx.Error(common.NewUnauthorizedError(errors.New("authorization header required")))
ctx.Abort()
+2
View File
@@ -3,6 +3,8 @@ go 1.23.4
use (
./apps/cli
./apps/daemon
./apps/proxy
./apps/runner
./libs/api-client-go
./libs/common-go
)
+43 -12
View File
@@ -37,10 +37,16 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/antonlindstrom/pgstore v0.0.0-20220421113606-e3a6e3fed12a/go.mod h1:Sdr/tmSOLEnncCuXS5TwZRxuk7deH1WXVY8cve3eVBM=
github.com/boj/redistore v1.4.1/go.mod h1:c0Tvw6aMjslog4jHIAcNv6EtJM849YoOAhMY7JBbWpI=
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20240916143655-c0e34fd2f304/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -52,10 +58,11 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -82,6 +89,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -93,6 +102,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -109,37 +119,52 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
github.com/laziness-coders/mongostore v0.0.14/go.mod h1:Rh+yJax2Vxc2QY62clIM/kRnLk+TxivgSLHOXENXPtk=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
github.com/memcachier/mc/v3 v3.0.3/go.mod h1:GzjocBahcXPxt2cmqzknrgqCOmMxiSzhVKPOe90Tpug=
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/wader/gormstore/v2 v2.0.3/go.mod h1:sr3N3a8F1+PBc3fHoKaphFqDXLRJ9Oe6Yow0HxKFbbg=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -193,8 +218,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -207,6 +232,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -230,16 +257,18 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -359,9 +388,10 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -370,6 +400,7 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
@@ -74,7 +74,11 @@ model_range.go
model_registry_push_access_dto.go
model_replace_request.go
model_replace_result.go
model_runner.go
model_runner_region.go
model_runner_state.go
model_sandbox.go
model_sandbox_class.go
model_sandbox_info.go
model_sandbox_labels.go
model_sandbox_state.go
+164
View File
@@ -1696,6 +1696,33 @@ paths:
summary: Update runner scheduling status
tags:
- runners
/runners/by-sandbox/{sandboxId}:
get:
operationId: getRunnerBySandboxId
parameters:
- explode: false
in: path
name: sandboxId
required: true
schema:
type: string
style: simple
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Runner'
description: Runner found
security:
- bearer: []
- oauth2:
- openid
- profile
- email
summary: Get runner by sandbox ID
tags:
- runners
/toolbox/{sandboxId}/toolbox/project-dir:
get:
operationId: getProjectDir
@@ -6023,6 +6050,143 @@ components:
- memory
- region
type: object
SandboxClass:
description: The class of the runner
enum:
- small
- medium
- large
type: string
RunnerRegion:
description: The region of the runner
enum:
- eu
- us
- asia
type: string
RunnerState:
description: The state of the runner
enum:
- initializing
- ready
- disabled
- decommissioned
- unresponsive
type: string
Runner:
example:
memory: 16
apiKey: api-key-123
cpu: 8
used: 2
gpu: 1
capacity: 10
createdAt: 2023-10-01T12:00:00Z
disk: 100
apiUrl: https://api.runner1.example.com
domain: runner1.example.com
id: runner123
state: initializing
region: eu
unschedulable: false
class: small
lastChecked: 2024-10-01T12:00:00Z
gpuType: gpuType
updatedAt: 2023-10-01T12:00:00Z
properties:
id:
description: The ID of the runner
example: runner123
type: string
domain:
description: The domain of the runner
example: runner1.example.com
type: string
apiUrl:
description: The API URL of the runner
example: https://api.runner1.example.com
type: string
apiKey:
description: The API key for the runner
example: api-key-123
type: string
cpu:
description: The CPU capacity of the runner
example: 8
type: number
memory:
description: The memory capacity of the runner in GB
example: 16
type: number
disk:
description: The disk capacity of the runner in GB
example: 100
type: number
gpu:
description: The GPU capacity of the runner
example: 1
type: number
gpuType:
description: The type of GPU
type: string
class:
allOf:
- $ref: '#/components/schemas/SandboxClass'
description: The class of the runner
example: small
used:
description: The current usage of the runner
example: 2
type: number
capacity:
description: The capacity of the runner
example: 10
type: number
region:
allOf:
- $ref: '#/components/schemas/RunnerRegion'
description: The region of the runner
example: eu
state:
allOf:
- $ref: '#/components/schemas/RunnerState'
description: The state of the runner
example: initializing
lastChecked:
description: The last time the runner was checked
example: 2024-10-01T12:00:00Z
type: string
unschedulable:
description: Whether the runner is unschedulable
example: false
type: boolean
createdAt:
description: The creation timestamp of the runner
example: 2023-10-01T12:00:00Z
type: string
updatedAt:
description: The last update timestamp of the runner
example: 2023-10-01T12:00:00Z
type: string
required:
- apiKey
- apiUrl
- capacity
- class
- cpu
- createdAt
- disk
- domain
- gpu
- gpuType
- id
- memory
- region
- state
- unschedulable
- updatedAt
- used
type: object
ProjectDirResponse:
example:
dir: dir
+115
View File
@@ -36,6 +36,19 @@ type RunnersAPI interface {
// CreateRunnerExecute executes the request
CreateRunnerExecute(r RunnersAPICreateRunnerRequest) (*http.Response, error)
/*
GetRunnerBySandboxId Get runner by sandbox ID
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@param sandboxId
@return RunnersAPIGetRunnerBySandboxIdRequest
*/
GetRunnerBySandboxId(ctx context.Context, sandboxId string) RunnersAPIGetRunnerBySandboxIdRequest
// GetRunnerBySandboxIdExecute executes the request
// @return Runner
GetRunnerBySandboxIdExecute(r RunnersAPIGetRunnerBySandboxIdRequest) (*Runner, *http.Response, error)
/*
ListRunners List all runners
@@ -160,6 +173,108 @@ func (a *RunnersAPIService) CreateRunnerExecute(r RunnersAPICreateRunnerRequest)
return localVarHTTPResponse, nil
}
type RunnersAPIGetRunnerBySandboxIdRequest struct {
ctx context.Context
ApiService RunnersAPI
sandboxId string
}
func (r RunnersAPIGetRunnerBySandboxIdRequest) Execute() (*Runner, *http.Response, error) {
return r.ApiService.GetRunnerBySandboxIdExecute(r)
}
/*
GetRunnerBySandboxId Get runner by sandbox ID
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@param sandboxId
@return RunnersAPIGetRunnerBySandboxIdRequest
*/
func (a *RunnersAPIService) GetRunnerBySandboxId(ctx context.Context, sandboxId string) RunnersAPIGetRunnerBySandboxIdRequest {
return RunnersAPIGetRunnerBySandboxIdRequest{
ApiService: a,
ctx: ctx,
sandboxId: sandboxId,
}
}
// Execute executes the request
//
// @return Runner
func (a *RunnersAPIService) GetRunnerBySandboxIdExecute(r RunnersAPIGetRunnerBySandboxIdRequest) (*Runner, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodGet
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *Runner
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "RunnersAPIService.GetRunnerBySandboxId")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/runners/by-sandbox/{sandboxId}"
localVarPath = strings.Replace(localVarPath, "{"+"sandboxId"+"}", url.PathEscape(parameterValueToString(r.sandboxId, "sandboxId")), -1)
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
}
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 RunnersAPIListRunnersRequest struct {
ctx context.Context
ApiService RunnersAPI
+662
View File
@@ -0,0 +1,662 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0
/*
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 daytonaapiclient
import (
"bytes"
"encoding/json"
"fmt"
)
// checks if the Runner type satisfies the MappedNullable interface at compile time
var _ MappedNullable = &Runner{}
// Runner struct for Runner
type Runner struct {
// The ID of the runner
Id string `json:"id"`
// The domain of the runner
Domain string `json:"domain"`
// The API URL of the runner
ApiUrl string `json:"apiUrl"`
// The API key for the runner
ApiKey string `json:"apiKey"`
// The CPU capacity of the runner
Cpu float32 `json:"cpu"`
// The memory capacity of the runner in GB
Memory float32 `json:"memory"`
// The disk capacity of the runner in GB
Disk float32 `json:"disk"`
// The GPU capacity of the runner
Gpu float32 `json:"gpu"`
// The type of GPU
GpuType string `json:"gpuType"`
// The class of the runner
Class SandboxClass `json:"class"`
// The current usage of the runner
Used float32 `json:"used"`
// The capacity of the runner
Capacity float32 `json:"capacity"`
// The region of the runner
Region RunnerRegion `json:"region"`
// The state of the runner
State RunnerState `json:"state"`
// The last time the runner was checked
LastChecked *string `json:"lastChecked,omitempty"`
// Whether the runner is unschedulable
Unschedulable bool `json:"unschedulable"`
// The creation timestamp of the runner
CreatedAt string `json:"createdAt"`
// The last update timestamp of the runner
UpdatedAt string `json:"updatedAt"`
}
type _Runner Runner
// NewRunner instantiates a new Runner 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 NewRunner(id string, domain string, apiUrl string, apiKey string, cpu float32, memory float32, disk float32, gpu float32, gpuType string, class SandboxClass, used float32, capacity float32, region RunnerRegion, state RunnerState, unschedulable bool, createdAt string, updatedAt string) *Runner {
this := Runner{}
this.Id = id
this.Domain = domain
this.ApiUrl = apiUrl
this.ApiKey = apiKey
this.Cpu = cpu
this.Memory = memory
this.Disk = disk
this.Gpu = gpu
this.GpuType = gpuType
this.Class = class
this.Used = used
this.Capacity = capacity
this.Region = region
this.State = state
this.Unschedulable = unschedulable
this.CreatedAt = createdAt
this.UpdatedAt = updatedAt
return &this
}
// NewRunnerWithDefaults instantiates a new Runner 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 NewRunnerWithDefaults() *Runner {
this := Runner{}
return &this
}
// GetId returns the Id field value
func (o *Runner) GetId() string {
if o == nil {
var ret string
return ret
}
return o.Id
}
// GetIdOk returns a tuple with the Id field value
// and a boolean to check if the value has been set.
func (o *Runner) GetIdOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.Id, true
}
// SetId sets field value
func (o *Runner) SetId(v string) {
o.Id = v
}
// GetDomain returns the Domain field value
func (o *Runner) GetDomain() string {
if o == nil {
var ret string
return ret
}
return o.Domain
}
// GetDomainOk returns a tuple with the Domain field value
// and a boolean to check if the value has been set.
func (o *Runner) GetDomainOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.Domain, true
}
// SetDomain sets field value
func (o *Runner) SetDomain(v string) {
o.Domain = v
}
// GetApiUrl returns the ApiUrl field value
func (o *Runner) GetApiUrl() string {
if o == nil {
var ret string
return ret
}
return o.ApiUrl
}
// GetApiUrlOk returns a tuple with the ApiUrl field value
// and a boolean to check if the value has been set.
func (o *Runner) GetApiUrlOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.ApiUrl, true
}
// SetApiUrl sets field value
func (o *Runner) SetApiUrl(v string) {
o.ApiUrl = v
}
// GetApiKey returns the ApiKey field value
func (o *Runner) GetApiKey() string {
if o == nil {
var ret string
return ret
}
return o.ApiKey
}
// GetApiKeyOk returns a tuple with the ApiKey field value
// and a boolean to check if the value has been set.
func (o *Runner) GetApiKeyOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.ApiKey, true
}
// SetApiKey sets field value
func (o *Runner) SetApiKey(v string) {
o.ApiKey = v
}
// GetCpu returns the Cpu field value
func (o *Runner) GetCpu() float32 {
if o == nil {
var ret float32
return ret
}
return o.Cpu
}
// GetCpuOk returns a tuple with the Cpu field value
// and a boolean to check if the value has been set.
func (o *Runner) GetCpuOk() (*float32, bool) {
if o == nil {
return nil, false
}
return &o.Cpu, true
}
// SetCpu sets field value
func (o *Runner) SetCpu(v float32) {
o.Cpu = v
}
// GetMemory returns the Memory field value
func (o *Runner) GetMemory() float32 {
if o == nil {
var ret float32
return ret
}
return o.Memory
}
// GetMemoryOk returns a tuple with the Memory field value
// and a boolean to check if the value has been set.
func (o *Runner) GetMemoryOk() (*float32, bool) {
if o == nil {
return nil, false
}
return &o.Memory, true
}
// SetMemory sets field value
func (o *Runner) SetMemory(v float32) {
o.Memory = v
}
// GetDisk returns the Disk field value
func (o *Runner) GetDisk() float32 {
if o == nil {
var ret float32
return ret
}
return o.Disk
}
// GetDiskOk returns a tuple with the Disk field value
// and a boolean to check if the value has been set.
func (o *Runner) GetDiskOk() (*float32, bool) {
if o == nil {
return nil, false
}
return &o.Disk, true
}
// SetDisk sets field value
func (o *Runner) SetDisk(v float32) {
o.Disk = v
}
// GetGpu returns the Gpu field value
func (o *Runner) GetGpu() float32 {
if o == nil {
var ret float32
return ret
}
return o.Gpu
}
// GetGpuOk returns a tuple with the Gpu field value
// and a boolean to check if the value has been set.
func (o *Runner) GetGpuOk() (*float32, bool) {
if o == nil {
return nil, false
}
return &o.Gpu, true
}
// SetGpu sets field value
func (o *Runner) SetGpu(v float32) {
o.Gpu = v
}
// GetGpuType returns the GpuType field value
func (o *Runner) GetGpuType() string {
if o == nil {
var ret string
return ret
}
return o.GpuType
}
// GetGpuTypeOk returns a tuple with the GpuType field value
// and a boolean to check if the value has been set.
func (o *Runner) GetGpuTypeOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.GpuType, true
}
// SetGpuType sets field value
func (o *Runner) SetGpuType(v string) {
o.GpuType = v
}
// GetClass returns the Class field value
func (o *Runner) GetClass() SandboxClass {
if o == nil {
var ret SandboxClass
return ret
}
return o.Class
}
// GetClassOk returns a tuple with the Class field value
// and a boolean to check if the value has been set.
func (o *Runner) GetClassOk() (*SandboxClass, bool) {
if o == nil {
return nil, false
}
return &o.Class, true
}
// SetClass sets field value
func (o *Runner) SetClass(v SandboxClass) {
o.Class = v
}
// GetUsed returns the Used field value
func (o *Runner) GetUsed() float32 {
if o == nil {
var ret float32
return ret
}
return o.Used
}
// GetUsedOk returns a tuple with the Used field value
// and a boolean to check if the value has been set.
func (o *Runner) GetUsedOk() (*float32, bool) {
if o == nil {
return nil, false
}
return &o.Used, true
}
// SetUsed sets field value
func (o *Runner) SetUsed(v float32) {
o.Used = v
}
// GetCapacity returns the Capacity field value
func (o *Runner) GetCapacity() float32 {
if o == nil {
var ret float32
return ret
}
return o.Capacity
}
// GetCapacityOk returns a tuple with the Capacity field value
// and a boolean to check if the value has been set.
func (o *Runner) GetCapacityOk() (*float32, bool) {
if o == nil {
return nil, false
}
return &o.Capacity, true
}
// SetCapacity sets field value
func (o *Runner) SetCapacity(v float32) {
o.Capacity = v
}
// GetRegion returns the Region field value
func (o *Runner) GetRegion() RunnerRegion {
if o == nil {
var ret RunnerRegion
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 *Runner) GetRegionOk() (*RunnerRegion, bool) {
if o == nil {
return nil, false
}
return &o.Region, true
}
// SetRegion sets field value
func (o *Runner) SetRegion(v RunnerRegion) {
o.Region = v
}
// GetState returns the State field value
func (o *Runner) GetState() RunnerState {
if o == nil {
var ret RunnerState
return ret
}
return o.State
}
// GetStateOk returns a tuple with the State field value
// and a boolean to check if the value has been set.
func (o *Runner) GetStateOk() (*RunnerState, bool) {
if o == nil {
return nil, false
}
return &o.State, true
}
// SetState sets field value
func (o *Runner) SetState(v RunnerState) {
o.State = v
}
// GetLastChecked returns the LastChecked field value if set, zero value otherwise.
func (o *Runner) GetLastChecked() string {
if o == nil || IsNil(o.LastChecked) {
var ret string
return ret
}
return *o.LastChecked
}
// GetLastCheckedOk returns a tuple with the LastChecked field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *Runner) GetLastCheckedOk() (*string, bool) {
if o == nil || IsNil(o.LastChecked) {
return nil, false
}
return o.LastChecked, true
}
// HasLastChecked returns a boolean if a field has been set.
func (o *Runner) HasLastChecked() bool {
if o != nil && !IsNil(o.LastChecked) {
return true
}
return false
}
// SetLastChecked gets a reference to the given string and assigns it to the LastChecked field.
func (o *Runner) SetLastChecked(v string) {
o.LastChecked = &v
}
// GetUnschedulable returns the Unschedulable field value
func (o *Runner) GetUnschedulable() bool {
if o == nil {
var ret bool
return ret
}
return o.Unschedulable
}
// GetUnschedulableOk returns a tuple with the Unschedulable field value
// and a boolean to check if the value has been set.
func (o *Runner) GetUnschedulableOk() (*bool, bool) {
if o == nil {
return nil, false
}
return &o.Unschedulable, true
}
// SetUnschedulable sets field value
func (o *Runner) SetUnschedulable(v bool) {
o.Unschedulable = v
}
// GetCreatedAt returns the CreatedAt field value
func (o *Runner) GetCreatedAt() string {
if o == nil {
var ret string
return ret
}
return o.CreatedAt
}
// GetCreatedAtOk returns a tuple with the CreatedAt field value
// and a boolean to check if the value has been set.
func (o *Runner) GetCreatedAtOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.CreatedAt, true
}
// SetCreatedAt sets field value
func (o *Runner) SetCreatedAt(v string) {
o.CreatedAt = v
}
// GetUpdatedAt returns the UpdatedAt field value
func (o *Runner) GetUpdatedAt() string {
if o == nil {
var ret string
return ret
}
return o.UpdatedAt
}
// GetUpdatedAtOk returns a tuple with the UpdatedAt field value
// and a boolean to check if the value has been set.
func (o *Runner) GetUpdatedAtOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.UpdatedAt, true
}
// SetUpdatedAt sets field value
func (o *Runner) SetUpdatedAt(v string) {
o.UpdatedAt = v
}
func (o Runner) MarshalJSON() ([]byte, error) {
toSerialize, err := o.ToMap()
if err != nil {
return []byte{}, err
}
return json.Marshal(toSerialize)
}
func (o Runner) ToMap() (map[string]interface{}, error) {
toSerialize := map[string]interface{}{}
toSerialize["id"] = o.Id
toSerialize["domain"] = o.Domain
toSerialize["apiUrl"] = o.ApiUrl
toSerialize["apiKey"] = o.ApiKey
toSerialize["cpu"] = o.Cpu
toSerialize["memory"] = o.Memory
toSerialize["disk"] = o.Disk
toSerialize["gpu"] = o.Gpu
toSerialize["gpuType"] = o.GpuType
toSerialize["class"] = o.Class
toSerialize["used"] = o.Used
toSerialize["capacity"] = o.Capacity
toSerialize["region"] = o.Region
toSerialize["state"] = o.State
if !IsNil(o.LastChecked) {
toSerialize["lastChecked"] = o.LastChecked
}
toSerialize["unschedulable"] = o.Unschedulable
toSerialize["createdAt"] = o.CreatedAt
toSerialize["updatedAt"] = o.UpdatedAt
return toSerialize, nil
}
func (o *Runner) 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{
"id",
"domain",
"apiUrl",
"apiKey",
"cpu",
"memory",
"disk",
"gpu",
"gpuType",
"class",
"used",
"capacity",
"region",
"state",
"unschedulable",
"createdAt",
"updatedAt",
}
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)
}
}
varRunner := _Runner{}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
err = decoder.Decode(&varRunner)
if err != nil {
return err
}
*o = Runner(varRunner)
return err
}
type NullableRunner struct {
value *Runner
isSet bool
}
func (v NullableRunner) Get() *Runner {
return v.value
}
func (v *NullableRunner) Set(val *Runner) {
v.value = val
v.isSet = true
}
func (v NullableRunner) IsSet() bool {
return v.isSet
}
func (v *NullableRunner) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableRunner(val *Runner) *NullableRunner {
return &NullableRunner{value: val, isSet: true}
}
func (v NullableRunner) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableRunner) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0
/*
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 daytonaapiclient
import (
"encoding/json"
"fmt"
)
// RunnerRegion The region of the runner
type RunnerRegion string
// List of RunnerRegion
const (
RUNNERREGION_EU RunnerRegion = "eu"
RUNNERREGION_US RunnerRegion = "us"
RUNNERREGION_ASIA RunnerRegion = "asia"
)
// All allowed values of RunnerRegion enum
var AllowedRunnerRegionEnumValues = []RunnerRegion{
"eu",
"us",
"asia",
}
func (v *RunnerRegion) UnmarshalJSON(src []byte) error {
var value string
err := json.Unmarshal(src, &value)
if err != nil {
return err
}
enumTypeValue := RunnerRegion(value)
for _, existing := range AllowedRunnerRegionEnumValues {
if existing == enumTypeValue {
*v = enumTypeValue
return nil
}
}
return fmt.Errorf("%+v is not a valid RunnerRegion", value)
}
// NewRunnerRegionFromValue returns a pointer to a valid RunnerRegion
// for the value passed as argument, or an error if the value passed is not allowed by the enum
func NewRunnerRegionFromValue(v string) (*RunnerRegion, error) {
ev := RunnerRegion(v)
if ev.IsValid() {
return &ev, nil
} else {
return nil, fmt.Errorf("invalid value '%v' for RunnerRegion: valid values are %v", v, AllowedRunnerRegionEnumValues)
}
}
// IsValid return true if the value is valid for the enum, false otherwise
func (v RunnerRegion) IsValid() bool {
for _, existing := range AllowedRunnerRegionEnumValues {
if existing == v {
return true
}
}
return false
}
// Ptr returns reference to RunnerRegion value
func (v RunnerRegion) Ptr() *RunnerRegion {
return &v
}
type NullableRunnerRegion struct {
value *RunnerRegion
isSet bool
}
func (v NullableRunnerRegion) Get() *RunnerRegion {
return v.value
}
func (v *NullableRunnerRegion) Set(val *RunnerRegion) {
v.value = val
v.isSet = true
}
func (v NullableRunnerRegion) IsSet() bool {
return v.isSet
}
func (v *NullableRunnerRegion) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableRunnerRegion(val *RunnerRegion) *NullableRunnerRegion {
return &NullableRunnerRegion{value: val, isSet: true}
}
func (v NullableRunnerRegion) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableRunnerRegion) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}
+120
View File
@@ -0,0 +1,120 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0
/*
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 daytonaapiclient
import (
"encoding/json"
"fmt"
)
// RunnerState The state of the runner
type RunnerState string
// List of RunnerState
const (
RUNNERSTATE_INITIALIZING RunnerState = "initializing"
RUNNERSTATE_READY RunnerState = "ready"
RUNNERSTATE_DISABLED RunnerState = "disabled"
RUNNERSTATE_DECOMMISSIONED RunnerState = "decommissioned"
RUNNERSTATE_UNRESPONSIVE RunnerState = "unresponsive"
)
// All allowed values of RunnerState enum
var AllowedRunnerStateEnumValues = []RunnerState{
"initializing",
"ready",
"disabled",
"decommissioned",
"unresponsive",
}
func (v *RunnerState) UnmarshalJSON(src []byte) error {
var value string
err := json.Unmarshal(src, &value)
if err != nil {
return err
}
enumTypeValue := RunnerState(value)
for _, existing := range AllowedRunnerStateEnumValues {
if existing == enumTypeValue {
*v = enumTypeValue
return nil
}
}
return fmt.Errorf("%+v is not a valid RunnerState", value)
}
// NewRunnerStateFromValue returns a pointer to a valid RunnerState
// for the value passed as argument, or an error if the value passed is not allowed by the enum
func NewRunnerStateFromValue(v string) (*RunnerState, error) {
ev := RunnerState(v)
if ev.IsValid() {
return &ev, nil
} else {
return nil, fmt.Errorf("invalid value '%v' for RunnerState: valid values are %v", v, AllowedRunnerStateEnumValues)
}
}
// IsValid return true if the value is valid for the enum, false otherwise
func (v RunnerState) IsValid() bool {
for _, existing := range AllowedRunnerStateEnumValues {
if existing == v {
return true
}
}
return false
}
// Ptr returns reference to RunnerState value
func (v RunnerState) Ptr() *RunnerState {
return &v
}
type NullableRunnerState struct {
value *RunnerState
isSet bool
}
func (v NullableRunnerState) Get() *RunnerState {
return v.value
}
func (v *NullableRunnerState) Set(val *RunnerState) {
v.value = val
v.isSet = true
}
func (v NullableRunnerState) IsSet() bool {
return v.isSet
}
func (v *NullableRunnerState) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableRunnerState(val *RunnerState) *NullableRunnerState {
return &NullableRunnerState{value: val, isSet: true}
}
func (v NullableRunnerState) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableRunnerState) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0
/*
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 daytonaapiclient
import (
"encoding/json"
"fmt"
)
// SandboxClass The class of the runner
type SandboxClass string
// List of SandboxClass
const (
SANDBOXCLASS_SMALL SandboxClass = "small"
SANDBOXCLASS_MEDIUM SandboxClass = "medium"
SANDBOXCLASS_LARGE SandboxClass = "large"
)
// All allowed values of SandboxClass enum
var AllowedSandboxClassEnumValues = []SandboxClass{
"small",
"medium",
"large",
}
func (v *SandboxClass) UnmarshalJSON(src []byte) error {
var value string
err := json.Unmarshal(src, &value)
if err != nil {
return err
}
enumTypeValue := SandboxClass(value)
for _, existing := range AllowedSandboxClassEnumValues {
if existing == enumTypeValue {
*v = enumTypeValue
return nil
}
}
return fmt.Errorf("%+v is not a valid SandboxClass", value)
}
// NewSandboxClassFromValue returns a pointer to a valid SandboxClass
// for the value passed as argument, or an error if the value passed is not allowed by the enum
func NewSandboxClassFromValue(v string) (*SandboxClass, error) {
ev := SandboxClass(v)
if ev.IsValid() {
return &ev, nil
} else {
return nil, fmt.Errorf("invalid value '%v' for SandboxClass: valid values are %v", v, AllowedSandboxClassEnumValues)
}
}
// IsValid return true if the value is valid for the enum, false otherwise
func (v SandboxClass) IsValid() bool {
for _, existing := range AllowedSandboxClassEnumValues {
if existing == v {
return true
}
}
return false
}
// Ptr returns reference to SandboxClass value
func (v SandboxClass) Ptr() *SandboxClass {
return &v
}
type NullableSandboxClass struct {
value *SandboxClass
isSet bool
}
func (v NullableSandboxClass) Get() *SandboxClass {
return v.value
}
func (v *NullableSandboxClass) Set(val *SandboxClass) {
v.value = val
v.isSet = true
}
func (v NullableSandboxClass) IsSet() bool {
return v.isSet
}
func (v *NullableSandboxClass) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableSandboxClass(val *SandboxClass) *NullableSandboxClass {
return &NullableSandboxClass{value: val, isSet: true}
}
func (v NullableSandboxClass) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableSandboxClass) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}
@@ -76,7 +76,11 @@ daytona_api_client_async/models/range.py
daytona_api_client_async/models/registry_push_access_dto.py
daytona_api_client_async/models/replace_request.py
daytona_api_client_async/models/replace_result.py
daytona_api_client_async/models/runner.py
daytona_api_client_async/models/runner_region.py
daytona_api_client_async/models/runner_state.py
daytona_api_client_async/models/sandbox.py
daytona_api_client_async/models/sandbox_class.py
daytona_api_client_async/models/sandbox_info.py
daytona_api_client_async/models/sandbox_labels.py
daytona_api_client_async/models/sandbox_state.py
@@ -104,7 +104,11 @@ from daytona_api_client_async.models.range import Range
from daytona_api_client_async.models.registry_push_access_dto import RegistryPushAccessDto
from daytona_api_client_async.models.replace_request import ReplaceRequest
from daytona_api_client_async.models.replace_result import ReplaceResult
from daytona_api_client_async.models.runner import Runner
from daytona_api_client_async.models.runner_region import RunnerRegion
from daytona_api_client_async.models.runner_state import RunnerState
from daytona_api_client_async.models.sandbox import Sandbox
from daytona_api_client_async.models.sandbox_class import SandboxClass
from daytona_api_client_async.models.sandbox_info import SandboxInfo
from daytona_api_client_async.models.sandbox_labels import SandboxLabels
from daytona_api_client_async.models.sandbox_state import SandboxState
@@ -22,6 +22,7 @@ from typing_extensions import Annotated
from pydantic import StrictStr
from daytona_api_client_async.models.create_runner import CreateRunner
from daytona_api_client_async.models.runner import Runner
from daytona_api_client_async.api_client import ApiClient, RequestSerialized
from daytona_api_client_async.api_response import ApiResponse
@@ -306,6 +307,265 @@ class RunnersApi:
@validate_call
async def get_runner_by_sandbox_id(
self,
sandbox_id: StrictStr,
_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,
) -> Runner:
"""Get runner by sandbox ID
:param sandbox_id: (required)
:type sandbox_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_runner_by_sandbox_id_serialize(
sandbox_id=sandbox_id,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "Runner",
}
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_runner_by_sandbox_id_with_http_info(
self,
sandbox_id: StrictStr,
_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[Runner]:
"""Get runner by sandbox ID
:param sandbox_id: (required)
:type sandbox_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_runner_by_sandbox_id_serialize(
sandbox_id=sandbox_id,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "Runner",
}
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_runner_by_sandbox_id_without_preload_content(
self,
sandbox_id: StrictStr,
_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 runner by sandbox ID
:param sandbox_id: (required)
:type sandbox_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_runner_by_sandbox_id_serialize(
sandbox_id=sandbox_id,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "Runner",
}
response_data = await self.api_client.call_api(
*_param,
_request_timeout=_request_timeout
)
return response_data.response
def _get_runner_by_sandbox_id_serialize(
self,
sandbox_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 sandbox_id is not None:
_path_params['sandboxId'] = sandbox_id
# process the query parameters
# process the header parameters
# 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='/runners/by-sandbox/{sandboxId}',
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_runners(
self,
@@ -76,7 +76,11 @@ from daytona_api_client_async.models.range import Range
from daytona_api_client_async.models.registry_push_access_dto import RegistryPushAccessDto
from daytona_api_client_async.models.replace_request import ReplaceRequest
from daytona_api_client_async.models.replace_result import ReplaceResult
from daytona_api_client_async.models.runner import Runner
from daytona_api_client_async.models.runner_region import RunnerRegion
from daytona_api_client_async.models.runner_state import RunnerState
from daytona_api_client_async.models.sandbox import Sandbox
from daytona_api_client_async.models.sandbox_class import SandboxClass
from daytona_api_client_async.models.sandbox_info import SandboxInfo
from daytona_api_client_async.models.sandbox_labels import SandboxLabels
from daytona_api_client_async.models.sandbox_state import SandboxState
@@ -0,0 +1,152 @@
# coding: utf-8
# Copyright 2025 Daytona Platforms Inc.
# SPDX-License-Identifier: Apache-2.0
"""
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, StrictBool, StrictFloat, StrictInt, StrictStr
from typing import Any, ClassVar, Dict, List, Optional, Union
from daytona_api_client_async.models.runner_region import RunnerRegion
from daytona_api_client_async.models.runner_state import RunnerState
from daytona_api_client_async.models.sandbox_class import SandboxClass
from typing import Optional, Set
from typing_extensions import Self
class Runner(BaseModel):
"""
Runner
""" # noqa: E501
id: StrictStr = Field(description="The ID of the runner")
domain: StrictStr = Field(description="The domain of the runner")
api_url: StrictStr = Field(
description="The API URL of the runner", alias="apiUrl")
api_key: StrictStr = Field(
description="The API key for the runner", alias="apiKey")
cpu: Union[StrictFloat, StrictInt] = Field(
description="The CPU capacity of the runner")
memory: Union[StrictFloat, StrictInt] = Field(
description="The memory capacity of the runner in GB")
disk: Union[StrictFloat, StrictInt] = Field(
description="The disk capacity of the runner in GB")
gpu: Union[StrictFloat, StrictInt] = Field(
description="The GPU capacity of the runner")
gpu_type: StrictStr = Field(description="The type of GPU", alias="gpuType")
var_class: SandboxClass = Field(
description="The class of the runner", alias="class")
used: Union[StrictFloat, StrictInt] = Field(
description="The current usage of the runner")
capacity: Union[StrictFloat, StrictInt] = Field(
description="The capacity of the runner")
region: RunnerRegion = Field(description="The region of the runner")
state: RunnerState = Field(description="The state of the runner")
last_checked: Optional[StrictStr] = Field(
default=None, description="The last time the runner was checked", alias="lastChecked")
unschedulable: StrictBool = Field(
description="Whether the runner is unschedulable")
created_at: StrictStr = Field(
description="The creation timestamp of the runner", alias="createdAt")
updated_at: StrictStr = Field(
description="The last update timestamp of the runner", alias="updatedAt")
additional_properties: Dict[str, Any] = {}
__properties: ClassVar[List[str]] = ["id", "domain", "apiUrl", "apiKey", "cpu", "memory", "disk", "gpu", "gpuType",
"class", "used", "capacity", "region", "state", "lastChecked", "unschedulable", "createdAt", "updatedAt"]
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"""
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
return json.dumps(self.to_dict())
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
"""Create an instance of Runner 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 Runner from a dict"""
if obj is None:
return None
if not isinstance(obj, dict):
return cls.model_validate(obj)
_obj = cls.model_validate({
"id": obj.get("id"),
"domain": obj.get("domain"),
"apiUrl": obj.get("apiUrl"),
"apiKey": obj.get("apiKey"),
"cpu": obj.get("cpu"),
"memory": obj.get("memory"),
"disk": obj.get("disk"),
"gpu": obj.get("gpu"),
"gpuType": obj.get("gpuType"),
"class": obj.get("class"),
"used": obj.get("used"),
"capacity": obj.get("capacity"),
"region": obj.get("region"),
"state": obj.get("state"),
"lastChecked": obj.get("lastChecked"),
"unschedulable": obj.get("unschedulable"),
"createdAt": obj.get("createdAt"),
"updatedAt": obj.get("updatedAt")
})
# 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
@@ -0,0 +1,40 @@
# coding: utf-8
# Copyright 2025 Daytona Platforms Inc.
# SPDX-License-Identifier: Apache-2.0
"""
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 RunnerRegion(str, Enum):
"""
The region of the runner
"""
"""
allowed enum values
"""
EU = 'eu'
US = 'us'
ASIA = 'asia'
@classmethod
def from_json(cls, json_str: str) -> Self:
"""Create an instance of RunnerRegion from a JSON string"""
return cls(json.loads(json_str))
@@ -0,0 +1,41 @@
# coding: utf-8
# Copyright 2025 Daytona Platforms Inc.
# SPDX-License-Identifier: Apache-2.0
"""
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 RunnerState(str, Enum):
"""
The state of the runner
"""
"""
allowed enum values
"""
INITIALIZING = 'initializing'
READY = 'ready'
DISABLED = 'disabled'
DECOMMISSIONED = 'decommissioned'
UNRESPONSIVE = 'unresponsive'
@classmethod
def from_json(cls, json_str: str) -> Self:
"""Create an instance of RunnerState from a JSON string"""
return cls(json.loads(json_str))
@@ -0,0 +1,39 @@
# coding: utf-8
# Copyright 2025 Daytona Platforms Inc.
# SPDX-License-Identifier: Apache-2.0
"""
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 SandboxClass(str, Enum):
"""
The class of the runner
"""
"""
allowed enum values
"""
SMALL = 'small'
MEDIUM = 'medium'
LARGE = 'large'
@classmethod
def from_json(cls, json_str: str) -> Self:
"""Create an instance of SandboxClass from a JSON string"""
return cls(json.loads(json_str))
@@ -76,7 +76,11 @@ daytona_api_client/models/range.py
daytona_api_client/models/registry_push_access_dto.py
daytona_api_client/models/replace_request.py
daytona_api_client/models/replace_result.py
daytona_api_client/models/runner.py
daytona_api_client/models/runner_region.py
daytona_api_client/models/runner_state.py
daytona_api_client/models/sandbox.py
daytona_api_client/models/sandbox_class.py
daytona_api_client/models/sandbox_info.py
daytona_api_client/models/sandbox_labels.py
daytona_api_client/models/sandbox_state.py
@@ -104,7 +104,11 @@ from daytona_api_client.models.range import Range
from daytona_api_client.models.registry_push_access_dto import RegistryPushAccessDto
from daytona_api_client.models.replace_request import ReplaceRequest
from daytona_api_client.models.replace_result import ReplaceResult
from daytona_api_client.models.runner import Runner
from daytona_api_client.models.runner_region import RunnerRegion
from daytona_api_client.models.runner_state import RunnerState
from daytona_api_client.models.sandbox import Sandbox
from daytona_api_client.models.sandbox_class import SandboxClass
from daytona_api_client.models.sandbox_info import SandboxInfo
from daytona_api_client.models.sandbox_labels import SandboxLabels
from daytona_api_client.models.sandbox_state import SandboxState
@@ -22,6 +22,7 @@ from typing_extensions import Annotated
from pydantic import StrictStr
from daytona_api_client.models.create_runner import CreateRunner
from daytona_api_client.models.runner import Runner
from daytona_api_client.api_client import ApiClient, RequestSerialized
from daytona_api_client.api_response import ApiResponse
@@ -266,6 +267,228 @@ class RunnersApi:
_request_auth=_request_auth,
)
@validate_call
def get_runner_by_sandbox_id(
self,
sandbox_id: StrictStr,
_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,
) -> Runner:
"""Get runner by sandbox ID
:param sandbox_id: (required)
:type sandbox_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_runner_by_sandbox_id_serialize(
sandbox_id=sandbox_id,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index,
)
_response_types_map: Dict[str, Optional[str]] = {
"200": "Runner",
}
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_runner_by_sandbox_id_with_http_info(
self,
sandbox_id: StrictStr,
_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[Runner]:
"""Get runner by sandbox ID
:param sandbox_id: (required)
:type sandbox_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_runner_by_sandbox_id_serialize(
sandbox_id=sandbox_id,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index,
)
_response_types_map: Dict[str, Optional[str]] = {
"200": "Runner",
}
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_runner_by_sandbox_id_without_preload_content(
self,
sandbox_id: StrictStr,
_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 runner by sandbox ID
:param sandbox_id: (required)
:type sandbox_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_runner_by_sandbox_id_serialize(
sandbox_id=sandbox_id,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index,
)
_response_types_map: Dict[str, Optional[str]] = {
"200": "Runner",
}
response_data = self.api_client.call_api(*_param, _request_timeout=_request_timeout)
return response_data.response
def _get_runner_by_sandbox_id_serialize(
self,
sandbox_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 sandbox_id is not None:
_path_params["sandboxId"] = sandbox_id
# process the query parameters
# process the header parameters
# 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="/runners/by-sandbox/{sandboxId}",
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_runners(
self,
@@ -76,7 +76,11 @@ from daytona_api_client.models.range import Range
from daytona_api_client.models.registry_push_access_dto import RegistryPushAccessDto
from daytona_api_client.models.replace_request import ReplaceRequest
from daytona_api_client.models.replace_result import ReplaceResult
from daytona_api_client.models.runner import Runner
from daytona_api_client.models.runner_region import RunnerRegion
from daytona_api_client.models.runner_state import RunnerState
from daytona_api_client.models.sandbox import Sandbox
from daytona_api_client.models.sandbox_class import SandboxClass
from daytona_api_client.models.sandbox_info import SandboxInfo
from daytona_api_client.models.sandbox_labels import SandboxLabels
from daytona_api_client.models.sandbox_state import SandboxState
@@ -0,0 +1,164 @@
# coding: utf-8
# Copyright 2025 Daytona Platforms Inc.
# SPDX-License-Identifier: Apache-2.0
"""
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, StrictBool, StrictFloat, StrictInt, StrictStr
from typing import Any, ClassVar, Dict, List, Optional, Union
from daytona_api_client.models.runner_region import RunnerRegion
from daytona_api_client.models.runner_state import RunnerState
from daytona_api_client.models.sandbox_class import SandboxClass
from typing import Optional, Set
from typing_extensions import Self
class Runner(BaseModel):
"""
Runner
""" # noqa: E501
id: StrictStr = Field(description="The ID of the runner")
domain: StrictStr = Field(description="The domain of the runner")
api_url: StrictStr = Field(description="The API URL of the runner", alias="apiUrl")
api_key: StrictStr = Field(description="The API key for the runner", alias="apiKey")
cpu: Union[StrictFloat, StrictInt] = Field(description="The CPU capacity of the runner")
memory: Union[StrictFloat, StrictInt] = Field(description="The memory capacity of the runner in GB")
disk: Union[StrictFloat, StrictInt] = Field(description="The disk capacity of the runner in GB")
gpu: Union[StrictFloat, StrictInt] = Field(description="The GPU capacity of the runner")
gpu_type: StrictStr = Field(description="The type of GPU", alias="gpuType")
var_class: SandboxClass = Field(description="The class of the runner", alias="class")
used: Union[StrictFloat, StrictInt] = Field(description="The current usage of the runner")
capacity: Union[StrictFloat, StrictInt] = Field(description="The capacity of the runner")
region: RunnerRegion = Field(description="The region of the runner")
state: RunnerState = Field(description="The state of the runner")
last_checked: Optional[StrictStr] = Field(
default=None, description="The last time the runner was checked", alias="lastChecked"
)
unschedulable: StrictBool = Field(description="Whether the runner is unschedulable")
created_at: StrictStr = Field(description="The creation timestamp of the runner", alias="createdAt")
updated_at: StrictStr = Field(description="The last update timestamp of the runner", alias="updatedAt")
additional_properties: Dict[str, Any] = {}
__properties: ClassVar[List[str]] = [
"id",
"domain",
"apiUrl",
"apiKey",
"cpu",
"memory",
"disk",
"gpu",
"gpuType",
"class",
"used",
"capacity",
"region",
"state",
"lastChecked",
"unschedulable",
"createdAt",
"updatedAt",
]
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"""
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
return json.dumps(self.to_dict())
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
"""Create an instance of Runner 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 Runner from a dict"""
if obj is None:
return None
if not isinstance(obj, dict):
return cls.model_validate(obj)
_obj = cls.model_validate(
{
"id": obj.get("id"),
"domain": obj.get("domain"),
"apiUrl": obj.get("apiUrl"),
"apiKey": obj.get("apiKey"),
"cpu": obj.get("cpu"),
"memory": obj.get("memory"),
"disk": obj.get("disk"),
"gpu": obj.get("gpu"),
"gpuType": obj.get("gpuType"),
"class": obj.get("class"),
"used": obj.get("used"),
"capacity": obj.get("capacity"),
"region": obj.get("region"),
"state": obj.get("state"),
"lastChecked": obj.get("lastChecked"),
"unschedulable": obj.get("unschedulable"),
"createdAt": obj.get("createdAt"),
"updatedAt": obj.get("updatedAt"),
}
)
# 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
@@ -0,0 +1,39 @@
# Copyright 2025 Daytona Platforms Inc.
# SPDX-License-Identifier: Apache-2.0
# 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 RunnerRegion(str, Enum):
"""
The region of the runner
"""
"""
allowed enum values
"""
EU = "eu"
US = "us"
ASIA = "asia"
@classmethod
def from_json(cls, json_str: str) -> Self:
"""Create an instance of RunnerRegion from a JSON string"""
return cls(json.loads(json_str))
@@ -0,0 +1,41 @@
# coding: utf-8
# Copyright 2025 Daytona Platforms Inc.
# SPDX-License-Identifier: Apache-2.0
"""
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 RunnerState(str, Enum):
"""
The state of the runner
"""
"""
allowed enum values
"""
INITIALIZING = "initializing"
READY = "ready"
DISABLED = "disabled"
DECOMMISSIONED = "decommissioned"
UNRESPONSIVE = "unresponsive"
@classmethod
def from_json(cls, json_str: str) -> Self:
"""Create an instance of RunnerState from a JSON string"""
return cls(json.loads(json_str))
@@ -0,0 +1,39 @@
# coding: utf-8
# Copyright 2025 Daytona Platforms Inc.
# SPDX-License-Identifier: Apache-2.0
"""
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 SandboxClass(str, Enum):
"""
The class of the runner
"""
"""
allowed enum values
"""
SMALL = "small"
MEDIUM = "medium"
LARGE = "large"
@classmethod
def from_json(cls, json_str: str) -> Self:
"""Create an instance of SandboxClass from a JSON string"""
return cls(json.loads(json_str))
@@ -77,6 +77,10 @@ models/range.ts
models/registry-push-access-dto.ts
models/replace-request.ts
models/replace-result.ts
models/runner-region.ts
models/runner-state.ts
models/runner.ts
models/sandbox-class.ts
models/sandbox-info.ts
models/sandbox-labels.ts
models/sandbox-state.ts
+89
View File
@@ -38,6 +38,8 @@ import {
import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base'
// @ts-ignore
import type { CreateRunner } from '../models'
// @ts-ignore
import type { Runner } from '../models'
/**
* RunnersApi - axios parameter creator
* @export
@@ -84,6 +86,46 @@ export const RunnersApiAxiosParamCreator = function (configuration?: Configurati
options: localVarRequestOptions,
}
},
/**
*
* @summary Get runner by sandbox ID
* @param {string} sandboxId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getRunnerBySandboxId: async (sandboxId: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'sandboxId' is not null or undefined
assertParamExists('getRunnerBySandboxId', 'sandboxId', sandboxId)
const localVarPath = `/runners/by-sandbox/{sandboxId}`.replace(
`{${'sandboxId'}}`,
encodeURIComponent(String(sandboxId)),
)
// 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
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 runners
@@ -188,6 +230,29 @@ export const RunnersApiFp = function (configuration?: Configuration) {
configuration,
)(axios, localVarOperationServerBasePath || basePath)
},
/**
*
* @summary Get runner by sandbox ID
* @param {string} sandboxId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getRunnerBySandboxId(
sandboxId: string,
options?: RawAxiosRequestConfig,
): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Runner>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getRunnerBySandboxId(sandboxId, options)
const localVarOperationServerIndex = configuration?.serverIndex ?? 0
const localVarOperationServerBasePath =
operationServerMap['RunnersApi.getRunnerBySandboxId']?.[localVarOperationServerIndex]?.url
return (axios, basePath) =>
createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration,
)(axios, localVarOperationServerBasePath || basePath)
},
/**
*
* @summary List all runners
@@ -252,6 +317,16 @@ export const RunnersApiFactory = function (configuration?: Configuration, basePa
createRunner(createRunner: CreateRunner, options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.createRunner(createRunner, options).then((request) => request(axios, basePath))
},
/**
*
* @summary Get runner by sandbox ID
* @param {string} sandboxId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getRunnerBySandboxId(sandboxId: string, options?: RawAxiosRequestConfig): AxiosPromise<Runner> {
return localVarFp.getRunnerBySandboxId(sandboxId, options).then((request) => request(axios, basePath))
},
/**
*
* @summary List all runners
@@ -295,6 +370,20 @@ export class RunnersApi extends BaseAPI {
.then((request) => request(this.axios, this.basePath))
}
/**
*
* @summary Get runner by sandbox ID
* @param {string} sandboxId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof RunnersApi
*/
public getRunnerBySandboxId(sandboxId: string, options?: RawAxiosRequestConfig) {
return RunnersApiFp(this.configuration)
.getRunnerBySandboxId(sandboxId, options)
.then((request) => request(this.axios, this.basePath))
}
/**
*
* @summary List all runners
+4
View File
@@ -61,7 +61,11 @@ export * from './range'
export * from './registry-push-access-dto'
export * from './replace-request'
export * from './replace-result'
export * from './runner'
export * from './runner-region'
export * from './runner-state'
export * from './sandbox'
export * from './sandbox-class'
export * from './sandbox-info'
export * from './sandbox-labels'
export * from './sandbox-state'
@@ -0,0 +1,32 @@
/*
* Copyright 2025 Daytona Platforms Inc.
* SPDX-License-Identifier: Apache-2.0
*/
/* tslint: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 region of the runner
* @export
* @enum {string}
*/
export const RunnerRegion = {
EU: 'eu',
US: 'us',
ASIA: 'asia',
} as const
export type RunnerRegion = (typeof RunnerRegion)[keyof typeof RunnerRegion]
@@ -0,0 +1,34 @@
/*
* Copyright 2025 Daytona Platforms Inc.
* SPDX-License-Identifier: Apache-2.0
*/
/* tslint: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 state of the runner
* @export
* @enum {string}
*/
export const RunnerState = {
INITIALIZING: 'initializing',
READY: 'ready',
DISABLED: 'disabled',
DECOMMISSIONED: 'decommissioned',
UNRESPONSIVE: 'unresponsive',
} as const
export type RunnerState = (typeof RunnerState)[keyof typeof RunnerState]
+144
View File
@@ -0,0 +1,144 @@
/*
* Copyright 2025 Daytona Platforms Inc.
* SPDX-License-Identifier: Apache-2.0
*/
/* 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.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { RunnerRegion } from './runner-region'
// May contain unused imports in some cases
// @ts-ignore
import type { RunnerState } from './runner-state'
// May contain unused imports in some cases
// @ts-ignore
import type { SandboxClass } from './sandbox-class'
/**
*
* @export
* @interface Runner
*/
export interface Runner {
/**
* The ID of the runner
* @type {string}
* @memberof Runner
*/
id: string
/**
* The domain of the runner
* @type {string}
* @memberof Runner
*/
domain: string
/**
* The API URL of the runner
* @type {string}
* @memberof Runner
*/
apiUrl: string
/**
* The API key for the runner
* @type {string}
* @memberof Runner
*/
apiKey: string
/**
* The CPU capacity of the runner
* @type {number}
* @memberof Runner
*/
cpu: number
/**
* The memory capacity of the runner in GB
* @type {number}
* @memberof Runner
*/
memory: number
/**
* The disk capacity of the runner in GB
* @type {number}
* @memberof Runner
*/
disk: number
/**
* The GPU capacity of the runner
* @type {number}
* @memberof Runner
*/
gpu: number
/**
* The type of GPU
* @type {string}
* @memberof Runner
*/
gpuType: string
/**
* The class of the runner
* @type {SandboxClass}
* @memberof Runner
*/
class: SandboxClass
/**
* The current usage of the runner
* @type {number}
* @memberof Runner
*/
used: number
/**
* The capacity of the runner
* @type {number}
* @memberof Runner
*/
capacity: number
/**
* The region of the runner
* @type {RunnerRegion}
* @memberof Runner
*/
region: RunnerRegion
/**
* The state of the runner
* @type {RunnerState}
* @memberof Runner
*/
state: RunnerState
/**
* The last time the runner was checked
* @type {string}
* @memberof Runner
*/
lastChecked?: string
/**
* Whether the runner is unschedulable
* @type {boolean}
* @memberof Runner
*/
unschedulable: boolean
/**
* The creation timestamp of the runner
* @type {string}
* @memberof Runner
*/
createdAt: string
/**
* The last update timestamp of the runner
* @type {string}
* @memberof Runner
*/
updatedAt: string
}
@@ -0,0 +1,32 @@
/*
* Copyright 2025 Daytona Platforms Inc.
* SPDX-License-Identifier: Apache-2.0
*/
/* tslint: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 class of the runner
* @export
* @enum {string}
*/
export const SandboxClass = {
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large',
} as const
export type SandboxClass = (typeof SandboxClass)[keyof typeof SandboxClass]
+45
View File
@@ -0,0 +1,45 @@
module github.com/daytonaio/common-go
go 1.23.0
require (
github.com/gin-gonic/gin v1.10.1
github.com/gorilla/websocket v1.5.3
github.com/sirupsen/logrus v1.9.3
)
require (
github.com/bytedance/sonic v1.12.6 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.36.3 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+103
View File
@@ -0,0 +1,103 @@
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+131
View File
@@ -0,0 +1,131 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0
package errors
import (
"fmt"
"strings"
"time"
)
// ErrorResponse represents the error response structure
//
// @Description Error response
// @Schema ErrorResponse
type ErrorResponse struct {
StatusCode int `json:"statusCode" example:"400" binding:"required"`
Message string `json:"message" example:"Bad request" binding:"required"`
Code string `json:"code" example:"BAD_REQUEST" binding:"required"`
Timestamp time.Time `json:"timestamp" example:"2023-01-01T12:00:00Z" binding:"required"`
Path string `json:"path" example:"/api/resource" binding:"required"`
Method string `json:"method" example:"GET" binding:"required"`
} // @name ErrorResponse
type CustomError struct {
StatusCode int
Message string
Code string
}
func (e *CustomError) Error() string {
return e.Message
}
func NewCustomError(statusCode int, message, code string) error {
return &CustomError{
StatusCode: statusCode,
Message: message,
Code: code,
}
}
type NotFoundError struct {
Message string
}
func NewNotFoundError(err error) error {
return &NotFoundError{
Message: fmt.Sprintf("not found: %s", err.Error()),
}
}
func (e *NotFoundError) Error() string {
return e.Message
}
func IsNotFoundError(err error) bool {
return err != nil && strings.Contains(err.Error(), "not found")
}
type InvalidBodyRequestError struct {
Message string
}
func (e *InvalidBodyRequestError) Error() string {
return e.Message
}
func NewInvalidBodyRequestError(err error) error {
return &InvalidBodyRequestError{
Message: fmt.Sprintf("invalid body request: %s", err.Error()),
}
}
func IsInvalidBodyRequestError(err error) bool {
return err != nil && strings.Contains(err.Error(), "invalid body request")
}
type UnauthorizedError struct {
Message string
}
func (e *UnauthorizedError) Error() string {
return e.Message
}
func NewUnauthorizedError(err error) error {
return &UnauthorizedError{
Message: fmt.Sprintf("unauthorized: %s", err.Error()),
}
}
func IsUnauthorizedError(err error) bool {
return err != nil && strings.Contains(err.Error(), "unauthorized")
}
type ConflictError struct {
Message string
}
func (e *ConflictError) Error() string {
return e.Message
}
func NewConflictError(err error) error {
return &ConflictError{
Message: fmt.Sprintf("conflict: %s", err.Error()),
}
}
func IsConflictError(err error) bool {
return err != nil && strings.Contains(err.Error(), "conflict")
}
type BadRequestError struct {
Message string
}
func (e *BadRequestError) Error() string {
return e.Message
}
func NewBadRequestError(err error) error {
return &BadRequestError{
Message: fmt.Sprintf("bad request: %s", err.Error()),
}
}
func IsBadRequestError(err error) bool {
return err != nil && strings.Contains(err.Error(), "bad request")
}
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0
package errors
import (
"fmt"
"net/http"
"regexp"
"time"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func NewErrorMiddleware(defaultErrorHandler func(ctx *gin.Context, err error) ErrorResponse) gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.Next()
errs := ctx.Errors
if len(errs) > 0 {
var errorResponse ErrorResponse
err := errs.Last()
switch e := err.Err.(type) {
case *CustomError:
errorResponse = ErrorResponse{
StatusCode: e.StatusCode,
Message: e.Message,
Code: e.Code,
Timestamp: time.Now(),
Path: ctx.Request.URL.Path,
Method: ctx.Request.Method,
}
case *NotFoundError:
errorResponse = ErrorResponse{
StatusCode: http.StatusNotFound,
Message: err.Err.Error(),
Code: "NOT_FOUND",
Timestamp: time.Now(),
Path: ctx.Request.URL.Path,
Method: ctx.Request.Method,
}
case *UnauthorizedError:
errorResponse = ErrorResponse{
StatusCode: http.StatusUnauthorized,
Message: err.Err.Error(),
Code: "UNAUTHORIZED",
Timestamp: time.Now(),
Path: ctx.Request.URL.Path,
Method: ctx.Request.Method,
}
case *InvalidBodyRequestError:
errorResponse = ErrorResponse{
StatusCode: http.StatusBadRequest,
Message: err.Err.Error(),
Code: "INVALID_REQUEST_BODY",
Timestamp: time.Now(),
Path: ctx.Request.URL.Path,
Method: ctx.Request.Method,
}
case *ConflictError:
errorResponse = ErrorResponse{
StatusCode: http.StatusConflict,
Message: err.Err.Error(),
Code: "CONFLICT",
Timestamp: time.Now(),
Path: ctx.Request.URL.Path,
Method: ctx.Request.Method,
}
case *BadRequestError:
errorResponse = ErrorResponse{
StatusCode: http.StatusBadRequest,
Message: ExtractErrorPart(err.Err.Error()),
Code: "BAD_REQUEST",
Timestamp: time.Now(),
Path: ctx.Request.URL.Path,
Method: ctx.Request.Method,
}
default:
errorResponse = defaultErrorHandler(ctx, err)
}
if errorResponse.StatusCode == http.StatusInternalServerError {
log.WithError(err).WithFields(log.Fields{
"path": ctx.Request.URL.Path,
"method": ctx.Request.Method,
}).Error("Internal Server Error")
} else {
log.WithFields(log.Fields{
"method": ctx.Request.Method,
"URI": ctx.Request.URL.Path,
"status": errorResponse.StatusCode,
"error": errorResponse.Message,
}).Error("API ERROR")
}
// Set explicit content type header
ctx.Header("Content-Type", "application/json")
ctx.JSON(errorResponse.StatusCode, errorResponse)
}
}
}
func ExtractErrorPart(errorMsg string) string {
r := regexp.MustCompile(`(unable to find user [^:]+)`)
matches := r.FindStringSubmatch(errorMsg)
if len(matches) < 2 {
return errorMsg
}
return fmt.Sprintf("bad request: %s", matches[1])
}
+169
View File
@@ -0,0 +1,169 @@
// Copyright 2025 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0
package proxy
import (
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
"time"
common_errors "github.com/daytonaio/common-go/pkg/errors"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
)
var proxyTransport = &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
DialContext: (&net.Dialer{
KeepAlive: 30 * time.Second,
}).DialContext,
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// Custom HTTP client that follows redirects while maintaining original headers
var proxyClient = &http.Client{
Transport: proxyTransport,
// Create a custom redirect policy
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// Copy headers from original request
if len(via) > 0 {
// Copy the headers from the original request
for key, values := range via[0].Header {
// Skip certain headers that shouldn't be copied
if key != "Cookie" {
for _, value := range values {
req.Header.Add(key, value)
}
}
}
}
// Limit the number of redirects to prevent infinite loops
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
},
}
// ProxyRequest handles proxying requests to a sandbox's container
//
// @Tags toolbox
// @Summary Proxy requests to the sandbox toolbox
// @Description Forwards the request to the specified sandbox's container
// @Param workspaceId path string true "Sandbox ID"
// @Param projectId path string true "Project ID"
// @Param path path string true "Path to forward"
// @Success 200 {object} string "Proxied response"
// @Failure 400 {object} string "Bad request"
// @Failure 401 {object} string "Unauthorized"
// @Failure 404 {object} string "Sandbox container not found"
// @Failure 409 {object} string "Sandbox container conflict"
// @Failure 500 {object} string "Internal server error"
// @Router /workspaces/{workspaceId}/{projectId}/toolbox/{path} [get]
func NewProxyRequestHandler(getProxyTarget func(*gin.Context) (*url.URL, string, map[string]string, error)) gin.HandlerFunc {
return func(ctx *gin.Context) {
target, fullTargetURL, extraHeaders, err := getProxyTarget(ctx)
if err != nil {
// Error already sent to the context
return
}
// Create a new outgoing request
outReq, err := http.NewRequestWithContext(
ctx.Request.Context(),
ctx.Request.Method,
fullTargetURL,
ctx.Request.Body,
)
if err != nil {
ctx.Error(common_errors.NewBadRequestError(fmt.Errorf("failed to create outgoing request: %w", err)))
return
}
// Copy headers from original request
for key, values := range ctx.Request.Header {
// Skip the Connection header
if key != "Connection" {
for _, value := range values {
outReq.Header.Add(key, value)
}
}
}
// Set the Host header to the target
outReq.Host = target.Host
outReq.Header.Set("Connection", "keep-alive")
for key, value := range extraHeaders {
outReq.Header.Add(key, value)
}
if ctx.Request.Header.Get("Upgrade") == "websocket" {
ws, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
defer ws.Close()
reqExtraHeaders := http.Header{}
for key, value := range extraHeaders {
reqExtraHeaders.Add(key, value)
}
conn, _, err := websocket.DefaultDialer.DialContext(ctx.Request.Context(), strings.Replace(fullTargetURL, "http", "ws", 1), reqExtraHeaders)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
defer conn.Close()
go func() {
io.Copy(ws.NetConn(), conn.NetConn())
}()
io.Copy(conn.NetConn(), ws.NetConn())
return
}
// Execute the request with our custom client that handles redirects
resp, err := proxyClient.Do(outReq)
if err != nil {
ctx.Error(fmt.Errorf("proxy request failed: %w", err))
return
}
defer resp.Body.Close()
// Copy response headers
for key, values := range resp.Header {
for _, value := range values {
ctx.Writer.Header().Add(key, value)
}
}
// Set the status code
ctx.Writer.WriteHeader(resp.StatusCode)
// Copy the response body
if _, err := io.Copy(ctx.Writer, resp.Body); err != nil {
log.Errorf("Error copying response body: %v", err)
// Error already sent to client, just log here
}
}
}
+1
View File
@@ -22,6 +22,7 @@
"build:production": "nx run-many --target=build --all --parallel=$(getconf _NPROCESSORS_ONLN) --configuration=production",
"serve": "nx run-many --target=serve --all --exclude=daemon --parallel=$(getconf _NPROCESSORS_ONLN) --configuration=development",
"serve:skip-runner": "nx run-many --target=serve --all --exclude=runner,daemon --parallel=$(getconf _NPROCESSORS_ONLN) --configuration=development",
"serve:skip-proxy": "nx run-many --target=serve --all --exclude=proxy,daemon --parallel=$(getconf _NPROCESSORS_ONLN) --configuration=development",
"serve:production": "nx run-many --target=serve --all --exclude=daemon --parallel=$(getconf _NPROCESSORS_ONLN) --configuration=production",
"generate:openapi": "nx run-many --target=openapi --all",
"generate:api-client": "yarn generate:openapi && nx run-many --target=generate:api-client --all",