All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.gesellix.docker.client.stack.ManageStackClient.groovy Maven / Gradle / Ivy

There is a newer version: 2024-09-15T19-40-00-groovy-4
Show newest version
package de.gesellix.docker.client.stack

import de.gesellix.docker.client.EngineResponseContent
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.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.ServiceCreateRequest
import de.gesellix.docker.remote.api.ServiceCreateResponse
import de.gesellix.docker.remote.api.ServiceSpec
import de.gesellix.docker.remote.api.ServiceUpdateRequest
import de.gesellix.docker.remote.api.ServiceUpdateResponse
import de.gesellix.docker.remote.api.Task
import de.gesellix.docker.remote.api.TaskSpecContainerSpecConfigsInner
import de.gesellix.docker.remote.api.TaskSpecContainerSpecSecretsInner
import de.gesellix.docker.remote.api.TaskState
import org.slf4j.Logger
import org.slf4j.LoggerFactory

class ManageStackClient implements ManageStack {

  private final Logger log = LoggerFactory.getLogger(ManageStackClient)

  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 = [:]

    EngineResponseContent> 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 { TaskSpecContainerSpecSecretsInner spec -> spec.secretName == name }
          if (index >= 0) {
            containerSpecSecrets.set(index, new TaskSpecContainerSpecSecretsInner(
                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 { TaskSpecContainerSpecConfigsInner spec -> spec.configName == name }
          if (index >= 0) {
            containerSpecConfigs.set(index, new TaskSpecContainerSpecConfigsInner(
                containerSpecConfigs.get(index).file,
                containerSpecConfigs.get(index).runtime,
                configId,
                name
            ))
          }
        }
      }
    }

    createOrUpdateServices(namespace, config.services, options.sendRegistryAuth)
  }

  void createNetworks(String namespace, Map networks) {
    EngineResponseContent> 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 = new HashMap()
      }
      secret.labels[(LabelNamespace)] = namespace

      String secretId
      if (knownSecrets.empty) {
        log.info("create secret ${secret.name}: $secret")
        secretId = manageSecret.createSecret(secret.name, secret.data, secret.labels).content.id
      }
      else {
        if (knownSecrets.size() != 1) {
          throw new IllegalStateException("ambiguous secret name '${secret.name}'")
        }
        Secret knownSecret = knownSecrets.first()
        log.info("update secret ${secret.name}: $secret")
        secretId = knownSecret.ID
        manageSecret.updateSecret(
            knownSecret.ID,
            knownSecret.version.index,
            new SecretSpec(
                secret.name,
                secret.labels,
                new String(secret.data),
                secret.driver,
                secret.templating))
      }
      return [(secret.name): secretId]
    }
  }

  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 = new HashMap()
      }
      config.labels[(LabelNamespace)] = namespace

      String configId
      if (knownConfigs.empty) {
        log.info("create config ${config.name}: $config")
        configId = manageConfig.createConfig(config.name, config.data, config.labels).content.id
      }
      else {
        if (knownConfigs.size() != 1) {
          throw new IllegalStateException("ambiguous config name '${config.name}'")
        }
        Config knownConfig = knownConfigs.first()
        log.info("update config ${config.name}: $config")
        configId = knownConfig.ID
        manageConfig.updateConfig(
            knownConfig.ID,
            knownConfig.version.index,
            new ConfigSpec(
                config.name,
                config.labels,
                new String(config.data),
                config.templating))
      }
      return [(config.name): configId]
    }
  }

  void pruneServices(String namespace, Collection services) {
    // Descope returns the name without the namespace prefix
    Closure descope = { String name ->
      return name.substring("${namespace}_".length())
    }

    EngineResponseContent> 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 = new HashMap()
    EngineResponseContent> 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 = new HashMap()
      }
      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 = new HashMap()
        if (sendRegistryAuth) {
          updateOptions.EncodedRegistryAuth = encodedAuth
        }
        ServiceUpdateRequest updateRequest = new ServiceUpdateRequest(
            serviceSpec.name,
            serviceSpec.labels,
            serviceSpec.taskTemplate,
            serviceSpec.mode,
            serviceSpec.updateConfig,
            serviceSpec.rollbackConfig,
            serviceSpec.networks,
            serviceSpec.endpointSpec)
        EngineResponseContent response = manageService.updateService(
            service.ID,
            service.version.index,
            updateRequest,
            null,
            sendRegistryAuth ? encodedAuth : null)
        response.content.warnings.each { String warning ->
          log.warn(warning)
        }
      }
      else {
        log.info("Creating service ${name}: ${serviceSpec}")

        Map createOptions = new HashMap()
        if (sendRegistryAuth) {
          createOptions.EncodedRegistryAuth = encodedAuth
        }
        ServiceCreateRequest createRequest = new ServiceCreateRequest(
            serviceSpec.name,
            serviceSpec.labels,
            serviceSpec.taskTemplate,
            serviceSpec.mode,
            serviceSpec.updateConfig,
            serviceSpec.rollbackConfig,
            serviceSpec.networks,
            serviceSpec.endpointSpec)
        EngineResponseContent response = manageService.createService(createRequest, 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
  EngineResponseContent> stackPs(String namespace, Map filters = [:]) {
    log.info("docker stack ps")

    String namespaceFilter = "${LabelNamespace}=${namespace}"

    Map actualFilters = filters ?: new HashMap()
    if (actualFilters.label) {
      actualFilters.label[(namespaceFilter)] = true
    }
    else {
      actualFilters['label'] = [(namespaceFilter): true]
    }
    EngineResponseContent> tasks = manageTask.tasks([filters: actualFilters])
    return tasks
  }

  @Override
  void stackRm(String namespace) {
    log.info("docker stack rm")

    String namespaceFilter = "${LabelNamespace}=${namespace}"

    EngineResponseContent> services = manageService.services([filters: [label: [(namespaceFilter): true]]])
    EngineResponseContent> networks = manageNetwork.networks([filters: [label: [(namespaceFilter): true]]])
    EngineResponseContent> secrets = manageSecret.secrets([filters: [label: [(namespaceFilter): true]]])
    EngineResponseContent> 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
  EngineResponseContent> stackServices(String namespace, Map filters = [:]) {
    log.info("docker stack services")

    String namespaceFilter = "${LabelNamespace}=${namespace}"
    Map actualFilters = filters ?: new HashMap()
    if (actualFilters.label) {
      actualFilters.label[(namespaceFilter)] = true
    }
    else {
      actualFilters['label'] = [(namespaceFilter): true]
    }
    EngineResponseContent> services = manageService.services([filters: actualFilters])
//    def infoByServiceId = getInfoByServiceId(services)
    return services
  }

  Map getInfoByServiceId(EngineResponseContent> services) {
    EngineResponseContent> nodes = manageNode.nodes()
    List activeNodes = nodes.content.findResults { Node node ->
      node.status.state != NodeState.Down ? node.ID : null
    }

    Map running = new HashMap()
    Map tasksNoShutdown = new HashMap()

    Map serviceFilter = [service: [:]]
    services.content.each { Service service ->
      serviceFilter.service[(service.ID as String)] = true
    }
    EngineResponseContent> 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] = tasksNoShutdown[task.serviceID] + 1
      }
      if (activeNodes.contains(task.nodeID) && task.status.state == TaskState.Running) {
        if (!running[task.serviceID]) {
          running[task.serviceID] = 0
        }
        running[task.serviceID] = running[task.serviceID] + 1
      }
    }

    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 - 2024 Weber Informatics LLC | Privacy Policy