<commit_before>/*
   Copyright 2017-2019 <NAME>.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package batect.integrationtests

import batect.config.HealthCheckConfig
import batect.config.PortMapping
import batect.config.VolumeMount
import batect.docker.DockerAPI
import batect.docker.DockerClient
import batect.docker.DockerConnectivityCheckResult
import batect.docker.DockerContainer
import batect.docker.DockerContainerCreationRequest
import batect.docker.DockerHealthCheckResult
import batect.docker.DockerHttpConfig
import batect.docker.DockerImage
import batect.docker.DockerNetwork
import batect.docker.DockerVersionInfoRetrievalResult
import batect.docker.HealthStatus
import batect.docker.UserAndGroup
import batect.docker.build.DockerIgnoreParser
import batect.docker.build.DockerImageBuildContextFactory
import batect.docker.build.DockerfileParser
import batect.docker.pull.DockerRegistryCredentialsConfigurationFile
import batect.docker.pull.DockerRegistryCredentialsProvider
import batect.docker.pull.DockerRegistryDomainResolver
import batect.docker.pull.DockerRegistryIndexResolver
import batect.docker.run.ContainerIOStreamer
import batect.docker.run.ContainerKiller
import batect.docker.run.ContainerTTYManager
import batect.docker.run.ContainerWaiter
import batect.logging.Logger
import batect.os.NativeMethods
import batect.os.ProcessOutput
import batect.os.ProcessRunner
import batect.os.SignalListener
import batect.os.SystemInfo
import batect.testutils.createForGroup
import batect.testutils.runBeforeGroup
import batect.ui.ConsoleDimensions
import batect.ui.ConsoleInfo
import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.containsSubstring
import com.natpryce.hamkrest.equalTo
import com.natpryce.hamkrest.has
import com.natpryce.hamkrest.isA
import com.natpryce.hamkrest.or
import com.nhaarman.mockitokotlin2.mock
import jnr.posix.POSIXFactory
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

object DockerClientIntegrationTest : Spek({
    describe("a Docker client") {
        val testImagePath by createForGroup { Paths.get("./src/integrationTest/resources/test-image/").toAbsolutePath().toString() }

        val logger by createForGroup { mock<Logger>() }
        val processRunner by createForGroup { ProcessRunner(logger) }
        val httpConfig by createForGroup { DockerHttpConfig(OkHttpClient(), DockerHttpConfig.defaultDockerHost) }
        val api by createForGroup { DockerAPI(httpConfig, logger) }
        val posix by createForGroup { POSIXFactory.getNativePOSIX() }
        val nativeMethods by createForGroup { NativeMethods(posix) }
        val consoleInfo by createForGroup { ConsoleInfo(posix, processRunner, logger) }
        val credentialsConfigurationFile by createForGroup { DockerRegistryCredentialsConfigurationFile(FileSystems.getDefault(), processRunner, logger) }
        val credentialsProvider by createForGroup { DockerRegistryCredentialsProvider(DockerRegistryDomainResolver(), DockerRegistryIndexResolver(), credentialsConfigurationFile) }
        val ignoreParser by createForGroup { DockerIgnoreParser() }
        val imageBuildContextFactory by createForGroup { DockerImageBuildContextFactory(ignoreParser) }
        val dockerfileParser by createForGroup { DockerfileParser() }
        val waiter by createForGroup { ContainerWaiter(api) }
        val streamer by createForGroup { ContainerIOStreamer(System.out, System.`in`) }
        val signalListener by createForGroup { SignalListener(posix) }
        val consoleDimensions by createForGroup { ConsoleDimensions(nativeMethods, signalListener, logger) }
        val killer by createForGroup { ContainerKiller(api, signalListener) }
        val ttyManager by createForGroup { ContainerTTYManager(api, consoleInfo, consoleDimensions, logger) }
        val client by createForGroup { DockerClient(api, consoleInfo, credentialsProvider, imageBuildContextFactory, dockerfileParser, waiter, streamer, killer, ttyManager, logger) }

        fun creationRequestForContainer(image: DockerImage, network: DockerNetwork, command: Iterable<String>, volumeMounts: Set<VolumeMount> = emptySet(), portMappings: Set<PortMapping> = emptySet(), userAndGroup: UserAndGroup? = null): DockerContainerCreationRequest {
            return DockerContainerCreationRequest(
                image,
                network,
                command,
                "test-container",
                "test-container",
                emptyMap(),
                null,
                volumeMounts,
                portMappings,
                HealthCheckConfig(),
                userAndGroup,
                false,
                init = false
            )
        }

        fun creationRequestForTestContainer(image: DockerImage, network: DockerNetwork, fileToCreate: Path, command: Iterable<String>): DockerContainerCreationRequest {
            val fileToCreateParent = fileToCreate.parent.toAbsolutePath()
            val volumeMount = VolumeMount(fileToCreateParent.toString(), fileToCreateParent.toString(), null)

            val systemInfo = SystemInfo(posix)
            val userAndGroup = UserAndGroup(systemInfo.userId, systemInfo.groupId)

            return creationRequestForContainer(image, network, command, volumeMounts = setOf(volumeMount), userAndGroup = userAndGroup)
        }

        fun creationRequestForContainerThatWaits(image: DockerImage, network: DockerNetwork, fileToCreate: Path): DockerContainerCreationRequest {
            // See https://stackoverflow.com/a/21882119/1668119 for an explanation of this - we need something that waits indefinitely but immediately responds to a SIGTERM by quitting (sh and wait don't do this).
            val command = listOf("sh", "-c", "echo 'Hello from container' >> \"$fileToCreate\"; trap 'trap - TERM; kill -s TERM -$$' TERM; tail -f /dev/null & wait")

            return creationRequestForTestContainer(image, network, fileToCreate, command)
        }

        fun creationRequestForContainerThatExits(image: DockerImage, network: DockerNetwork, fileToCreate: Path): DockerContainerCreationRequest {
            val command = listOf("sh", "-c", "echo 'Hello from container' >> \"$fileToCreate\"")

            return creationRequestForTestContainer(image, network, fileToCreate, command)
        }

        fun <T> withNetwork(action: (DockerNetwork) -> T): T {
            val network = client.createNewBridgeNetwork()<commit_msg>Fix integration test broken with previous commit.<commit_after>/*
   Copyright 2017-2019 <NAME>.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package batect.integrationtests

import batect.config.HealthCheckConfig
import batect.config.PortMapping
import batect.config.VolumeMount
import batect.docker.DockerAPI
import batect.docker.DockerClient
import batect.docker.DockerConnectivityCheckResult
import batect.docker.DockerContainer
import batect.docker.DockerContainerCreationRequest
import batect.docker.DockerHealthCheckResult
import batect.docker.DockerHttpConfig
import batect.docker.DockerImage
import batect.docker.DockerNetwork
import batect.docker.DockerVersionInfoRetrievalResult
import batect.docker.HealthStatus
import batect.docker.UserAndGroup
import batect.docker.build.DockerIgnoreParser
import batect.docker.build.DockerImageBuildContextFactory
import batect.docker.build.DockerfileParser
import batect.docker.pull.DockerRegistryCredentialsConfigurationFile
import batect.docker.pull.DockerRegistryCredentialsProvider
import batect.docker.pull.DockerRegistryDomainResolver
import batect.docker.pull.DockerRegistryIndexResolver
import batect.docker.run.ContainerIOStreamer
import batect.docker.run.ContainerKiller
import batect.docker.run.ContainerTTYManager
import batect.docker.run.ContainerWaiter
import batect.logging.Logger
import batect.os.NativeMethods
import batect.os.ProcessOutput
import batect.os.ProcessRunner
import batect.os.SignalListener
import batect.os.SystemInfo
import batect.testutils.createForGroup
import batect.testutils.runBeforeGroup
import batect.ui.ConsoleDimensions
import batect.ui.ConsoleInfo
import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.containsSubstring
import com.natpryce.hamkrest.equalTo
import com.natpryce.hamkrest.has
import com.natpryce.hamkrest.isA
import com.natpryce.hamkrest.or
import com.nhaarman.mockitokotlin2.mock
import jnr.posix.POSIXFactory
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

object DockerClientIntegrationTest : Spek({
    describe("a Docker client") {
        val testImagePath by createForGroup { Paths.get("./src/integrationTest/resources/test-image/").toAbsolutePath().toString() }

        val logger by createForGroup { mock<Logger>() }
        val processRunner by createForGroup { ProcessRunner(logger) }
        val httpConfig by createForGroup { DockerHttpConfig(OkHttpClient(), DockerHttpConfig.defaultDockerHost) }
        val api by createForGroup { DockerAPI(httpConfig, logger) }
        val posix by createForGroup { POSIXFactory.getNativePOSIX() }
        val nativeMethods by createForGroup { NativeMethods(posix) }
        val consoleInfo by createForGroup { ConsoleInfo(posix, processRunner, logger) }
        val credentialsConfigurationFile by createForGroup { DockerRegistryCredentialsConfigurationFile(FileSystems.getDefault(), processRunner, logger) }
        val credentialsProvider by createForGroup { DockerRegistryCredentialsProvider(DockerRegistryDomainResolver(), DockerRegistryIndexResolver(), credentialsConfigurationFile) }
        val ignoreParser by createForGroup { DockerIgnoreParser() }
        val imageBuildContextFactory by createForGroup { DockerImageBuildContextFactory(ignoreParser) }
        val dockerfileParser by createForGroup { DockerfileParser() }
        val waiter by createForGroup { ContainerWaiter(api) }
        val streamer by createForGroup { ContainerIOStreamer(System.out, System.`in`) }
        val signalListener by createForGroup { SignalListener(posix) }
        val consoleDimensions by createForGroup { ConsoleDimensions(nativeMethods, signalListener, logger) }
        val killer by createForGroup { ContainerKiller(api, signalListener) }
        val ttyManager by createForGroup { ContainerTTYManager(api, consoleInfo, consoleDimensions, logger) }
        val client by createForGroup { DockerClient(api, consoleInfo, credentialsProvider, imageBuildContextFactory, dockerfileParser, waiter, streamer, killer, ttyManager, logger) }

        fun creationRequestForContainer(image: DockerImage, network: DockerNetwork, command: Iterable<String>, volumeMounts: Set<VolumeMount> = emptySet(), portMappings: Set<PortMapping> = emptySet(), userAndGroup: UserAndGroup? = null): DockerContainerCreationRequest {
            return DockerContainerCreationRequest(
                image,
                network,
                command,
                "test-container",
                "test-container",
                emptyMap(),
                null,
                volumeMounts,
                portMappings,
                HealthCheckConfig(),
                userAndGroup,
                privileged = false,
                init = false,
                capabilitiesToAdd = emptySet(),
                capabilitiesToDrop = emptySet()
            )
        }

        fun creationRequestForTestContainer(image: DockerImage, network: DockerNetwork, fileToCreate: Path, command: Iterable<String>): DockerContainerCreationRequest {
            val fileToCreateParent = fileToCreate.parent.toAbsolutePath()
            val volumeMount = VolumeMount(fileToCreateParent.toString(), fileToCreateParent.toString(), null)

            val systemInfo = SystemInfo(posix)
            val userAndGroup = UserAndGroup(systemInfo.userId, systemInfo.groupId)

            return creationRequestForContainer(image, network, command, volumeMounts = setOf(volumeMount), userAndGroup = userAndGroup)
        }

        fun creationRequestForContainerThatWaits(image: DockerImage, network: DockerNetwork, fileToCreate: Path): DockerContainerCreationRequest {
            // See https://stackoverflow.com/a/21882119/1668119 for an explanation of this - we need something that waits indefinitely but immediately responds to a SIGTERM by quitting (sh and wait don't do this).
            val command = listOf("sh", "-c", "echo 'Hello from container' >> \"$fileToCreate\"; trap 'trap - TERM; kill -s TERM -$$' TERM; tail -f /dev/null & wait")

            return creationRequestForTestContainer(image, network, fileToCreate, command)
        }

        fun creationRequestForContainerThatExits(image: DockerImage, network: DockerNetwork, fileToCreate: Path): DockerContainerCreationRequest {
            val command = listOf("sh", "-c", "echo 'Hello from container' >> \"$fileToCreate\"")

            return creationRequestForTestContainer(image, network, fileToCreate, command)
        }

        fun <T> withNetwork(action: (DockerNetwork) -> T): T {
            val network = client.createNewBridgeNetwork()
{'fraction_non_alphanumeric': 0.0738338393596467, 'fraction_numerical': 0.004554236820314656, 'mean_word_length': 4.110366713681241, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 27, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '683598', 'n_tokens_mistral': 3971, 'n_tokens_neox': 3765, 'n_words': 964}