Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 26 additions & 38 deletions src/main/kotlin/com/coder/gateway/sdk/BaseCoderRestClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import okhttp3.logging.HttpLoggingInterceptor
import org.imgscalr.Scalr
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.time.Instant
Expand Down Expand Up @@ -103,6 +102,11 @@ open class BaseCoderRestClient(
.build().create(CoderV2RestFacade::class.java)
}

private fun <T> error(action: String, res: retrofit2.Response<T>): String {
val details = res.errorBody()?.charStream()?.use{ it.readText() } ?: "no details provided"
return "Unable to $action: url=$url, code=${res.code()}, details=$details"
}

/**
* Authenticate and load information about the current user and the build
* version.
Expand All @@ -122,11 +126,7 @@ open class BaseCoderRestClient(
fun me(): User {
val userResponse = retroRestClient.me().execute()
if (!userResponse.isSuccessful) {
throw AuthenticationResponseException(
"Unable to authenticate to $url: code ${userResponse.code()}, ${
userResponse.message().ifBlank { "has your token expired?" }
}"
)
throw AuthenticationResponseException(error("authenticate", userResponse))
}

return userResponse.body()!!
Expand All @@ -139,11 +139,7 @@ open class BaseCoderRestClient(
fun workspaces(): List<Workspace> {
val workspacesResponse = retroRestClient.workspaces("owner:me").execute()
if (!workspacesResponse.isSuccessful) {
throw WorkspaceResponseException(
"Unable to retrieve workspaces from $url: code ${workspacesResponse.code()}, reason: ${
workspacesResponse.message().ifBlank { "no reason provided" }
}"
)
throw WorkspaceResponseException(error("retrieve workspaces", workspacesResponse))
}

return workspacesResponse.body()!!.workspaces
Expand All @@ -169,31 +165,23 @@ open class BaseCoderRestClient(
fun resources(workspace: Workspace): List<WorkspaceResource> {
val resourcesResponse = retroRestClient.templateVersionResources(workspace.latestBuild.templateVersionID).execute()
if (!resourcesResponse.isSuccessful) {
throw WorkspaceResponseException(
"Unable to retrieve template resources for ${workspace.name} from $url: code ${resourcesResponse.code()}, reason: ${
resourcesResponse.message().ifBlank { "no reason provided" }
}"
)
throw WorkspaceResponseException(error("retrieve resources for ${workspace.name}", resourcesResponse))
}
return resourcesResponse.body()!!
}

fun buildInfo(): BuildInfo {
val buildInfoResponse = retroRestClient.buildInfo().execute()
if (!buildInfoResponse.isSuccessful) {
throw java.lang.IllegalStateException("Unable to retrieve build information for $url, code: ${buildInfoResponse.code()}, reason: ${buildInfoResponse.message().ifBlank { "no reason provided" }}")
throw java.lang.IllegalStateException(error("retrieve build information", buildInfoResponse))
}
return buildInfoResponse.body()!!
}

private fun template(templateID: UUID): Template {
val templateResponse = retroRestClient.template(templateID).execute()
if (!templateResponse.isSuccessful) {
throw TemplateResponseException(
"Unable to retrieve template with ID $templateID from $url, code: ${templateResponse.code()}, reason: ${
templateResponse.message().ifBlank { "no reason provided" }
}"
)
throw TemplateResponseException(error("retrieve template with ID $templateID", templateResponse))
}
return templateResponse.body()!!
}
Expand All @@ -202,11 +190,7 @@ open class BaseCoderRestClient(
val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.START, null, null, null, null)
val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute()
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
throw WorkspaceResponseException(
"Unable to build workspace $workspaceName on $url, code: ${buildResponse.code()}, reason: ${
buildResponse.message().ifBlank { "no reason provided" }
}"
)
throw WorkspaceResponseException(error("start workspace $workspaceName", buildResponse))
}

return buildResponse.body()!!
Expand All @@ -216,28 +200,32 @@ open class BaseCoderRestClient(
val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.STOP, null, null, null, null)
val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute()
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
throw WorkspaceResponseException(
"Unable to stop workspace $workspaceName on $url, code: ${buildResponse.code()}, reason: ${
buildResponse.message().ifBlank { "no reason provided" }
}"
)
throw WorkspaceResponseException(error("stop workspace $workspaceName", buildResponse))
}

return buildResponse.body()!!
}

fun updateWorkspace(workspaceID: UUID, workspaceName: String, lastWorkspaceTransition: WorkspaceTransition, templateID: UUID): WorkspaceBuild {
// Best practice is to STOP a workspace before doing an update if it is
// started.
// 1. If the update changes parameters, the old template might be needed
// to correctly STOP with the existing parameter values.
// 2. The agent gets a new ID and token on each START build. Many
// template authors are not diligent about making sure the agent gets
// restarted with this information when we do two START builds in a
// row.
if (lastWorkspaceTransition == WorkspaceTransition.START) {
stopWorkspace(workspaceID, workspaceName)
}

val template = template(templateID)

val buildRequest =
CreateWorkspaceBuildRequest(template.activeVersionID, lastWorkspaceTransition, null, null, null, null)
CreateWorkspaceBuildRequest(template.activeVersionID, WorkspaceTransition.START, null, null, null, null)
val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute()
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
throw WorkspaceResponseException(
"Unable to update workspace $workspaceName on $url, code: ${buildResponse.code()}, reason: ${
buildResponse.message().ifBlank { "no reason provided" }
}"
)
throw WorkspaceResponseException(error("update workspace $workspaceName", buildResponse))
}

return buildResponse.body()!!
Expand Down
14 changes: 14 additions & 0 deletions src/main/kotlin/com/coder/gateway/sdk/v2/models/Response.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.coder.gateway.sdk.v2.models

import com.google.gson.annotations.SerializedName

data class Validation (
@SerializedName("field") val field: String,
@SerializedName("detail") val detail: String,
)

data class Response (
@SerializedName("message") val message: String,
@SerializedName("detail") val detail: String,
@SerializedName("validations") val validations: List<Validation> = emptyList(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal class CoderGatewayConnectionProviderTest {

@Test
fun getMatchingAgent() {
val ws = DataGen.workspace("ws", agents)
val ws = DataGen.workspace("ws", agents = agents)

val tests = listOf(
Pair(mapOf("agent" to "agent_name"), "9a920eee-47fb-4571-9501-e4b3120c12f2"),
Expand All @@ -41,7 +41,7 @@ internal class CoderGatewayConnectionProviderTest {

@Test
fun failsToGetMatchingAgent() {
val ws = DataGen.workspace("ws", agents)
val ws = DataGen.workspace("ws", agents = agents)
val tests = listOf(
Triple(emptyMap(), MissingArgumentException::class, "Unable to determine"),
Triple(mapOf("agent" to ""), MissingArgumentException::class, "Unable to determine"),
Expand All @@ -68,7 +68,7 @@ internal class CoderGatewayConnectionProviderTest {

@Test
fun getsFirstAgentWhenOnlyOne() {
val ws = DataGen.workspace("ws", oneAgent)
val ws = DataGen.workspace("ws", agents = oneAgent)
val tests = listOf(
emptyMap(),
mapOf("agent" to ""),
Expand All @@ -84,7 +84,7 @@ internal class CoderGatewayConnectionProviderTest {

@Test
fun failsToGetAgentWhenOnlyOne() {
val ws = DataGen.workspace("ws", oneAgent)
val ws = DataGen.workspace("ws", agents = oneAgent)
val tests = listOf(
Triple(mapOf("agent" to "ws"), IllegalArgumentException::class, "agent named"),
Triple(mapOf("agent" to "ws.agent_name_3"), IllegalArgumentException::class, "agent named"),
Expand Down Expand Up @@ -120,4 +120,4 @@ internal class CoderGatewayConnectionProviderTest {
assertContains(ex.message.toString(), it.third)
}
}
}
}
Loading