Skip to content

Commit dfed373

Browse files
committed
[PROD-13446] Solution.sdkVersion is now retrieved from the docker image
The field is now read only and is 'computed' at runtime from a label on the docker image. This requires the solution image heving been built with a SDK version 11.3.0 or newer, but it should be relatively easy to manually add the required label ('com.cosmotech.sdk-version') with older SDK version
1 parent 0d47207 commit dfed373

File tree

11 files changed

+88
-43
lines changed

11 files changed

+88
-43
lines changed

api/src/integrationTest/kotlin/com/cosmotech/api/home/ControllerTestUtils.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ class ControllerTestUtils {
8585
parameters: MutableList<RunTemplateParameter> = mutableListOf(),
8686
parameterGroups: MutableList<RunTemplateParameterGroup> = mutableListOf(),
8787
runTemplates: MutableList<RunTemplate> = mutableListOf(),
88-
sdkVersion: String = "",
8988
url: String = "",
9089
security: SolutionSecurity? = null
9190
): SolutionCreateRequest {
@@ -101,7 +100,6 @@ class ControllerTestUtils {
101100
parameters = parameters,
102101
parameterGroups = parameterGroups,
103102
runTemplates = runTemplates,
104-
sdkVersion = sdkVersion,
105103
url = url,
106104
security = security,
107105
)
@@ -117,7 +115,6 @@ class ControllerTestUtils {
117115
description: String = "",
118116
alwaysPull: Boolean? = null,
119117
tags: MutableList<String> = mutableListOf(),
120-
sdkVersion: String = "",
121118
url: String = "",
122119
): SolutionUpdateRequest {
123120
return SolutionUpdateRequest(
@@ -129,7 +126,6 @@ class ControllerTestUtils {
129126
description = description,
130127
alwaysPull = alwaysPull,
131128
tags = tags,
132-
sdkVersion = sdkVersion,
133129
url = url)
134130
}
135131
}

api/src/integrationTest/kotlin/com/cosmotech/api/home/solution/SolutionConstants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ object SolutionConstants {
1313
const val SOLUTION_KEY = "my_solution_key"
1414
const val SOLUTION_REPOSITORY = "solution_repository"
1515
const val SOLUTION_VERSION = "1.0.0"
16+
const val SOLUTION_SDK_VERSION = "11.3.0-45678.abcdef12"
1617
const val SOLUTION_SIMULATOR = "my_simulator_name"
1718
const val NEW_USER_ID = "[email protected]"
1819
const val NEW_USER_ROLE = "editor"

api/src/integrationTest/kotlin/com/cosmotech/api/home/solution/SolutionControllerTests.kt

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license.
33
package com.cosmotech.api.home.solution
44

5+
import com.cosmotech.api.containerregistry.ContainerRegistryService
56
import com.cosmotech.api.home.Constants.PLATFORM_ADMIN_EMAIL
67
import com.cosmotech.api.home.ControllerTestBase
78
import com.cosmotech.api.home.ControllerTestUtils.OrganizationUtils.constructOrganizationCreateRequest
@@ -15,21 +16,27 @@ import com.cosmotech.api.home.organization.OrganizationConstants.NEW_USER_ROLE
1516
import com.cosmotech.api.home.solution.SolutionConstants.SOLUTION_KEY
1617
import com.cosmotech.api.home.solution.SolutionConstants.SOLUTION_NAME
1718
import com.cosmotech.api.home.solution.SolutionConstants.SOLUTION_REPOSITORY
19+
import com.cosmotech.api.home.solution.SolutionConstants.SOLUTION_SDK_VERSION
1820
import com.cosmotech.api.home.solution.SolutionConstants.SOLUTION_SIMULATOR
1921
import com.cosmotech.api.home.solution.SolutionConstants.SOLUTION_VERSION
2022
import com.cosmotech.api.rbac.ROLE_ADMIN
2123
import com.cosmotech.api.rbac.ROLE_NONE
2224
import com.cosmotech.api.rbac.ROLE_VIEWER
25+
import com.cosmotech.solution.api.SolutionApiService
2326
import com.cosmotech.solution.domain.*
27+
import io.mockk.every
28+
import io.mockk.mockk
2429
import org.json.JSONArray
2530
import org.json.JSONObject
2631
import org.junit.jupiter.api.BeforeEach
2732
import org.junit.jupiter.api.Test
33+
import org.springframework.beans.factory.annotation.Autowired
2834
import org.springframework.boot.test.context.SpringBootTest
2935
import org.springframework.http.MediaType
3036
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document
3137
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
3238
import org.springframework.test.context.ActiveProfiles
39+
import org.springframework.test.util.ReflectionTestUtils
3340
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
3441
import org.springframework.test.web.servlet.result.MockMvcResultHandlers
3542
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
@@ -39,10 +46,18 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
3946
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
4047
class SolutionControllerTests : ControllerTestBase() {
4148

49+
@Autowired lateinit var solutionApiService: SolutionApiService
4250
private lateinit var organizationId: String
4351

52+
private var containerRegistryService: ContainerRegistryService = mockk(relaxed = true)
53+
4454
@BeforeEach
4555
fun beforeEach() {
56+
ReflectionTestUtils.setField(
57+
solutionApiService, "containerRegistryService", containerRegistryService)
58+
every { containerRegistryService.getImageLabel(any(), any(), any()) } returns
59+
SOLUTION_SDK_VERSION
60+
4661
organizationId = createOrganizationAndReturnId(mvc, constructOrganizationCreateRequest())
4762
}
4863

@@ -78,6 +93,7 @@ class SolutionControllerTests : ControllerTestBase() {
7893
.andExpect(jsonPath("$[0].name").value(firstSolutionName))
7994
.andExpect(jsonPath("$[0].ownerId").value(PLATFORM_ADMIN_EMAIL))
8095
.andExpect(jsonPath("$[0].organizationId").value(organizationId))
96+
.andExpect(jsonPath("$[0].sdkVersion").value(SOLUTION_SDK_VERSION))
8197
.andExpect(jsonPath("$[0].security.default").value(ROLE_NONE))
8298
.andExpect(jsonPath("$[0].security.accessControlList[0].role").value(ROLE_ADMIN))
8399
.andExpect(jsonPath("$[0].security.accessControlList[0].id").value(PLATFORM_ADMIN_EMAIL))
@@ -86,6 +102,7 @@ class SolutionControllerTests : ControllerTestBase() {
86102
.andExpect(jsonPath("$[1].name").value(secondSolutionName))
87103
.andExpect(jsonPath("$[1].ownerId").value(PLATFORM_ADMIN_EMAIL))
88104
.andExpect(jsonPath("$[1].organizationId").value(organizationId))
105+
.andExpect(jsonPath("$[1].sdkVersion").value(SOLUTION_SDK_VERSION))
89106
.andExpect(jsonPath("$[1].security.default").value(ROLE_NONE))
90107
.andExpect(jsonPath("$[1].security.accessControlList[0].role").value(ROLE_ADMIN))
91108
.andExpect(jsonPath("$[1].security.accessControlList[0].id").value(PLATFORM_ADMIN_EMAIL))
@@ -152,7 +169,6 @@ class SolutionControllerTests : ControllerTestBase() {
152169
mutableListOf(parameterGroupId),
153170
10))
154171

155-
val sdkVersion = "this_is_the_sdk_version"
156172
val url = "this_is_the_solution_url"
157173
val security =
158174
SolutionSecurity(
@@ -174,7 +190,6 @@ class SolutionControllerTests : ControllerTestBase() {
174190
parameters,
175191
parameterGroups,
176192
runTemplates,
177-
sdkVersion,
178193
url,
179194
security)
180195

@@ -218,6 +233,7 @@ class SolutionControllerTests : ControllerTestBase() {
218233
.andExpect(jsonPath("$.url").value(url))
219234
.andExpect(jsonPath("$.tags").value(tags))
220235
.andExpect(jsonPath("$.organizationId").value(organizationId))
236+
.andExpect(jsonPath("$.sdkVersion").value(SOLUTION_SDK_VERSION))
221237
.andExpect(jsonPath("$.security.default").value(ROLE_NONE))
222238
.andExpect(jsonPath("$.security.accessControlList[0].role").value(ROLE_ADMIN))
223239
.andExpect(jsonPath("$.security.accessControlList[0].id").value(PLATFORM_ADMIN_EMAIL))
@@ -286,7 +302,6 @@ class SolutionControllerTests : ControllerTestBase() {
286302
mutableListOf(parameterGroupId),
287303
10))
288304

289-
val sdkVersion = "this_is_the_sdk_version"
290305
val url = "this_is_the_solution_url"
291306
val security =
292307
SolutionSecurity(
@@ -308,7 +323,6 @@ class SolutionControllerTests : ControllerTestBase() {
308323
parameters,
309324
parameterGroups,
310325
runTemplates,
311-
sdkVersion,
312326
url,
313327
security)
314328

@@ -361,6 +375,7 @@ class SolutionControllerTests : ControllerTestBase() {
361375
.andExpect(jsonPath("$.url").value(url))
362376
.andExpect(jsonPath("$.tags").value(tags))
363377
.andExpect(jsonPath("$.organizationId").value(organizationId))
378+
.andExpect(jsonPath("$.sdkVersion").value(SOLUTION_SDK_VERSION))
364379
.andExpect(jsonPath("$.security.default").value(ROLE_NONE))
365380
.andExpect(jsonPath("$.security.accessControlList[0].role").value(ROLE_ADMIN))
366381
.andExpect(jsonPath("$.security.accessControlList[0].id").value(PLATFORM_ADMIN_EMAIL))
@@ -394,7 +409,6 @@ class SolutionControllerTests : ControllerTestBase() {
394409

395410
val description = "this_is_a_description"
396411
val tags = mutableListOf("tag1", "tag2")
397-
val sdkVersion = "this_is_the_sdk_version"
398412
val url = "this_is_the_solution_url"
399413

400414
val solutionUpdateRequest =
@@ -407,7 +421,6 @@ class SolutionControllerTests : ControllerTestBase() {
407421
description,
408422
true,
409423
tags,
410-
sdkVersion,
411424
url)
412425

413426
mvc.perform(
@@ -426,6 +439,7 @@ class SolutionControllerTests : ControllerTestBase() {
426439
.andExpect(jsonPath("$.url").value(url))
427440
.andExpect(jsonPath("$.tags").value(tags))
428441
.andExpect(jsonPath("$.organizationId").value(organizationId))
442+
.andExpect(jsonPath("$.sdkVersion").value(SOLUTION_SDK_VERSION))
429443
.andExpect(jsonPath("$.security.default").value(ROLE_NONE))
430444
.andExpect(jsonPath("$.security.accessControlList[0].role").value(ROLE_ADMIN))
431445
.andExpect(jsonPath("$.security.accessControlList[0].id").value(PLATFORM_ADMIN_EMAIL))

build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ val springWebVersion = "6.2.1"
6161

6262
// Implementation
6363
val kotlinJvmTarget = 21
64-
val cosmotechApiCommonVersion = "2.1.0-SNAPSHOT"
64+
val cosmotechApiCommonVersion = "2.1.1-SNAPSHOT"
6565
val jedisVersion = "4.4.6"
6666
val springOauthVersion = "6.4.2"
6767
val redisOmSpringVersion = "0.9.7"
@@ -133,6 +133,7 @@ allprojects {
133133
configurations { all { resolutionStrategy { force("com.redis.om:redis-om-spring:0.9.1") } } }
134134

135135
repositories {
136+
mavenLocal()
136137
maven {
137138
name = "GitHubPackages"
138139
url = uri("https://maven.pkg.github.com/Cosmo-Tech/cosmotech-api-common")

doc/Models/Solution.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
| **csmSimulator** | **String** | The main Cosmo Tech simulator name used in standard Run Template | [default to null] |
1414
| **version** | **String** | The Solution version MAJOR.MINOR.PATCH. Must be aligned with an existing repository tag | [default to null] |
1515
| **ownerId** | **String** | The User id which owns this Solution | [default to null] |
16-
| **sdkVersion** | **String** | The MAJOR.MINOR version used to build this solution | [optional] [default to null] |
16+
| **sdkVersion** | **String** | The full SDK version used to build this solution, if available | [optional] [default to null] |
1717
| **url** | **String** | An optional URL link to solution page | [optional] [default to null] |
1818
| **tags** | **List** | The list of tags | [optional] [default to null] |
1919
| **parameters** | [**List**](RunTemplateParameter.md) | The list of Run Template Parameters | [default to null] |

doc/Models/SolutionCreateRequest.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
| **parameters** | [**List**](RunTemplateParameter.md) | The list of Run Template Parameters | [optional] [default to []] |
1515
| **parameterGroups** | [**List**](RunTemplateParameterGroup.md) | The list of parameters groups for the Run Templates | [optional] [default to []] |
1616
| **runTemplates** | [**List**](RunTemplate.md) | List of Run Templates | [optional] [default to []] |
17-
| **sdkVersion** | **String** | The MAJOR.MINOR version used to build this solution | [optional] [default to null] |
1817
| **url** | **String** | An optional URL link to solution page | [optional] [default to null] |
1918
| **security** | [**SolutionSecurity**](SolutionSecurity.md) | | [optional] [default to null] |
2019

doc/Models/SolutionUpdateRequest.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
| **alwaysPull** | **Boolean** | Set to true if the runtemplate wants to always pull the image | [optional] [default to false] |
1111
| **csmSimulator** | **String** | The main Cosmo Tech simulator name used in standard Run Template | [optional] [default to null] |
1212
| **version** | **String** | The Solution version MAJOR.MINOR.PATCH. Must be aligned with an existing repository tag | [optional] [default to null] |
13-
| **sdkVersion** | **String** | The MAJOR.MINOR version used to build this solution | [optional] [default to null] |
1413
| **url** | **String** | An optional URL link to solution page | [optional] [default to null] |
1514
| **tags** | **List** | The list of tags | [optional] [default to null] |
1615

openapi/plantuml/schemas.plantuml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,6 @@ entity SolutionCreateRequest {
470470
parameters: List<RunTemplateParameter>
471471
parameterGroups: List<RunTemplateParameterGroup>
472472
runTemplates: List<RunTemplate>
473-
sdkVersion: String
474473
url: String
475474
security: SolutionSecurity
476475
}
@@ -492,7 +491,6 @@ entity SolutionUpdateRequest {
492491
alwaysPull: Boolean
493492
csmSimulator: String
494493
version: String
495-
sdkVersion: String
496494
url: String
497495
tags: List<String>
498496
}

solution/src/integrationTest/kotlin/com/cosmotech/solution/service/SolutionServiceIntegrationTest.kt

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package com.cosmotech.solution.service
44

55
import com.cosmotech.api.config.CsmPlatformProperties
6+
import com.cosmotech.api.containerregistry.ContainerRegistryService
67
import com.cosmotech.api.exceptions.CsmAccessForbiddenException
78
import com.cosmotech.api.exceptions.CsmResourceNotFoundException
89
import com.cosmotech.api.rbac.ROLE_ADMIN
@@ -32,13 +33,17 @@ import com.cosmotech.solution.domain.SolutionUpdateRequest
3233
import com.redis.om.spring.RediSearchIndexer
3334
import io.mockk.every
3435
import io.mockk.junit5.MockKExtension
36+
import io.mockk.mockk
3537
import io.mockk.mockkStatic
3638
import java.util.*
3739
import kotlin.test.assertEquals
40+
import kotlin.test.assertFalse
3841
import kotlin.test.assertNotNull
3942
import kotlin.test.assertTrue
4043
import org.junit.jupiter.api.BeforeEach
44+
import org.junit.jupiter.api.DynamicTest
4145
import org.junit.jupiter.api.Test
46+
import org.junit.jupiter.api.TestFactory
4247
import org.junit.jupiter.api.assertThrows
4348
import org.junit.jupiter.api.extension.ExtendWith
4449
import org.junit.runner.RunWith
@@ -48,6 +53,7 @@ import org.springframework.boot.test.context.SpringBootTest
4853
import org.springframework.test.context.ActiveProfiles
4954
import org.springframework.test.context.junit.jupiter.SpringExtension
5055
import org.springframework.test.context.junit4.SpringRunner
56+
import org.springframework.test.util.ReflectionTestUtils
5157

5258
const val CONNECTED_ADMIN_USER = "[email protected]"
5359
const val CONNECTED_READER_USER = "[email protected]"
@@ -66,6 +72,8 @@ class SolutionServiceIntegrationTest : CsmRedisTestBase() {
6672
@Autowired lateinit var solutionApiService: SolutionApiServiceInterface
6773
@Autowired lateinit var csmPlatformProperties: CsmPlatformProperties
6874

75+
private var containerRegistryService: ContainerRegistryService = mockk(relaxed = true)
76+
6977
lateinit var organization: OrganizationCreateRequest
7078
lateinit var solution: SolutionCreateRequest
7179

@@ -75,9 +83,12 @@ class SolutionServiceIntegrationTest : CsmRedisTestBase() {
7583
@BeforeEach
7684
fun setUp() {
7785
mockkStatic("com.cosmotech.api.utils.SecurityUtilsKt")
86+
ReflectionTestUtils.setField(
87+
solutionApiService, "containerRegistryService", containerRegistryService)
7888
every { getCurrentAccountIdentifier(any()) } returns CONNECTED_ADMIN_USER
7989
every { getCurrentAuthenticatedUserName(csmPlatformProperties) } returns "test.user"
8090
every { getCurrentAuthenticatedRoles(any()) } returns listOf("user")
91+
every { containerRegistryService.getImageLabel(any(), any(), any()) } returns null
8192

8293
rediSearchIndexer.createIndexFor(Organization::class.java)
8394
rediSearchIndexer.createIndexFor(Solution::class.java)
@@ -156,6 +167,41 @@ class SolutionServiceIntegrationTest : CsmRedisTestBase() {
156167
assertTrue(solutionsFoundAfterDelete.size == 1)
157168
}
158169

170+
@TestFactory
171+
fun `sdkVersion on Solution CRUD`() =
172+
mapOf(
173+
"no image label" to null,
174+
"with image label" to "11.3.0-rc1-456.abc",
175+
)
176+
.map { (name, imageLabel) ->
177+
DynamicTest.dynamicTest("sdkVersion on Solution CRUD: $name") {
178+
every { containerRegistryService.getImageLabel(any(), any(), any()) } returns
179+
imageLabel
180+
181+
val solutionCreated =
182+
solutionApiService.createSolution(
183+
organizationSaved.id, makeSolution(organizationSaved.id))
184+
assertEquals(imageLabel, solutionCreated.sdkVersion)
185+
186+
assertEquals(
187+
imageLabel,
188+
solutionApiService
189+
.getSolution(organizationSaved.id, solutionCreated.id)
190+
.sdkVersion)
191+
192+
val solutions = solutionApiService.listSolutions(organizationSaved.id, null, null)
193+
assertFalse(solutions.isEmpty())
194+
solutions.forEach { assertEquals(imageLabel, it.sdkVersion) }
195+
196+
val solutionUpdateRequest = SolutionUpdateRequest(name = "New name")
197+
val solutionUpdated =
198+
solutionApiService.updateSolution(
199+
organizationSaved.id, solutionCreated.id, solutionUpdateRequest)
200+
assertEquals(solutionUpdateRequest.name, solutionUpdated.name)
201+
assertEquals(imageLabel, solutionUpdated.sdkVersion)
202+
}
203+
}
204+
159205
@Test
160206
fun `can delete solution when user is not the owner and is Platform Admin`() {
161207
logger.info("Register new solution...")
@@ -521,7 +567,6 @@ class SolutionServiceIntegrationTest : CsmRedisTestBase() {
521567
csmSimulator = "simulator",
522568
repository = "repository",
523569
alwaysPull = true,
524-
sdkVersion = "1.0.0",
525570
security =
526571
SolutionSecurity(
527572
default = ROLE_ADMIN,
@@ -538,7 +583,6 @@ class SolutionServiceIntegrationTest : CsmRedisTestBase() {
538583
parameterGroups = solutionToCreate.parameterGroups,
539584
parameters = solutionToCreate.parameters,
540585
security = solutionToCreate.security,
541-
sdkVersion = solutionToCreate.sdkVersion,
542586
csmSimulator = solutionToCreate.csmSimulator,
543587
url = solutionToCreate.url,
544588
alwaysPull = solutionToCreate.alwaysPull,
@@ -570,7 +614,6 @@ class SolutionServiceIntegrationTest : CsmRedisTestBase() {
570614
csmSimulator = "simulator",
571615
repository = "repository",
572616
alwaysPull = true,
573-
sdkVersion = "1.0.0",
574617
security =
575618
SolutionSecurity(
576619
default = ROLE_ADMIN,
@@ -599,7 +642,6 @@ class SolutionServiceIntegrationTest : CsmRedisTestBase() {
599642
tags = mutableListOf("newTag1", "newTag2"),
600643
alwaysPull = false,
601644
repository = "new_repo",
602-
sdkVersion = "2.0.0",
603645
csmSimulator = "new_simulator",
604646
url = "new_url",
605647
version = "2.0.0")
@@ -612,7 +654,6 @@ class SolutionServiceIntegrationTest : CsmRedisTestBase() {
612654
description = solutionUpdateRequest.description,
613655
tags = solutionUpdateRequest.tags,
614656
alwaysPull = solutionUpdateRequest.alwaysPull,
615-
sdkVersion = solutionUpdateRequest.sdkVersion,
616657
csmSimulator = solutionUpdateRequest.csmSimulator!!,
617658
url = solutionUpdateRequest.url,
618659
repository = solutionUpdateRequest.repository!!,

0 commit comments

Comments
 (0)