From e858ab0455103bec0b184d193f897918f4f9a0d5 Mon Sep 17 00:00:00 2001 From: Ioan Faur Date: Mon, 21 Nov 2022 22:51:15 +0200 Subject: [PATCH 1/4] Upgrade gradle plugins --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7def51ff..e837e472 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { // Kotlin support id("org.jetbrains.kotlin.jvm") version "1.7.21" // Gradle IntelliJ Plugin - id("org.jetbrains.intellij") version "1.9.0" + id("org.jetbrains.intellij") version "1.10.0" // Gradle Changelog Plugin id("org.jetbrains.changelog") version "2.0.0" // Gradle Qodana Plugin @@ -23,7 +23,7 @@ val ktorVersion = properties("ktorVersion") dependencies { implementation("com.squareup.retrofit2:retrofit:2.9.0") // define a BOM and its version - implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3")) + implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0")) implementation("com.squareup.retrofit2:converter-gson:2.9.0") implementation("com.squareup.okhttp3:okhttp") implementation("com.squareup.okhttp3:okhttp-urlconnection") From 7477fcfd988f30444f118c982065b943c6d17607 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 22 Nov 2022 00:51:54 +0200 Subject: [PATCH 2/4] Fix: support for the new Coder auth cookie - cookie was renamed from seesion_token to coder_session_token - resolves #86 --- CHANGELOG.md | 18 ++++++++++++++---- .../gateway/models/WorkspaceAgentStatus.kt | 18 +++++++++--------- .../gateway/sdk/CoderRestClientService.kt | 4 ++-- .../gateway/sdk/v2/models/ProvisionerJob.kt | 3 +++ .../sdk/v2/models/WorkspaceResourceMetadata.kt | 9 +++++++++ 5 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceResourceMetadata.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bdd5408..7ffcba0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,23 @@ # coder-gateway Changelog ## [Unreleased] +### Added + +- upgraded API models + +### Fixed + +- support for the new Coder auth cookies ## [2.1.1] -### Added -- support for remembering last opened Coder session -### Changed -- minimum supported Gateway build is now 222.3739.54 +### Added + +- support for remembering last opened Coder session + +### Changed + +- minimum supported Gateway build is now 222.3739.54 - some dialog titles ## [2.1.0] diff --git a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentStatus.kt b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentStatus.kt index 6a8246c3..cdb67a3e 100644 --- a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentStatus.kt +++ b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentStatus.kt @@ -2,7 +2,7 @@ package com.coder.gateway.models import com.coder.gateway.sdk.v2.models.ProvisionerJobStatus import com.coder.gateway.sdk.v2.models.Workspace -import com.coder.gateway.sdk.v2.models.WorkspaceBuildTransition +import com.coder.gateway.sdk.v2.models.WorkspaceTransition enum class WorkspaceAgentStatus(val label: String) { QUEUED("◍ Queued"), STARTING("⦿ Starting"), STOPPING("◍ Stopping"), DELETING("⦸ Deleting"), @@ -12,16 +12,16 @@ enum class WorkspaceAgentStatus(val label: String) { companion object { fun from(workspace: Workspace) = when (workspace.latestBuild.job.status) { ProvisionerJobStatus.PENDING -> QUEUED - ProvisionerJobStatus.RUNNING -> when (workspace.latestBuild.workspaceTransition) { - WorkspaceBuildTransition.START -> STARTING - WorkspaceBuildTransition.STOP -> STOPPING - WorkspaceBuildTransition.DELETE -> DELETING + ProvisionerJobStatus.RUNNING -> when (workspace.latestBuild.transition) { + WorkspaceTransition.START -> STARTING + WorkspaceTransition.STOP -> STOPPING + WorkspaceTransition.DELETE -> DELETING } - ProvisionerJobStatus.SUCCEEDED -> when (workspace.latestBuild.workspaceTransition) { - WorkspaceBuildTransition.START -> RUNNING - WorkspaceBuildTransition.STOP -> STOPPED - WorkspaceBuildTransition.DELETE -> DELETED + ProvisionerJobStatus.SUCCEEDED -> when (workspace.latestBuild.transition) { + WorkspaceTransition.START -> RUNNING + WorkspaceTransition.STOP -> STOPPED + WorkspaceTransition.DELETE -> DELETED } ProvisionerJobStatus.CANCELING -> CANCELING diff --git a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt index be81a45a..71018494 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt @@ -46,7 +46,7 @@ class CoderRestClientService { val cookieJar = JavaNetCookieJar(CookieManager()).apply { saveFromResponse( cookieUrl, - listOf(Cookie.parse(cookieUrl, "session_token=$token")!!) + listOf(Cookie.parse(cookieUrl, "coder_session_token=$token")!!) ) } val gson: Gson = GsonBuilder() @@ -55,7 +55,7 @@ class CoderRestClientService { .create() val interceptor = HttpLoggingInterceptor() - interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC) + interceptor.setLevel(HttpLoggingInterceptor.Level.BODY) retroRestClient = Retrofit.Builder() .baseUrl(url.toString()) .client( diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/ProvisionerJob.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/ProvisionerJob.kt index 193e4fc5..008edf34 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/ProvisionerJob.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/ProvisionerJob.kt @@ -9,9 +9,12 @@ data class ProvisionerJob( @SerializedName("created_at") val createdAt: Instant, @SerializedName("started_at") val startedAt: Instant, @SerializedName("completed_at") val completedAt: Instant, + @SerializedName("canceled_at") val canceledAt: Instant, @SerializedName("error") val error: String, @SerializedName("status") val status: ProvisionerJobStatus, @SerializedName("worker_id") val workerID: UUID, + @SerializedName("file_id") val fileID: UUID, + @SerializedName("tags") val tags: Map, ) enum class ProvisionerJobStatus { diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceResourceMetadata.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceResourceMetadata.kt new file mode 100644 index 00000000..fd90111b --- /dev/null +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceResourceMetadata.kt @@ -0,0 +1,9 @@ +package com.coder.gateway.sdk.v2.models + +import com.google.gson.annotations.SerializedName + +data class WorkspaceResourceMetadata( + @SerializedName("key") val key: String, + @SerializedName("value") val value: String, + @SerializedName("sensitive") val sensitive: Boolean +) From d511e8c81e7f7efa4713032c17394ef97b9b8e29 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 22 Nov 2022 23:56:26 +0200 Subject: [PATCH 3/4] Impl: updated all API models The http client now works with latest versions of Coder --- CHANGELOG.md | 2 +- .../gateway/models/WorkspaceAgentModel.kt | 3 +- .../gateway/sdk/CoderRestClientService.kt | 13 ++--- .../coder/gateway/sdk/v2/CoderV2RestFacade.kt | 4 +- .../gateway/sdk/v2/models/BuildReason.kt | 20 ++++++++ .../sdk/v2/models/CreateParameterRequest.kt | 25 ++++++++-- .../v2/models/CreateWorkspaceBuildRequest.kt | 20 ++++---- .../gateway/sdk/v2/models/ProvisionerJob.kt | 10 ++-- .../com/coder/gateway/sdk/v2/models/Role.kt | 32 +++++++++++- .../coder/gateway/sdk/v2/models/Template.kt | 20 ++++++-- .../com/coder/gateway/sdk/v2/models/User.kt | 16 ++++-- .../coder/gateway/sdk/v2/models/Workspace.kt | 8 ++- .../gateway/sdk/v2/models/WorkspaceAgent.kt | 31 ++++++++++-- .../gateway/sdk/v2/models/WorkspaceApp.kt | 43 +++++++++++++++- .../gateway/sdk/v2/models/WorkspaceBuild.kt | 49 ++++++++++++++----- .../sdk/v2/models/WorkspaceResource.kt | 10 ++-- .../sdk/v2/models/WorkspaceTransition.kt | 14 ++++++ .../sdk/v2/models/WorkspacesResponse.kt | 8 +++ .../views/steps/CoderWorkspacesStepView.kt | 6 +-- 19 files changed, 276 insertions(+), 58 deletions(-) create mode 100644 src/main/kotlin/com/coder/gateway/sdk/v2/models/BuildReason.kt create mode 100644 src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceTransition.kt create mode 100644 src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspacesResponse.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ffcba0c..16180b21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ## [Unreleased] ### Added -- upgraded API models +- upgraded support to the latest Coder platform ### Fixed diff --git a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt index 8ebdee2e..049501c0 100644 --- a/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt +++ b/src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt @@ -2,6 +2,7 @@ package com.coder.gateway.models import com.coder.gateway.sdk.Arch import com.coder.gateway.sdk.OS +import com.coder.gateway.sdk.v2.models.WorkspaceTransition import java.util.UUID data class WorkspaceAgentModel( @@ -12,7 +13,7 @@ data class WorkspaceAgentModel( val templateName: String, val status: WorkspaceVersionStatus, val agentStatus: WorkspaceAgentStatus, - val lastBuildTransition: String, + val lastBuildTransition: WorkspaceTransition, val agentOS: OS?, val agentArch: Arch?, val homeDirectory: String? diff --git a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt index 71018494..f5121f6d 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt @@ -13,6 +13,7 @@ import com.coder.gateway.sdk.v2.models.User import com.coder.gateway.sdk.v2.models.Workspace import com.coder.gateway.sdk.v2.models.WorkspaceAgent import com.coder.gateway.sdk.v2.models.WorkspaceBuild +import com.coder.gateway.sdk.v2.models.WorkspaceTransition import com.google.gson.Gson import com.google.gson.GsonBuilder import com.intellij.openapi.components.Service @@ -55,7 +56,7 @@ class CoderRestClientService { .create() val interceptor = HttpLoggingInterceptor() - interceptor.setLevel(HttpLoggingInterceptor.Level.BODY) + interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC) retroRestClient = Retrofit.Builder() .baseUrl(url.toString()) .client( @@ -91,7 +92,7 @@ class CoderRestClientService { throw WorkspaceResponseException("Could not retrieve Coder Workspaces:${workspacesResponse.code()}, reason: ${workspacesResponse.message()}") } - return workspacesResponse.body()!! + return workspacesResponse.body()!!.workspaces } private fun buildInfo(): BuildInfo { @@ -126,7 +127,7 @@ class CoderRestClientService { } fun startWorkspace(workspaceID: UUID, workspaceName: String): WorkspaceBuild { - val buildRequest = CreateWorkspaceBuildRequest(null, "start", null, null, null) + val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.START, null, null, null, null) val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute() if (buildResponse.code() != HTTP_CREATED) { throw WorkspaceResponseException("Failed to build workspace ${workspaceName}: ${buildResponse.code()}, reason: ${buildResponse.message()}") @@ -136,7 +137,7 @@ class CoderRestClientService { } fun stopWorkspace(workspaceID: UUID, workspaceName: String): WorkspaceBuild { - val buildRequest = CreateWorkspaceBuildRequest(null, "stop", null, null, null) + val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.STOP, null, null, null, null) val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute() if (buildResponse.code() != HTTP_CREATED) { throw WorkspaceResponseException("Failed to stop workspace ${workspaceName}: ${buildResponse.code()}, reason: ${buildResponse.message()}") @@ -145,10 +146,10 @@ class CoderRestClientService { return buildResponse.body()!! } - fun updateWorkspace(workspaceID: UUID, workspaceName: String, lastWorkspaceTransition: String, templateID: UUID): WorkspaceBuild { + fun updateWorkspace(workspaceID: UUID, workspaceName: String, lastWorkspaceTransition: WorkspaceTransition, templateID: UUID): WorkspaceBuild { val template = template(templateID) - val buildRequest = CreateWorkspaceBuildRequest(template.activeVersionID, lastWorkspaceTransition, null, null, null) + val buildRequest = CreateWorkspaceBuildRequest(template.activeVersionID, lastWorkspaceTransition, null, null, null, null) val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute() if (buildResponse.code() != HTTP_CREATED) { throw WorkspaceResponseException("Failed to update workspace ${workspaceName}: ${buildResponse.code()}, reason: ${buildResponse.message()}") diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt index 37618b51..4587b27b 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/CoderV2RestFacade.kt @@ -4,9 +4,9 @@ import com.coder.gateway.sdk.v2.models.BuildInfo import com.coder.gateway.sdk.v2.models.CreateWorkspaceBuildRequest import com.coder.gateway.sdk.v2.models.Template import com.coder.gateway.sdk.v2.models.User -import com.coder.gateway.sdk.v2.models.Workspace import com.coder.gateway.sdk.v2.models.WorkspaceBuild import com.coder.gateway.sdk.v2.models.WorkspaceResource +import com.coder.gateway.sdk.v2.models.WorkspacesResponse import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET @@ -27,7 +27,7 @@ interface CoderV2RestFacade { * Retrieves all workspaces the authenticated user has access to. */ @GET("api/v2/workspaces") - fun workspaces(@Query("q") searchParams: String): Call> + fun workspaces(@Query("q") searchParams: String): Call @GET("api/v2/buildinfo") fun buildInfo(): Call diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/BuildReason.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/BuildReason.kt new file mode 100644 index 00000000..7ddaebab --- /dev/null +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/BuildReason.kt @@ -0,0 +1,20 @@ +package com.coder.gateway.sdk.v2.models + +import com.google.gson.annotations.SerializedName + +enum class BuildReason { + // "initiator" is used when a workspace build is triggered by a user. + // Combined with the initiator id/username, it indicates which user initiated the build. + @SerializedName("initiator") + INITIATOR, + + // "autostart" is used when a build to start a workspace is triggered by Autostart. + // The initiator id/username in this case is the workspace owner and can be ignored. + @SerializedName("autostart") + AUTOSTART, + + // "autostop" is used when a build to stop a workspace is triggered by Autostop. + // The initiator id/username in this case is the workspace owner and can be ignored. + @SerializedName("autostop") + AUTOSTOP +} \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateParameterRequest.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateParameterRequest.kt index 782c036c..04e5f12b 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateParameterRequest.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateParameterRequest.kt @@ -7,6 +7,25 @@ data class CreateParameterRequest( @SerializedName("copy_from_parameter") val cloneID: UUID?, @SerializedName("name") val name: String, @SerializedName("source_value") val sourceValue: String, - @SerializedName("source_scheme") val sourceScheme: String, - @SerializedName("destination_scheme") val destinationScheme: String -) \ No newline at end of file + @SerializedName("source_scheme") val sourceScheme: ParameterSourceScheme, + @SerializedName("destination_scheme") val destinationScheme: ParameterDestinationScheme +) + +enum class ParameterSourceScheme { + @SerializedName("none") + NONE, + + @SerializedName("data") + DATA +} + +enum class ParameterDestinationScheme { + @SerializedName("none") + NONE, + + @SerializedName("environment_variable") + ENVIRONMENT_VARIABLE, + + @SerializedName("provisioner_variable") + PROVISIONER_VARIABLE +} \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateWorkspaceBuildRequest.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateWorkspaceBuildRequest.kt index 690ab029..4a3e192e 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateWorkspaceBuildRequest.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/CreateWorkspaceBuildRequest.kt @@ -5,12 +5,13 @@ import java.util.UUID data class CreateWorkspaceBuildRequest( @SerializedName("template_version_id") val templateVersionID: UUID?, - @SerializedName("transition") val transition: String, + @SerializedName("transition") val transition: WorkspaceTransition, @SerializedName("dry_run") val dryRun: Boolean?, - @SerializedName("state") val state: Array?, + @SerializedName("state") val provisionerState: Array?, + // Orphan may be set for the Destroy transition. + @SerializedName("orphan") val orphan: Boolean?, @SerializedName("parameter_values") val parameterValues: Array? ) { - override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -20,10 +21,11 @@ data class CreateWorkspaceBuildRequest( if (templateVersionID != other.templateVersionID) return false if (transition != other.transition) return false if (dryRun != other.dryRun) return false - if (state != null) { - if (other.state == null) return false - if (!state.contentEquals(other.state)) return false - } else if (other.state != null) return false + if (provisionerState != null) { + if (other.provisionerState == null) return false + if (!provisionerState.contentEquals(other.provisionerState)) return false + } else if (other.provisionerState != null) return false + if (orphan != other.orphan) return false if (parameterValues != null) { if (other.parameterValues == null) return false if (!parameterValues.contentEquals(other.parameterValues)) return false @@ -36,9 +38,9 @@ data class CreateWorkspaceBuildRequest( var result = templateVersionID?.hashCode() ?: 0 result = 31 * result + transition.hashCode() result = 31 * result + (dryRun?.hashCode() ?: 0) - result = 31 * result + (state?.contentHashCode() ?: 0) + result = 31 * result + (provisionerState?.contentHashCode() ?: 0) + result = 31 * result + (orphan?.hashCode() ?: 0) result = 31 * result + (parameterValues?.contentHashCode() ?: 0) return result } } - diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/ProvisionerJob.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/ProvisionerJob.kt index 008edf34..aec24808 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/ProvisionerJob.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/ProvisionerJob.kt @@ -7,12 +7,12 @@ import java.util.UUID data class ProvisionerJob( @SerializedName("id") val id: UUID, @SerializedName("created_at") val createdAt: Instant, - @SerializedName("started_at") val startedAt: Instant, - @SerializedName("completed_at") val completedAt: Instant, - @SerializedName("canceled_at") val canceledAt: Instant, - @SerializedName("error") val error: String, + @SerializedName("started_at") val startedAt: Instant?, + @SerializedName("completed_at") val completedAt: Instant?, + @SerializedName("canceled_at") val canceledAt: Instant?, + @SerializedName("error") val error: String?, @SerializedName("status") val status: ProvisionerJobStatus, - @SerializedName("worker_id") val workerID: UUID, + @SerializedName("worker_id") val workerID: UUID?, @SerializedName("file_id") val fileID: UUID, @SerializedName("tags") val tags: Map, ) diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/Role.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/Role.kt index a0457ec8..f46a5c9f 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/Role.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/Role.kt @@ -2,4 +2,34 @@ package com.coder.gateway.sdk.v2.models import com.google.gson.annotations.SerializedName -data class Role(@SerializedName("name") val name: String, @SerializedName("display_name") val displayName: String) +data class Role( + @SerializedName("name") val name: String, + @SerializedName("display_name") val displayName: String, + @SerializedName("site") val site: Permission, + // Org is a map of orgid to permissions. We represent orgid as a string. + // We scope the organizations in the role so we can easily combine all the + // roles. + @SerializedName("org") val org: Map>, + @SerializedName("user") val user: List, + + ) + +data class Permission( + @SerializedName("negate") val negate: Boolean, + @SerializedName("resource_type") val resourceType: String, + @SerializedName("action") val action: Action, +) + +enum class Action { + @SerializedName("create") + CREATE, + + @SerializedName("read") + READ, + + @SerializedName("update") + UPDATE, + + @SerializedName("delete") + DELETE +} \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/Template.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/Template.kt index 42edd61c..4909f45a 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/Template.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/Template.kt @@ -10,12 +10,26 @@ data class Template( @SerializedName("updated_at") val updatedAt: Instant, @SerializedName("organization_id") val organizationIterator: UUID, @SerializedName("name") val name: String, - @SerializedName("provisioner") val provisioner: String, + @SerializedName("display_name") val displayName: String, + @SerializedName("provisioner") val provisioner: ProvisionerType, @SerializedName("active_version_id") val activeVersionID: UUID, @SerializedName("workspace_owner_count") val workspaceOwnerCount: Int, + @SerializedName("active_user_count") val activeUserCount: Int, + @SerializedName("build_time_stats") val buildTimeStats: Map, @SerializedName("description") val description: String, - @SerializedName("max_ttl_ms") val maxTTLMillis: Long, - @SerializedName("min_autostart_interval_ms") val minAutostartIntervalMillis: Long, + @SerializedName("icon") val icon: String, + @SerializedName("default_ttl_ms") val defaultTTLMillis: Long, @SerializedName("created_by_id") val createdByID: UUID, @SerializedName("created_by_name") val createdByName: String, + @SerializedName("allow_user_cancel_workspace_jobs") val allowUserCancelWorkspaceJobs: Boolean, ) + +enum class ProvisionerType { + @SerializedName("echo") + ECHO, + + @SerializedName("terraform") + TERRAFORM +} + +data class TransitionStats(val p50: Long, val p95: Long) \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/User.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/User.kt index 2b7c99d7..c9ec6394 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/User.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/User.kt @@ -6,11 +6,21 @@ import java.util.UUID data class User( @SerializedName("id") val id: UUID, - @SerializedName("email") val email: String, @SerializedName("username") val username: String, + @SerializedName("email") val email: String, @SerializedName("created_at") val createdAt: Instant, + @SerializedName("last_seen_at") val lastSeenAt: Instant, - @SerializedName("status") val status: String?, - @SerializedName("organization_ids") val organizationIDs: List?, + @SerializedName("status") val status: UserStatus, + @SerializedName("organization_ids") val organizationIDs: List, @SerializedName("roles") val roles: List?, + @SerializedName("avatar_url") val avatarURL: String, ) + +enum class UserStatus { + @SerializedName("active") + ACTIVE, + + @SerializedName("suspended") + SUSPENDED +} \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/Workspace.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/Workspace.kt index 180fcc70..8ccecaa2 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/Workspace.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/Workspace.kt @@ -15,9 +15,13 @@ data class Workspace( @SerializedName("owner_name") val ownerName: String, @SerializedName("template_id") val templateID: UUID, @SerializedName("template_name") val templateName: String, + @SerializedName("template_display_name") val templateDisplayName: String, + @SerializedName("template_icon") val templateIcon: String, + @SerializedName("template_allow_user_cancel_workspace_jobs") val templateAllowUserCancelWorkspaceJobs: Boolean, @SerializedName("latest_build") val latestBuild: WorkspaceBuild, @SerializedName("outdated") val outdated: Boolean, @SerializedName("name") val name: String, - @SerializedName("autostart_schedule") val autostartSchedule: String, - @SerializedName("ttl") val ttl: Long, + @SerializedName("autostart_schedule") val autostartSchedule: String?, + @SerializedName("ttl_ms") val ttlMillis: Long?, + @SerializedName("last_used_at") val lastUsedAt: Instant, ) \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceAgent.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceAgent.kt index 4fb4af56..727a9097 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceAgent.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceAgent.kt @@ -11,14 +11,37 @@ data class WorkspaceAgent( @SerializedName("first_connected_at") val firstConnectedAt: Instant?, @SerializedName("last_connected_at") val lastConnectedAt: Instant?, @SerializedName("disconnected_at") val disconnectedAt: Instant?, - @SerializedName("status") val status: String, + @SerializedName("status") val status: WorkspaceAgentStatus, @SerializedName("name") val name: String, @SerializedName("resource_id") val resourceID: UUID, - @SerializedName("instance_id") val instanceID: String, + @SerializedName("instance_id") val instanceID: String?, @SerializedName("architecture") val architecture: String, @SerializedName("environment_variables") val envVariables: Map, @SerializedName("operating_system") val operatingSystem: String, - @SerializedName("startup_script") val startupScript: String, + @SerializedName("startup_script") val startupScript: String?, @SerializedName("directory") val directory: String?, - @SerializedName("apps") val apps: List + @SerializedName("version") val version: String, + @SerializedName("apps") val apps: List, + @SerializedName("latency") val derpLatency: Map?, + @SerializedName("connection_timeout_seconds") val connectionTimeoutSeconds: Int, + @SerializedName("troubleshooting_url") val troubleshootingURL: String +) + +enum class WorkspaceAgentStatus { + @SerializedName("connecting") + CONNECTING, + + @SerializedName("connected") + CONNECTED, + + @SerializedName("disconnected") + DISCONNECTED, + + @SerializedName("timeout") + TIMEOUT +} + +data class DERPRegion( + @SerializedName("preferred") val preferred: Boolean, + @SerializedName("latency_ms") val latencyMillis: Double ) \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceApp.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceApp.kt index 736dd813..82d978c9 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceApp.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceApp.kt @@ -5,7 +5,48 @@ import java.util.UUID data class WorkspaceApp( @SerializedName("id") val id: UUID, - @SerializedName("name") val name: String, + // unique identifier within the agent + @SerializedName("slug") val slug: String, + // friendly name for the app + @SerializedName("display_name") val displayName: String, @SerializedName("command") val command: String?, + // relative path or external URL @SerializedName("icon") val icon: String?, + @SerializedName("subdomain") val subdomain: Boolean, + @SerializedName("sharing_level") val sharingLevel: WorkspaceAppSharingLevel, + @SerializedName("healthcheck") val healthCheck: HealthCheck, + @SerializedName("health") val health: WorkspaceAppHealth, ) + +enum class WorkspaceAppSharingLevel { + @SerializedName("owner") + OWNER, + + @SerializedName("authenticated") + AUTHENTICATED, + + @SerializedName("public") + PUBLIC +} + +data class HealthCheck( + @SerializedName("url") val url: String, + // Interval specifies the seconds between each health check. + @SerializedName("interval") val interval: Int, + // Threshold specifies the number of consecutive failed health checks before returning "unhealthy". + @SerializedName("Threshold") val threshold: Int +) + +enum class WorkspaceAppHealth { + @SerializedName("disabled") + DISABLED, + + @SerializedName("initializing") + INITIALIZING, + + @SerializedName("healthy") + HEALTHY, + + @SerializedName("unhealthy") + UNHEALTHY +} \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceBuild.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceBuild.kt index 047ef204..138ba262 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceBuild.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceBuild.kt @@ -13,23 +13,50 @@ data class WorkspaceBuild( @SerializedName("created_at") val createdAt: Instant, @SerializedName("updated_at") val updatedAt: Instant, @SerializedName("workspace_id") val workspaceID: UUID, + @SerializedName("workspace_name") val workspaceName: String, + @SerializedName("workspace_owner_id") val workspaceOwnerID: UUID, + @SerializedName("workspace_owner_name") val workspaceOwnerName: String, @SerializedName("template_version_id") val templateVersionID: UUID, @SerializedName("build_number") val buildNumber: Int, - @SerializedName("name") val name: String, - @SerializedName("transition") val workspaceTransition: WorkspaceBuildTransition, - @SerializedName("owner_id") val ownerID: UUID, + @SerializedName("transition") val transition: WorkspaceTransition, @SerializedName("initiator_id") val initiatorID: UUID, + @SerializedName("initiator_name") val initiatorUsername: String, @SerializedName("job") val job: ProvisionerJob, - @SerializedName("deadline") val deadline: Instant, + @SerializedName("reason") val reason: BuildReason, + @SerializedName("resources") val resources: List, + @SerializedName("deadline") val deadline: Instant?, + @SerializedName("status") val status: WorkspaceStatus, + @SerializedName("daily_cost") val dailyCost: Int, ) -enum class WorkspaceBuildTransition { - @SerializedName("start") - START, +enum class WorkspaceStatus { + @SerializedName("pending") + PENDING, - @SerializedName("stop") - STOP, + @SerializedName("starting") + STARTING, - @SerializedName("delete") - DELETE + @SerializedName("running") + RUNNING, + + @SerializedName("stopping") + STOPPING, + + @SerializedName("stopped") + STOPPED, + + @SerializedName("failed") + FAILED, + + @SerializedName("canceling") + CANCELING, + + @SerializedName("canceled") + CANCELED, + + @SerializedName("deleting") + DELETING, + + @SerializedName("deleted") + DELETED } \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceResource.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceResource.kt index 8b993232..81ea90e5 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceResource.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceResource.kt @@ -8,8 +8,12 @@ data class WorkspaceResource( @SerializedName("id") val id: UUID, @SerializedName("created_at") val createdAt: Instant, @SerializedName("job_id") val jobID: UUID, - @SerializedName("workspace_transition") val workspaceTransition: String, + @SerializedName("workspace_transition") val workspaceTransition: WorkspaceTransition, @SerializedName("type") val type: String, @SerializedName("name") val name: String, - @SerializedName("agents") val agents: List? -) + @SerializedName("hide") val hide: Boolean, + @SerializedName("icon") val icon: String, + @SerializedName("agents") val agents: List?, + @SerializedName("metadata") val metadata: List?, + @SerializedName("daily_cost") val dailyCost: Int +) \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceTransition.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceTransition.kt new file mode 100644 index 00000000..95516c68 --- /dev/null +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceTransition.kt @@ -0,0 +1,14 @@ +package com.coder.gateway.sdk.v2.models + +import com.google.gson.annotations.SerializedName + +enum class WorkspaceTransition { + @SerializedName("start") + START, + + @SerializedName("stop") + STOP, + + @SerializedName("delete") + DELETE +} \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspacesResponse.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspacesResponse.kt new file mode 100644 index 00000000..486341af --- /dev/null +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspacesResponse.kt @@ -0,0 +1,8 @@ +package com.coder.gateway.sdk.v2.models + +import com.google.gson.annotations.SerializedName + +data class WorkspacesResponse( + @SerializedName("workspaces") val workspaces: List, + @SerializedName("count") val count: Int +) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index dbd93cc6..b1c388e4 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -425,7 +425,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : this.templateName, WorkspaceVersionStatus.from(this), WorkspaceAgentStatus.from(this), - this.latestBuild.workspaceTransition.name.toLowerCase(), + this.latestBuild.transition, null, null, null @@ -443,7 +443,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : this.templateName, WorkspaceVersionStatus.from(this), WorkspaceAgentStatus.from(this), - this.latestBuild.workspaceTransition.name.toLowerCase(), + this.latestBuild.transition, OS.from(agent.operatingSystem), Arch.from(agent.architecture), agent.directory @@ -461,7 +461,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) : this.templateName, WorkspaceVersionStatus.from(this), WorkspaceAgentStatus.from(this), - this.latestBuild.workspaceTransition.name.toLowerCase(), + this.latestBuild.transition, null, null, null From a8ccb01dd031b4eda21d91960f14bd1dcf33acde Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Wed, 23 Nov 2022 00:57:24 +0200 Subject: [PATCH 4/4] Impl: authenticate with HTTP header - previously done with the help of session cookies - session token is now sent as a HTTP header --- CHANGELOG.md | 4 ++-- build.gradle.kts | 1 - .../coder/gateway/sdk/CoderRestClientService.kt | 17 ++--------------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16180b21..ad8bceb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,11 @@ ## [Unreleased] ### Added -- upgraded support to the latest Coder platform +- upgraded support for the latest Coder REST API ### Fixed -- support for the new Coder auth cookies +- authentication flow is now done using HTTP headers ## [2.1.1] diff --git a/build.gradle.kts b/build.gradle.kts index e837e472..638749cd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,7 +26,6 @@ dependencies { implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0")) implementation("com.squareup.retrofit2:converter-gson:2.9.0") implementation("com.squareup.okhttp3:okhttp") - implementation("com.squareup.okhttp3:okhttp-urlconnection") implementation("com.squareup.okhttp3:logging-interceptor") implementation("org.zeroturnaround:zt-exec:1.12") { diff --git a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt index f5121f6d..2cc56438 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt @@ -17,14 +17,10 @@ import com.coder.gateway.sdk.v2.models.WorkspaceTransition import com.google.gson.Gson import com.google.gson.GsonBuilder import com.intellij.openapi.components.Service -import okhttp3.Cookie -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.JavaNetCookieJar import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory -import java.net.CookieManager import java.net.HttpURLConnection.HTTP_CREATED import java.net.URL import java.time.Instant @@ -43,26 +39,17 @@ class CoderRestClientService { * @throws [AuthenticationResponseException] if authentication failed. */ fun initClientSession(url: URL, token: String): User { - val cookieUrl = url.toHttpUrlOrNull()!! - val cookieJar = JavaNetCookieJar(CookieManager()).apply { - saveFromResponse( - cookieUrl, - listOf(Cookie.parse(cookieUrl, "coder_session_token=$token")!!) - ) - } val gson: Gson = GsonBuilder() .registerTypeAdapter(Instant::class.java, InstantConverter()) .setPrettyPrinting() .create() - val interceptor = HttpLoggingInterceptor() - interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC) retroRestClient = Retrofit.Builder() .baseUrl(url.toString()) .client( OkHttpClient.Builder() - .addInterceptor(interceptor) - .cookieJar(cookieJar) + .addInterceptor { it.proceed(it.request().newBuilder().addHeader("Coder-Session-Token", token).build()) } + .addInterceptor(HttpLoggingInterceptor().apply { setLevel(HttpLoggingInterceptor.Level.BASIC) }) .build() ) .addConverterFactory(GsonConverterFactory.create(gson))