Skip to content

Commit 09e2d45

Browse files
committed
Impl: prevent version mismatch
- basic semantic versioning parser and comparator - logic to compare if two coder versions are API compatible - basic checking between a tested Coder version number and the Coder version - resolves #89
1 parent 4be829c commit 09e2d45

File tree

5 files changed

+73
-0
lines changed

5 files changed

+73
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.coder.gateway
2+
3+
import com.coder.gateway.sdk.CoderSemVer
4+
import com.intellij.DynamicBundle
5+
import org.jetbrains.annotations.NonNls
6+
import org.jetbrains.annotations.PropertyKey
7+
8+
@NonNls
9+
private const val BUNDLE = "version.CoderSupportedVersions"
10+
11+
object CoderSupportedVersions : DynamicBundle(BUNDLE) {
12+
val maxCoderVersion = CoderSemVer.parse(message("maxCoderVersion"))
13+
14+
@Suppress("SpreadOperator")
15+
@JvmStatic
16+
private fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = getMessage(key, *params)
17+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.coder.gateway.sdk
2+
3+
4+
class CoderSemVer(private val major: Int = 0, private val minor: Int = 0) {
5+
6+
init {
7+
require(major >= 0) { "Coder major version must be a positive number" }
8+
require(minor >= 0) { "Coder minor version must be a positive number" }
9+
}
10+
11+
fun isCompatibleWith(other: CoderSemVer): Boolean {
12+
// in the initial development phase minor changes when there are API incompatibilities
13+
if (this.major == 0) {
14+
if (other.major > 0) return false
15+
return this.minor == other.minor
16+
}
17+
return this.major <= other.major
18+
}
19+
20+
companion object {
21+
private val pattern = """^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?\$""".toRegex()
22+
23+
fun isValidVersion(semVer: String) = pattern.matchEntire(semVer.trimStart('v')) != null
24+
25+
fun parse(semVer: String): CoderSemVer {
26+
val matchResult = pattern.matchEntire(semVer.trimStart('v')) ?: throw IllegalArgumentException("$semVer could not be parsed")
27+
return CoderSemVer(
28+
if (matchResult.groupValues[1].isNotEmpty()) matchResult.groupValues[1].toInt() else 0,
29+
if (matchResult.groupValues[2].isNotEmpty()) matchResult.groupValues[2].toInt() else 0,
30+
)
31+
}
32+
}
33+
}

src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.coder.gateway.views.steps
22

33
import com.coder.gateway.CoderGatewayBundle
4+
import com.coder.gateway.CoderSupportedVersions
45
import com.coder.gateway.icons.CoderIcons
56
import com.coder.gateway.models.CoderWorkspacesWizardModel
67
import com.coder.gateway.models.WorkspaceAgentModel
@@ -15,6 +16,7 @@ import com.coder.gateway.models.WorkspaceVersionStatus
1516
import com.coder.gateway.sdk.Arch
1617
import com.coder.gateway.sdk.CoderCLIManager
1718
import com.coder.gateway.sdk.CoderRestClientService
19+
import com.coder.gateway.sdk.CoderSemVer
1820
import com.coder.gateway.sdk.OS
1921
import com.coder.gateway.sdk.ex.AuthenticationResponseException
2022
import com.coder.gateway.sdk.ex.TemplateResponseException
@@ -304,6 +306,24 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
304306
private fun loginAndLoadWorkspace(token: String) {
305307
try {
306308
coderClient.initClientSession(localWizardModel.coderURL.toURL(), token)
309+
if (!CoderSemVer.isValidVersion(coderClient.buildVersion)) {
310+
notificationBand.apply {
311+
isVisible = true
312+
icon = AllIcons.General.Warning
313+
text = CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.invalid.coder.version", coderClient.buildVersion)
314+
}
315+
} else {
316+
val coderVersion = CoderSemVer.parse(coderClient.buildVersion)
317+
val testedCoderVersion = CoderSupportedVersions.maxCoderVersion
318+
319+
if (!testedCoderVersion.isCompatibleWith(coderVersion)) {
320+
notificationBand.apply {
321+
isVisible = true
322+
icon = AllIcons.General.Warning
323+
text = CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.unsupported.coder.version", coderClient.buildVersion)
324+
}
325+
}
326+
}
307327
} catch (e: AuthenticationResponseException) {
308328
logger.error("Could not authenticate on ${localWizardModel.coderURL}. Reason $e")
309329
return

src/main/resources/messages/CoderGatewayBundle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ gateway.connector.view.coder.workspaces.start.text=Start Workspace
1515
gateway.connector.view.coder.workspaces.stop.text=Stop Workspace
1616
gateway.connector.view.coder.workspaces.update.text=Update Workspace Template
1717
gateway.connector.view.coder.workspaces.unsupported.os.info=Gateway supports only Linux machines. Support for macOS and Windows is planned.
18+
gateway.connector.view.coder.workspaces.invalid.coder.version=Could not parse Coder version {0}. Coder Gateway plugin might not be compatible with this version.
19+
gateway.connector.view.coder.workspaces.unsupported.coder.version=Coder version {0} might not be compatible with this plugin version.
1820
gateway.connector.view.coder.remoteproject.loading.text=Retrieving products...
1921
gateway.connector.view.coder.remoteproject.ide.error.text=Could not retrieve any IDE for workspace {0} because an error was encountered
2022
gateway.connector.view.coder.remoteproject.next.text=Start IDE and connect
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
maxCoderVersion=0.12

0 commit comments

Comments
 (0)