Skip to content

Commit

Permalink
feat(verif): in delivery config, use "image" instead of "repository/t…
Browse files Browse the repository at this point in the history
…ag" (#1819)

* feat(verif): "image" instead of "repository.tag"

# Problem

Previously, the container image specified in a `verifyWith` section was identified by "repository" and "tag" fields, for example:

```
verifyWith:
- type: test-container
  repository: acme/mytests
  tag: stable
  ...
```

However, `repository` is a confusing name, as everybody calls this an `image`. In addition, the convention with specifying images is to add the tag afterwards as a comment.

# Proposed solution

Enable users to specify the image name and tag using a field named `image`.

```
verifyWith:
- type: test-container
  image: acme/mytests:stable
```

## Deprecating the older syntax

Note: to avoid breaking existing apps, the original syntax (repository/tag) still works, the code supports both for now.  However, this older syntax should be considered deprecated, and we are going to remove it once we track down all apps that are using it and convert them over.

## Code changes

### TestContainerVerification

TestContainerVerification is the class that is deserialized with the `verifyWith` entry in the delivery config.  It now takes a nullable `image` field, which will become non-nullable in a future PR. . The `repository` and `tag` fields are now also nullable. Those will go away in a future PR.

### ContainerJobConfig

ContainerJobConfig now has an imageId property that is responsible for identifying whether the newer (image) field is in use or the old (repository/tag) is in use, and using this info to compute the correct `imageId` field that ultimately gets passed to orca.

This PR also removes reference to the `digest` field in ContainerJobConfig, which we never used.

* fix(pr): refactor based on feedback

* fix(pr): zap whitespace

* fix(pr): disable flakey test

* fix(pr): zap whitespace
  • Loading branch information
lorin committed Feb 23, 2021
1 parent 390a1ad commit dac4630
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ internal class MemoryCloudDriverCacheTest {
verify(exactly = 1) { cloudDriver.getCertificates() }
}

// the sleep of 1 ms is not fixing this test, disabling it until we can investigate further.
@Disabled
@Test
fun `all certs are cached at once when requested by name`() {
every { cloudDriver.getCertificates() } returns certificates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,41 @@ import com.netflix.spinnaker.keel.api.Verification
import com.netflix.spinnaker.keel.api.titus.TitusServerGroup.Location

data class TestContainerVerification(
val repository: String,
val tag: String = "latest",
/**
* Image name with optional tag, e.g.
*
* "acme/widget"
* "acme/widget:stable"
*
* This will become non-nullable once repository/tag are removed
*/
val image: String? = null,

@Deprecated("replaced by image field")
val repository: String? = null,

@Deprecated("replaced by image field")
val tag: String? = "latest",

val location: Location,
val application: String? = null
) : Verification {
override val type = TYPE
override val id = "$repository:$tag"
override val id = imageId

/**
* Determine imageId depending on the newer field (image) or the deprecated fields (repository, tag)
*/
val imageId : String
get() =
when {
image != null && image.contains(":") -> image
image != null && !image.contains(":") -> "${image}:latest"

repository != null && tag != null -> "${repository}:${tag}"
repository != null && tag == null -> "${repository}:latest"
else -> error("no container image specified")
}

companion object {
const val TYPE = "test-container"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ const val RUN_JOB_TYPE: String = "runJob"
*/
data class ContainerJobConfig(
/**
* Repository name associated with the container, e.g.: "acme/widget"
* image name, with optional tag. Examples:
*
* acme/widget
* acme/widget:latest
*/
val repository: String,
val tag: String? = "latest",
val digest: String? = null,
val image: String,
val application: String,
val location: TitusServerGroup.Location,
val resources: TitusServerGroup.Resources = TitusServerGroup.Resources(
Expand All @@ -39,21 +40,11 @@ data class ContainerJobConfig(
val waitForCompletion: Boolean = true
) {
init {
require((tag == null) xor (digest == null)) {
"One, and only one, of digest or tag must be supplied"
}
require(retries >= 0) {
"Retries must be positive or zero"
}
}

/**
* ID that identifies an image, e.g.:
*
* acme/widget:latest
* acme/widget:sha256:780f11bfc03495da29f9e2d25bf55123330715fb494ac27f45c96f808fd2d4c5
*/
val imageId: String = "$repository:${tag ?: digest}"
val cloudProvider: String = "titus"
val cloudProviderType: String = "aws"
}
Expand Down Expand Up @@ -95,7 +86,7 @@ fun ContainerJobConfig.createRunJobStage() =
"iamProfile" to iamInstanceProfile,
"region" to location.region,
"capacityGroup" to capacityGroup,
"imageId" to imageId,
"imageId" to image,
"entryPoint" to entrypoint
),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,16 @@ class TestContainerVerificationEvaluator(
taskLauncher.submitJob(
type = VERIFICATION,
subject = "container integration test for ${context.deliveryConfig.application}.${context.environmentName}",
description = "Verifying ${context.version} in environment ${context.environmentName} with test container ${verification.repository}:${verification.tag}",
description = "Verifying ${context.version} in environment ${context.environmentName} with test container ${verification.imageId}",
user = context.deliveryConfig.serviceAccount,
application = context.deliveryConfig.application,
notifications = emptySet(),
stages = listOf(
ContainerJobConfig(
application = verification.application ?: context.deliveryConfig.application,
location = verification.location,
repository = verification.repository,
credentials = verification.location.account,
tag = verification.tag,
digest = null
image = verification.imageId,
).createRunJobStage()
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,17 @@ internal class TestContainerVerificationEvaluatorTests {
artifactReference = "fnord",
version = "1.1"
)

private val app = "fnord-test-app"
private val loc = Location(
account = "titustestvpc",
region = "ap-south-1"
)

private val verification = TestContainerVerification(
repository = "illuminati/fnord",
location = Location(
account = "titustestvpc",
region = "ap-south-1"
),
application = "fnord-test-app"
location = loc,
application = app
)

@Test
Expand Down Expand Up @@ -82,6 +86,59 @@ internal class TestContainerVerificationEvaluatorTests {
}
}

private fun verifyImageId(expectedImageId : String) {
verify {
taskLauncher.submitJob(
type = VERIFICATION,
user = any(),
application = any(),
notifications = any(),
subject = any(),
description = any(),
correlationId = any(),
stages = match {
expectedImageId == (it.first()["cluster"] as Map<String, Any>)["imageId"]
}
)
}
}

@Test
fun `image id specified by repository field and tag`() {
stubTaskLaunch()

subject.start(context, TestContainerVerification(repository="illuminati/fnord", tag="stable", location=loc, application=app))

verifyImageId("illuminati/fnord:stable")
}

@Test
fun `image id specified by repository field, no tag`() {
stubTaskLaunch()

subject.start(context, TestContainerVerification(repository="illuminati/fnord", location=loc, application=app))

verifyImageId("illuminati/fnord:latest")
}

@Test
fun `image id specified by image field and tag`() {
stubTaskLaunch()

subject.start(context, TestContainerVerification(image="acme/rollerskates:rocket", location=loc, application=app))

verifyImageId("acme/rollerskates:rocket")
}

@Test
fun `image id specified by image field, no tag`() {
stubTaskLaunch()

subject.start(context, TestContainerVerification(image="acme/rollerskates", location=loc, application=app))

verifyImageId("acme/rollerskates:latest")
}

@Test
fun `container job runs with verification's application`() {
val job = captureTaskLaunch()
Expand Down

0 comments on commit dac4630

Please sign in to comment.