Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(dockerMetadata): adding commit id, build number for docker images #853

Merged
merged 6 commits into from
Sep 8, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,22 @@ class DockerMonitor extends CommonPollingMonitor<ImageDelta, DockerPollingDelta>

if (keelService.isPresent()) {
String imageReference = image.repository + ":" + image.tag
Map <String, String> metadata = [fullname: imageReference, registry: image.account, tag: image.tag]
if (image.newtLabels != null) {
Optional.ofNullable(image.newtLabels.get("buildNumber"))
.ifPresent({ buildNumber -> metadata.put("buildNumber", buildNumber.toString()) })
Optional.ofNullable(image.newtLabels.get("commitId"))
.ifPresent({ commitId -> metadata.put("commitId", commitId.toString()) })
}

Artifact artifact = Artifact.builder()
.type("DOCKER")
.customKind(false)
.name(image.repository)
.version(image.tag)
.location(image.account)
.reference(imageId)
.metadata([fullname: imageReference, registry: image.account, tag: image.tag],)
.metadata(metadata)
.provenance(image.registry)
.build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ class TaggedImage {
String registry
String repository
String tag
Map<String,String> newtLabels
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,172 +26,199 @@ import com.netflix.spinnaker.igor.history.EchoService
import com.netflix.spinnaker.igor.history.model.DockerEvent
import com.netflix.spinnaker.igor.keel.KeelService
import com.netflix.spinnaker.igor.polling.LockService
import com.netflix.spinnaker.kork.discovery.DiscoveryStatusChangeEvent
import com.netflix.spinnaker.kork.discovery.DiscoveryStatusListener
import com.netflix.spinnaker.kork.discovery.InstanceStatus
import com.netflix.spinnaker.kork.discovery.RemoteStatusChangedEvent
import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService
import org.springframework.scheduling.TaskScheduler
import spock.lang.Specification
import spock.lang.Unroll

class DockerMonitorSpec extends Specification {

def properties = new IgorConfigurationProperties()
def registry = new NoopRegistry()
def dynamicConfig = new DynamicConfigService.NoopDynamicConfig()
DiscoveryStatusListener discoveryStatusListener = new DiscoveryStatusListener(true)
Optional<LockService> lockService = Optional.empty()
def dockerRegistryCache = Mock(DockerRegistryCache)
def dockerRegistryAccounts = Mock(DockerRegistryAccounts)
def echoService = Mock(EchoService)
def keelService = Mock(KeelService)
def dockerRegistryProperties = new DockerRegistryProperties(enabled: true, itemUpperThreshold: 5)

@Unroll
void 'should only publish events if account has been indexed previously'() {
given:
def taggedImage = new TaggedImage(
tag: "tag",
account: "account",
registry: "registry",
repository: "repository",
digest: "digest"
)

when:
new DockerMonitor(
properties,
registry,
dynamicConfig,
discoveryStatusListener,
lockService,
dockerRegistryCache,
dockerRegistryAccounts,
Optional.of(echoService),
Optional.of(keelService),
dockerRegistryProperties,
Mock(TaskScheduler))
.postEvent(cachedImages, taggedImage, "imageId")

then:
echoServiceCallCount * echoService.postEvent({ DockerEvent event ->
assert event.content.tag == taggedImage.tag
assert event.content.account == taggedImage.account
assert event.content.registry == taggedImage.registry
assert event.content.repository == taggedImage.repository
assert event.content.digest == taggedImage.digest
return true
})

when: "should short circuit if `echoService` is not available"
createSubject().postEvent(["imageId"] as Set, taggedImage, "imageId")

then:
notThrown(NullPointerException)

where:
cachedImages || echoServiceCallCount
null || 0
[] as Set || 0
["job1"] as Set || 1

}

void 'should include decorated artifact in the payload'() {
given:
def taggedImage = new TaggedImage(
tag: "tag",
account: "account",
registry: "registry",
repository: "repository",
digest: "digest"
)

when:
createSubject().postEvent(["job1"] as Set, taggedImage, "imageId")

then:
1 * echoService.postEvent({ DockerEvent event ->
assert event.artifact.version == taggedImage.tag
assert event.artifact.name == taggedImage.repository
assert event.artifact.type == "docker"
assert event.artifact.reference == "registry/repository:tag"
assert event.artifact.metadata.registry == taggedImage.registry
return true
})
1 * keelService.sendArtifactEvent({ Map event ->
def artifacts = event.payload.artifacts
assert artifacts.size() == 1
assert artifacts[0].name == "repository"
assert artifacts[0].type == "DOCKER"
return true
})
}

@Unroll
void "should update cache if image is not already cached"() {
given:
def subject = createSubject()
Set<String> cachedImages = [
'prefix:dockerRegistry:v2:account:registry:tag',
'prefix:dockerRegistry:v2:account:anotherregistry:tag',
]

when:
def taggedImage = new TaggedImage(tag: tag, account: "account", registry: "registry", repository: "repository", digest: digest)
def result = subject.getUpdateType(cachedImages, keyFromTaggedImage(taggedImage), taggedImage, trackDigest)

then:
dockerRegistryCache.getLastDigest(_, _, _) >> cachedDigest
assert result.updateCache == updateCache
assert result.sendEvent == sendEvent

where:
tag | digest | cachedDigest | trackDigest || updateCache | sendEvent
"tag" | "digest" | "digest" | false || false | false
"new" | "digest" | "digest" | false || true | true
"tag" | "digest2" | "digest" | true || true | true
"tag" | null | "digest" | true || false | false
"tag" | "digest" | null | true || true | false
"tag" | null | null | true || false | false
"tag" | null | "digest" | false || false | false
"tag" | "digest" | null | false || false | false
"tag" | null | null | false || false | false
}

@Unroll
def "should retrieve itemUpperThreshold #upperThreshold for #partition with fallback value #fallbackThreshold from igor properties"() {
given:
def subject = createSubject()
dockerRegistryProperties.setItemUpperThreshold(fallbackThreshold)

when:
def result = subject.getPartitionUpperThreshold(partition)

then:
1 * dockerRegistryAccounts.accounts >> [
[name: 'partition1', itemUpperThreshold: 10],
[name: 'partition2', itemUpperThreshold: 20],
[name: 'partition3']
]
assert result == upperThreshold

where:
partition | fallbackThreshold || upperThreshold
'partition1' | 100 || 10
'partition1' | null || 10
'partition2' | 100 || 20
'partition3' | 100 || 100
'partition4' | 100 || 100
'partition4' | null || null
}

private DockerMonitor createSubject() {
return new DockerMonitor(properties, registry, dynamicConfig, discoveryStatusListener, lockService, dockerRegistryCache, dockerRegistryAccounts, Optional.of(echoService), Optional.of(keelService), dockerRegistryProperties, Mock(TaskScheduler))
}

private static String keyFromTaggedImage(TaggedImage taggedImage) {
return new DockerRegistryV2Key("prefix", DockerRegistryCache.ID, taggedImage.account, taggedImage.registry, taggedImage.tag).toString()
}
def properties = new IgorConfigurationProperties()
def registry = new NoopRegistry()
def dynamicConfig = new DynamicConfigService.NoopDynamicConfig()
DiscoveryStatusListener discoveryStatusListener = new DiscoveryStatusListener(true)
Optional<LockService> lockService = Optional.empty()
def dockerRegistryCache = Mock(DockerRegistryCache)
def dockerRegistryAccounts = Mock(DockerRegistryAccounts)
def echoService = Mock(EchoService)
def keelService = Mock(KeelService)
def dockerRegistryProperties = new DockerRegistryProperties(enabled: true, itemUpperThreshold: 5)

@Unroll
void 'should only publish events if account has been indexed previously'() {
given:
def taggedImage = new TaggedImage(
tag: "tag",
account: "account",
registry: "registry",
repository: "repository",
digest: "digest"
)

when:
new DockerMonitor(
properties,
registry,
dynamicConfig,
discoveryStatusListener,
lockService,
dockerRegistryCache,
dockerRegistryAccounts,
Optional.of(echoService),
Optional.of(keelService),
dockerRegistryProperties,
Mock(TaskScheduler))
.postEvent(cachedImages, taggedImage, "imageId")

then:
echoServiceCallCount * echoService.postEvent({ DockerEvent event ->
assert event.content.tag == taggedImage.tag
assert event.content.account == taggedImage.account
assert event.content.registry == taggedImage.registry
assert event.content.repository == taggedImage.repository
assert event.content.digest == taggedImage.digest
return true
})

when: "should short circuit if `echoService` is not available"
createSubject().postEvent(["imageId"] as Set, taggedImage, "imageId")

then:
notThrown(NullPointerException)

where:
cachedImages || echoServiceCallCount
null || 0
[] as Set || 0
["job1"] as Set || 1

}

void 'should include decorated artifact in the payload'() {
given:
def taggedImage = new TaggedImage(
tag: "tag",
account: "account",
registry: "registry",
repository: "repository",
digest: "digest",
newtLabels: ["buildNumber": "111", "commitId": "ab12c3"]
)

when:
createSubject().postEvent(["job1"] as Set, taggedImage, "imageId")

then:
1 * echoService.postEvent({ DockerEvent event ->
assert event.artifact.version == taggedImage.tag
assert event.artifact.name == taggedImage.repository
assert event.artifact.type == "docker"
assert event.artifact.reference == "registry/repository:tag"
assert event.artifact.metadata.registry == taggedImage.registry
return true
})
1 * keelService.sendArtifactEvent({ Map event ->
def artifacts = event.payload.artifacts
assert artifacts.size() == 1
assert artifacts[0].name == "repository"
assert artifacts[0].type == "DOCKER"
assert artifacts[0].metadata.tag == "tag"
assert artifacts[0].metadata.buildNumber == "111"
assert artifacts[0].metadata.commitId == "ab12c3"
return true
})
}

void 'should include not include build and commit details if newtLables is missing from taggedImage'() {
given:
def taggedImageWithoutMetadata = new TaggedImage(
tag: "new-tag",
account: "account",
registry: "registry",
repository: "repository",
digest: "digest"
)

when:
createSubject().postEvent(["job1"] as Set, taggedImageWithoutMetadata, "imageId")

then:
1 * keelService.sendArtifactEvent({ Map event ->
def artifacts = event.payload.artifacts
assert artifacts.size() == 1
assert artifacts[0].name == "repository"
assert artifacts[0].type == "DOCKER"
assert artifacts[0].metadata.tag == "new-tag"
assert artifacts[0].metadata.containsKey("buildNumber") == false
assert artifacts[0].metadata.containsKey("commitId") == false
return true
})
}

@Unroll
void "should update cache if image is not already cached"() {
given:
def subject = createSubject()
Set<String> cachedImages = [
'prefix:dockerRegistry:v2:account:registry:tag',
'prefix:dockerRegistry:v2:account:anotherregistry:tag',
]

when:
def taggedImage = new TaggedImage(tag: tag, account: "account", registry: "registry", repository: "repository", digest: digest)
def result = subject.getUpdateType(cachedImages, keyFromTaggedImage(taggedImage), taggedImage, trackDigest)

then:
dockerRegistryCache.getLastDigest(_, _, _) >> cachedDigest
assert result.updateCache == updateCache
assert result.sendEvent == sendEvent

where:
tag | digest | cachedDigest | trackDigest || updateCache | sendEvent
"tag" | "digest" | "digest" | false || false | false
"new" | "digest" | "digest" | false || true | true
"tag" | "digest2" | "digest" | true || true | true
"tag" | null | "digest" | true || false | false
"tag" | "digest" | null | true || true | false
"tag" | null | null | true || false | false
"tag" | null | "digest" | false || false | false
"tag" | "digest" | null | false || false | false
"tag" | null | null | false || false | false
}

@Unroll
def "should retrieve itemUpperThreshold #upperThreshold for #partition with fallback value #fallbackThreshold from igor properties"() {
given:
def subject = createSubject()
dockerRegistryProperties.setItemUpperThreshold(fallbackThreshold)

when:
def result = subject.getPartitionUpperThreshold(partition)

then:
1 * dockerRegistryAccounts.accounts >> [
[name: 'partition1', itemUpperThreshold: 10],
[name: 'partition2', itemUpperThreshold: 20],
[name: 'partition3']
]
assert result == upperThreshold

where:
partition | fallbackThreshold || upperThreshold
'partition1' | 100 || 10
'partition1' | null || 10
'partition2' | 100 || 20
'partition3' | 100 || 100
'partition4' | 100 || 100
'partition4' | null || null
}

private DockerMonitor createSubject() {
return new DockerMonitor(properties, registry, dynamicConfig, discoveryStatusListener, lockService, dockerRegistryCache, dockerRegistryAccounts, Optional.of(echoService), Optional.of(keelService), dockerRegistryProperties, Mock(TaskScheduler))
}

private static String keyFromTaggedImage(TaggedImage taggedImage) {
return new DockerRegistryV2Key("prefix", DockerRegistryCache.ID, taggedImage.account, taggedImage.registry, taggedImage.tag).toString()
}
}