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

de.gesellix.docker.compose.ComposeFileReader.kt Maven / Gradle / Ivy

package de.gesellix.docker.compose

import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import de.gesellix.docker.compose.adapters.ListToExposeAdapter
import de.gesellix.docker.compose.adapters.ListToPortConfigsAdapter
import de.gesellix.docker.compose.adapters.ListToServiceConfigsAdapter
import de.gesellix.docker.compose.adapters.ListToServiceSecretsAdapter
import de.gesellix.docker.compose.adapters.ListToServiceVolumesAdapter
import de.gesellix.docker.compose.adapters.MapOrListToEnvironmentAdapter
import de.gesellix.docker.compose.adapters.MapOrListToExtraHosts
import de.gesellix.docker.compose.adapters.MapOrListToLabelAdapter
import de.gesellix.docker.compose.adapters.MapToDriverOptsAdapter
import de.gesellix.docker.compose.adapters.MapToExternalAdapter
import de.gesellix.docker.compose.adapters.StringOrListToCommandAdapter
import de.gesellix.docker.compose.adapters.StringOrListToEntrypointAdapter
import de.gesellix.docker.compose.adapters.StringToServiceNetworksAdapter
import de.gesellix.docker.compose.interpolation.ComposeInterpolator
import de.gesellix.docker.compose.types.ComposeConfig
import mu.KotlinLogging
import org.yaml.snakeyaml.Yaml
import java.io.InputStream

private val log = KotlinLogging.logger {}

class ComposeFileReader {

  // UnsupportedProperties not yet supported by this implementation of the compose file
  private val unsupportedProperties = listOf(
    "build",
    "cap_add",
    "cap_drop",
    "cgroup_parent",
    "devices",
    "dns",
    "dns_search",
    "domainname",
    "external_links",
    "ipc",
    "links",
    "mac_address",
    "network_mode",
    "privileged",
    "read_only",
    "restart",
    "security_opt",
    "shm_size",
    "sysctls",
    "tmpfs",
    "userns_mode"
  )

  // DeprecatedProperties that were removed from the v3 format, but their use should not impact the behaviour of the application.
  private val deprecatedProperties = hashMapOf().let {
    it["container_name"] = "Setting the container name is not supported."
    it["expose"] = "Exposing ports is unnecessary - services on the same network can access each other's containers on any port."
    it
  }

  // ForbiddenProperties that are not supported in this implementation of the compose file.
  private val forbiddenProperties = hashMapOf().let {
    it["extends"] = "Support for `extends` is not implemented yet."
    it["volume_driver"] = "Instead of setting the volume driver on the service, define a volume using the top-level `volumes` option and specify the driver there."
    it["volumes_from"] = "To share a volume between services, define it using the top-level `volumes` option and reference it from each service that shares it using the service-level `volumes` option."
    it["cpu_quota"] = "Set resource limits using deploy.resources"
    it["cpu_shares"] = "Set resource limits using deploy.resources"
    it["cpuset"] = "Set resource limits using deploy.resources"
    it["mem_limit"] = "Set resource limits using deploy.resources"
    it["memswap_limit"] = "Set resource limits using deploy.resources"
    it
  }

  private val interpolator = ComposeInterpolator()

  fun loadYaml(composeFile: InputStream): HashMap?>> {
//    val loadSettings = LoadSettings.builder().build()
//    val composeContent = Load(loadSettings).loadFromInputStream(composeFile) as Map?>>
    val composeContent = Yaml().loadAs(composeFile, Map::class.java) as Map?>>
    log.info("composeContent: $composeContent}")

    return hashMapOf?>>().apply {
      putAll(composeContent)
    }
  }

  fun load(
    composeFile: InputStream,
    workingDir: String,
    environment: Map = System.getenv()
  ): ComposeConfig? {
    val composeContent = loadYaml(composeFile)

    val forbiddenProperties = collectForbiddenServiceProperties(composeContent["services"], forbiddenProperties)
    if (forbiddenProperties.isNotEmpty()) {
      log.error("Configuration contains forbidden properties: $forbiddenProperties")
      throw IllegalStateException("Configuration contains forbidden properties")
    }

    // overrides interpolated sections, but keeps non-interpolated ones.
    composeContent.putAll(interpolator.interpolate(composeContent, environment))

    val cfg = Moshi.Builder()
      .add(KotlinJsonAdapterFactory())
      .add(ListToExposeAdapter())
      .add(ListToPortConfigsAdapter())
      .add(ListToServiceSecretsAdapter())
      .add(ListToServiceVolumesAdapter())
      .add(ListToServiceConfigsAdapter())
      .add(MapOrListToEnvironmentAdapter())
      .add(MapOrListToExtraHosts())
      .add(MapOrListToLabelAdapter())
      .add(MapToDriverOptsAdapter())
      .add(MapToExternalAdapter())
      .add(StringOrListToEntrypointAdapter())
      .add(StringOrListToCommandAdapter())
      .add(StringToServiceNetworksAdapter())
      .build()
      .adapter(ComposeConfig::class.java)
      .fromJsonValue(composeContent)

//        def valid = new SchemaValidator().validate(composeContent)

    val unsupportedProperties = collectUnsupportedServiceProperties(composeContent["services"], unsupportedProperties)
    if (unsupportedProperties.isNotEmpty()) {
      log.warn("Ignoring unsupported options: ${unsupportedProperties.joinToString(", ")}")
    }

    val deprecatedProperties = collectDeprecatedServiceProperties(composeContent["services"], deprecatedProperties)
    if (deprecatedProperties.isNotEmpty()) {
      log.warn("Ignoring deprecated options: $deprecatedProperties")
    }

//        if volume.External.Name == "" {
//            volume.External.Name = name
//            volumes[name] = volume
//        } else {
//            logrus.Warnf("volume %s: volume.external.name is deprecated in favor of volume.name", name)
//
//            if volume.Name != "" {
//                return nil, errors.Errorf("volume %s: volume.external.name and volume.name conflict; only use volume.name", name)
//            }
//        }

    return cfg
  }

  private fun collectForbiddenServiceProperties(
    services: Map?>?,
    forbiddenProperties: Map
  ): Map {
    val hits = hashMapOf()
    services?.forEach { service, serviceConfig ->
      if (serviceConfig != null) {
        forbiddenProperties.forEach { (property, description) ->
          if (serviceConfig.containsKey(property)) {
            hits["$service.$property"] = description
          }
        }
      }
    }
    return hits
  }

  private fun collectUnsupportedServiceProperties(
    services: Map?>?,
    unsupportedProperties: List
  ): List {
    val hits = arrayListOf()
    services?.forEach { service, serviceConfig ->
      if (serviceConfig != null) {
        unsupportedProperties.forEach { property ->
          if (serviceConfig.containsKey(property)) {
            hits.add("$service.$property")
          }
        }
      }
    }
    return hits
  }

  private fun collectDeprecatedServiceProperties(
    services: Map?>?,
    deprecatedProperties: Map
  ): Map {
    val hits = hashMapOf()
    services?.forEach { service, serviceConfig ->
      if (serviceConfig != null) {
        deprecatedProperties.forEach { (property, description) ->
          if (serviceConfig.containsKey(property)) {
            hits["$service.$property"] = description
          }
        }
      }
    }
    return hits
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy