de.gesellix.docker.client.stack.ManageStackClient.groovy Maven / Gradle / Ivy
package de.gesellix.docker.client.stack
import de.gesellix.docker.client.authentication.ManageAuthentication
import de.gesellix.docker.client.config.ManageConfig
import de.gesellix.docker.client.network.ManageNetwork
import de.gesellix.docker.client.node.ManageNode
import de.gesellix.docker.client.secret.ManageSecret
import de.gesellix.docker.client.service.ManageService
import de.gesellix.docker.client.stack.types.StackConfig
import de.gesellix.docker.client.stack.types.StackSecret
import de.gesellix.docker.client.system.ManageSystem
import de.gesellix.docker.client.tasks.ManageTask
import de.gesellix.docker.engine.EngineResponse
import de.gesellix.docker.remote.api.Config
import de.gesellix.docker.remote.api.ConfigSpec
import de.gesellix.docker.remote.api.Network
import de.gesellix.docker.remote.api.NetworkCreateRequest
import de.gesellix.docker.remote.api.Node
import de.gesellix.docker.remote.api.NodeState
import de.gesellix.docker.remote.api.Secret
import de.gesellix.docker.remote.api.SecretSpec
import de.gesellix.docker.remote.api.Service
import de.gesellix.docker.remote.api.ServiceCreateResponse
import de.gesellix.docker.remote.api.ServiceSpec
import de.gesellix.docker.remote.api.ServiceUpdateResponse
import de.gesellix.docker.remote.api.Task
import de.gesellix.docker.remote.api.TaskSpecContainerSpecConfigs
import de.gesellix.docker.remote.api.TaskSpecContainerSpecSecrets
import de.gesellix.docker.remote.api.TaskState
import groovy.util.logging.Slf4j
@Slf4j
class ManageStackClient implements ManageStack {
private ManageService manageService
private ManageTask manageTask
private ManageNode manageNode
private ManageNetwork manageNetwork
private ManageSecret manageSecret
private ManageConfig manageConfig
private ManageSystem manageSystem
private ManageAuthentication manageAuthentication
ManageStackClient(
ManageService manageService,
ManageTask manageTask,
ManageNode manageNode,
ManageNetwork manageNetwork,
ManageSecret manageSecret,
ManageConfig manageConfig,
ManageSystem manageSystem,
ManageAuthentication manageAuthentication) {
this.manageService = manageService
this.manageTask = manageTask
this.manageNode = manageNode
this.manageNetwork = manageNetwork
this.manageSecret = manageSecret
this.manageConfig = manageConfig
this.manageSystem = manageSystem
this.manageAuthentication = manageAuthentication
}
@Override
Collection lsStacks() {
log.info("docker stack ls")
Map stacksByName = [:]
EngineResponse services = manageService.services([filters: [label: [(LabelNamespace): true]]])
services.content?.each { Service service ->
String stackName = service.spec.labels[(LabelNamespace)]
if (!stacksByName[(stackName)]) {
stacksByName[(stackName)] = new Stack(name: stackName, services: 0)
}
stacksByName[(stackName)].services++
}
return stacksByName.values()
}
@Override
void stackDeploy(String namespace, DeployStackConfig config, DeployStackOptions options) {
log.info("docker stack deploy")
checkDaemonIsSwarmManager()
if (options.pruneServices) {
Set serviceNames = config.services.keySet()
pruneServices(namespace, serviceNames)
}
createNetworks(namespace, config.networks)
Map changedSecrets = createSecrets(namespace, config.secrets)
Map changedConfigs = createConfigs(namespace, config.configs)
config.services.each { Map.Entry service ->
List containerSpecSecrets = service.value.taskTemplate?.containerSpec?.secrets
if (containerSpecSecrets) {
changedSecrets.each { String name, String secretId ->
int index = containerSpecSecrets.findIndexOf { TaskSpecContainerSpecSecrets spec -> spec.secretName == name }
if (index >= 0) {
containerSpecSecrets.set(index, new TaskSpecContainerSpecSecrets(
containerSpecSecrets.get(index).file,
secretId,
name
))
}
}
}
List containerSpecConfigs = service.value.taskTemplate?.containerSpec?.configs
if (containerSpecConfigs) {
changedConfigs.each { String name, String configId ->
int index = containerSpecConfigs.findIndexOf { TaskSpecContainerSpecConfigs spec -> spec.configName == name }
if (index >= 0) {
containerSpecConfigs.set(index, new TaskSpecContainerSpecConfigs(
containerSpecConfigs.get(index).file,
containerSpecConfigs.get(index).runtime,
configId,
name
))
}
}
}
}
createOrUpdateServices(namespace, config.services, options.sendRegistryAuth)
}
void createNetworks(String namespace, Map networks) {
EngineResponse> existingNetworks = manageNetwork.networks([
filters: [
label: [("${LabelNamespace}=${namespace}" as String): true]]])
List existingNetworkNames = []
existingNetworks.content.each { Network network ->
existingNetworkNames << network.name
}
networks.each { String name, NetworkCreateRequest network ->
name = "${namespace}_${name}" as String
if (!existingNetworkNames.contains(name)) {
log.info("create network $name: $network")
network.name = name
if (!network.labels) {
network.labels = [:]
}
network.labels[(LabelNamespace)] = namespace
manageNetwork.createNetwork(network)
}
}
}
Map createSecrets(String namespace, Map secrets) {
return secrets.collectEntries { String name, StackSecret secret ->
List knownSecrets = manageSecret.secrets([filters: [name: [secret.name]]]).content
log.debug("known: $knownSecrets")
if (!secret.labels) {
secret.labels = [:]
}
secret.labels[(LabelNamespace)] = namespace
EngineResponse response
if (knownSecrets.empty) {
log.info("create secret ${secret.name}: $secret")
response = manageSecret.createSecret(secret.name, secret.data, secret.labels)
}
else {
if (knownSecrets.size() != 1) {
throw new IllegalStateException("ambiguous secret name '${secret.name}'")
}
def knownSecret = knownSecrets.first()
log.info("update secret ${secret.name}: $secret")
response = manageSecret.updateSecret(
knownSecret.ID,
knownSecret.version.index,
new SecretSpec(
secret.name,
secret.labels,
new String(secret.data),
secret.driver,
secret.templating))
}
return [(secret.name): response.content.id]
}
}
Map createConfigs(String namespace, Map configs) {
return configs.collectEntries { String name, StackConfig config ->
List knownConfigs = manageConfig.configs([filters: [name: [config.name]]]).content
log.debug("known: $knownConfigs")
if (!config.labels) {
config.labels = [:]
}
config.labels[(LabelNamespace)] = namespace
EngineResponse response
if (knownConfigs.empty) {
log.info("create config ${config.name}: $config")
response = manageConfig.createConfig(config.name, config.data, config.labels)
}
else {
if (knownConfigs.size() != 1) {
throw new IllegalStateException("ambiguous config name '${config.name}'")
}
Config knownConfig = knownConfigs.first()
log.info("update config ${config.name}: $config")
response = manageConfig.updateConfig(
knownConfig.ID,
knownConfig.version.index,
new ConfigSpec(
config.name,
config.labels,
new String(config.data),
config.templating))
}
return [(config.name): response.content.id]
}
}
void pruneServices(String namespace, Collection services) {
// Descope returns the name without the namespace prefix
Closure descope = { String name ->
return name.substring("${namespace}_".length())
}
EngineResponse> oldServices = stackServices(namespace)
Collection pruneServices = oldServices.content.findResults { Service service ->
return services.contains(descope(service.spec.name)) ? null : service
}
pruneServices.each { Service service ->
manageService.rmService(service.ID)
}
}
void createOrUpdateServices(String namespace, Map services, boolean sendRegistryAuth) {
Map existingServicesByName = [:]
EngineResponse> existingServices = stackServices(namespace)
existingServices.content.each { Service service ->
existingServicesByName[service.spec.name] = service
}
services.each { String internalName, ServiceSpec serviceSpec ->
String name = "${namespace}_${internalName}"
serviceSpec.name = serviceSpec.name ?: name
if (!serviceSpec.labels) {
serviceSpec.labels = [:]
}
serviceSpec.labels[(LabelNamespace)] = namespace
String encodedAuth = ""
if (sendRegistryAuth) {
// Retrieve encoded auth token from the image reference
String image = serviceSpec.taskTemplate.containerSpec.image
encodedAuth = manageAuthentication.retrieveEncodedAuthTokenForImage(image)
}
Service service = existingServicesByName[name]
if (service) {
log.info("Updating service ${name} (id ${service.ID}): ${serviceSpec}")
Map updateOptions = [:]
if (sendRegistryAuth) {
updateOptions.EncodedRegistryAuth = encodedAuth
}
EngineResponse response = manageService.updateService(
service.ID,
service.version.index,
serviceSpec,
null,
sendRegistryAuth ? encodedAuth : null)
response.content.warnings.each { String warning ->
log.warn(warning)
}
}
else {
log.info("Creating service ${name}: ${serviceSpec}")
Map createOptions = [:]
if (sendRegistryAuth) {
createOptions.EncodedRegistryAuth = encodedAuth
}
EngineResponse response = manageService.createService(serviceSpec, sendRegistryAuth ? encodedAuth : null)
}
}
}
// checkDaemonIsSwarmManager does an Info API call to verify that the daemon is
// a swarm manager. This is necessary because we must create networks before we
// create services, but the API call for creating a network does not return a
// proper status code when it can't create a network in the "global" scope.
void checkDaemonIsSwarmManager() {
if (!manageSystem.info()?.content?.swarm?.controlAvailable) {
throw new IllegalStateException("This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again.")
}
}
@Override
EngineResponse> stackPs(String namespace, Map filters = [:]) {
log.info("docker stack ps")
String namespaceFilter = "${LabelNamespace}=${namespace}"
Map actualFilters = filters ?: [:]
if (actualFilters.label) {
actualFilters.label[(namespaceFilter)] = true
}
else {
actualFilters['label'] = [(namespaceFilter): true]
}
EngineResponse> tasks = manageTask.tasks([filters: actualFilters])
return tasks
}
@Override
void stackRm(String namespace) {
log.info("docker stack rm")
String namespaceFilter = "${LabelNamespace}=${namespace}"
EngineResponse> services = manageService.services([filters: [label: [(namespaceFilter): true]]])
EngineResponse> networks = manageNetwork.networks([filters: [label: [(namespaceFilter): true]]])
EngineResponse> secrets = manageSecret.secrets([filters: [label: [(namespaceFilter): true]]])
EngineResponse> configs = manageConfig.configs([filters: [label: [(namespaceFilter): true]]])
services.content.each { Service service ->
manageService.rmService(service.ID)
}
networks.content.each { Network network ->
manageNetwork.rmNetwork(network.id)
}
secrets.content.each { Secret secret ->
manageSecret.rmSecret(secret.ID)
}
configs.content.each { Config config ->
manageConfig.rmConfig(config.ID)
}
}
@Override
EngineResponse> stackServices(String namespace, Map filters = [:]) {
log.info("docker stack services")
String namespaceFilter = "${LabelNamespace}=${namespace}"
Map actualFilters = filters ?: [:]
if (actualFilters.label) {
actualFilters.label[(namespaceFilter)] = true
}
else {
actualFilters['label'] = [(namespaceFilter): true]
}
EngineResponse> services = manageService.services([filters: actualFilters])
// def infoByServiceId = getInfoByServiceId(services)
return services
}
Map getInfoByServiceId(EngineResponse> services) {
EngineResponse> nodes = manageNode.nodes()
List activeNodes = nodes.content.findResults { Node node ->
node.status.state != NodeState.Down ? node.ID : null
}
Map running = [:]
Map tasksNoShutdown = [:]
Map serviceFilter = [service: [:]]
services.content.each { Service service ->
serviceFilter.service[(service.ID as String)] = true
}
EngineResponse> tasks = manageTask.tasks([filters: serviceFilter])
tasks.content.each { Task task ->
if (task.desiredState != TaskState.Shutdown) {
if (!tasksNoShutdown[task.serviceID]) {
tasksNoShutdown[task.serviceID] = 0
}
tasksNoShutdown[task.serviceID]++
}
if (activeNodes.contains(task.nodeID) && task.status.state == TaskState.Running) {
if (!running[task.serviceID]) {
running[task.serviceID] = 0
}
running[task.serviceID]++
}
}
Map infoByServiceId = [:]
services.content.each { Service service ->
if (service.spec.mode.replicated && service.spec.mode.replicated.replicas) {
infoByServiceId[service.ID] = new ServiceInfo(mode: 'replicated', replicas: "${running[service.ID as String] ?: 0}/${service.spec.mode.replicated.replicas}")
}
else if (service.spec.mode.global) {
infoByServiceId[service.ID] = new ServiceInfo(mode: 'global', replicas: "${running[service.ID as String] ?: 0}}/${tasksNoShutdown[service.ID as String]}")
}
}
return infoByServiceId
}
static class ServiceInfo {
String mode
String replicas
@Override
String toString() {
"$mode, $replicas"
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy