initial script and publish action

This commit is contained in:
Gergely Hegedus 2022-08-25 19:02:00 +03:00
parent a1f1eca3f6
commit e478a4c836
70 changed files with 2602 additions and 0 deletions

1
main/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

31
main/build.gradle.kts Normal file
View file

@ -0,0 +1,31 @@
plugins {
id("org.jetbrains.kotlin.jvm")
id("application")
}
application {
mainClass.set("org.fnives.android.test.dockerfile.Main")
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.test {
useJUnitPlatform()
testLogging {
setEvents(setOf("started", "passed", "skipped", "failed"))
setExceptionFormat("full")
showStandardStreams = true
}
}
dependencies {
val tomlParserVersion = "1.1.0"
implementation("cc.ekblad:4koma:$tomlParserVersion")
val testingJunitJupiterVersion = "5.7.0"
val testingJunitJupiterRuntimeVersion = "5.3.1"
testImplementation("org.junit.jupiter:junit-jupiter-engine:$testingJunitJupiterVersion")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$testingJunitJupiterRuntimeVersion")
}

View file

@ -0,0 +1,65 @@
package org.fnives.android.test.dockerfile
import org.fnives.android.test.dockerfile.configuration.Configuration
import org.fnives.android.test.dockerfile.configuration.ImageConfiguration
import org.fnives.android.test.dockerfile.di.ServiceLocatorHolder.ServiceLocator
import java.io.File
/**
* Step into the Script.
*
* This script is intended to:
* - Generate Dockerfiles from the given configuration.
* - Create DockerImages from the generated Dockerfiles
* - Push those images up to DockerHub based on the configucation.
*
* Needs a single argument to the path of the configuration.toml file.
*
* Usually run like: ./gradlew run --args="$(pwd)/configuration.toml
*
* **Docker needs to be installed**
*/
object Main {
@JvmStatic
@Throws(IllegalArgumentException::class)
fun main(args: Array<String>) {
val configuration = getConfigurationFromArgument(args)
val configuredServiceLocator = ServiceLocator.loadConfiguration(configuration)
val imageConfigurations = listOf(ImageConfiguration(configuration))
.plus(configuration.apiLevelVariations.map(::ImageConfiguration))
configuredServiceLocator.copyScript().invoke()
imageConfigurations.map(configuredServiceLocator.contentGenerator()::create)
.map(configuredServiceLocator.imageWriter()::write)
.forEach(configuredServiceLocator.imageBuildAndPush()::buildPushClean)
}
@Throws(IllegalArgumentException::class)
private fun getConfigurationFromArgument(args: Array<String>): Configuration {
val configFile = getFileFromArgument(args)
return try {
configFile.inputStream().use(ServiceLocator.parser()::invoke)
} catch (illegalArgumentException: IllegalArgumentException) {
throw IllegalArgumentException("Unparseable file at given path: ${configFile.absolutePath}", illegalArgumentException)
}
}
@Throws(IllegalArgumentException::class)
private fun getFileFromArgument(args: Array<String>): File {
require(args.isNotEmpty()) {
"Required argument is missing! Required argument is the config.toml file path and just that!"
}
require(args.size < 2) {
"More arguments than expected! Required argument is the config.toml file path and just that!"
}
val configFile = File(args[0])
require(configFile.exists()) {
"Can't find file at given path: ${configFile.absolutePath}"
}
return configFile
}
}

View file

@ -0,0 +1,28 @@
package org.fnives.android.test.dockerfile.configuration
import org.fnives.android.test.dockerfile.generator.DockerFileContent
/**
* Describes all the configurations which can be set inside the [toml][https://toml.io/en/] file.
*
* @param buildTools android build tools installed into the DockerImages
* @param sdks AndroidSDKs installed into the DockerImages
* @param commandlineTools AndroidCommandLinesTools installed into the DockerImages
* @param gradleVersion GradleVersion to be installed into the DockerImages
* @param output the folder which will contain the generated Dockerfiles
* @param dockerRepository the repository in DockerHub to push into.
* @param dockerNamespace the namespace inside the [dockerRepository] defined on DockerHub.
* @param dockerTagPrefix the prefix which should be added to all DockerImage tags when creating [DockerFileContent]
* @param apiLevelVariations the specific api levels that should have their own image. This is used so everything is ready for the Emulator for a specific API version.
*/
data class Configuration(
val buildTools: Set<String>,
val sdks: Set<Int>,
val commandlineTools: String,
val gradleVersion: String,
val output: String,
val dockerRepository: String,
val dockerNamespace: String,
val dockerTagPrefix: String,
val apiLevelVariations: Set<Int> = emptySet()
)

View file

@ -0,0 +1,55 @@
package org.fnives.android.test.dockerfile.configuration
import cc.ekblad.toml.TomlMapper
import cc.ekblad.toml.decode
import cc.ekblad.toml.tomlMapper
import java.io.InputStream
/**
* Parses the given [toml][https://toml.io/en/] file into a [Configuration] to be usable by the other parts of the app.
*/
class ConfigurationParser(private val mapper: TomlMapper = tomlMapper {}) {
@Throws(IllegalArgumentException::class)
fun invoke(content: InputStream): Configuration {
val raw = try {
mapper.decode<RawConfiguration>(content)
} catch (cause: Throwable) {
throw IllegalArgumentException("Couldn't process configuration", cause)
}
return raw.convert()
}
private fun RawConfiguration.convert(): Configuration = Configuration(
buildTools = buildTools,
sdks = sdks,
commandlineTools = androidCommandlineTools,
gradleVersion = gradleVersion,
output = output,
apiLevelVariations = variations?.apiLevels.orEmpty(),
dockerNamespace = image.namespace,
dockerRepository = image.repository,
dockerTagPrefix = image.tagPrefix
)
private data class RawConfiguration(
val buildTools: Set<String>,
val sdks: Set<Int>,
val androidCommandlineTools: String,
val gradleVersion: String,
val output: String,
val variations: Variations?,
val image: Image
) {
class Variations(
val apiLevels: Set<Int> = emptySet()
)
class Image(
val namespace: String,
val repository: String,
val tagPrefix: String,
)
}
}

View file

@ -0,0 +1,17 @@
package org.fnives.android.test.dockerfile.configuration
/**
* Describes the specific that should be used to fill out the Dockerfile templates.
*/
sealed class ImageConfiguration {
data class Generic(
val buildTools: Set<String>,
val sdks: Set<Int>,
val commandlineTools: String,
val gradleVersion: String
) : ImageConfiguration()
data class ApiVersion(
val version: Int
) : ImageConfiguration()
}

View file

@ -0,0 +1,17 @@
package org.fnives.android.test.dockerfile.configuration
/**
* Utility function to ease the creation of a generic [ImageConfiguration]
*/
fun ImageConfiguration(configuration: Configuration): ImageConfiguration =
ImageConfiguration.Generic(
buildTools = configuration.buildTools,
sdks = configuration.sdks,
commandlineTools = configuration.commandlineTools,
gradleVersion = configuration.gradleVersion,
)
/**
* Utility function to ease the creation of a specific [ImageConfiguration], that will depend on a Generic one.
*/
fun ImageConfiguration(version: Int): ImageConfiguration = ImageConfiguration.ApiVersion(version)

View file

@ -0,0 +1,16 @@
package org.fnives.android.test.dockerfile.di
import org.fnives.android.test.dockerfile.generator.DockerFileContentGenerator
import org.fnives.android.test.dockerfile.push.DockerBuildAndPush
import org.fnives.android.test.dockerfile.write.DockerFileWriter
import org.fnives.android.test.dockerfile.write.ScriptCopier
/**
* Gives access to the Services that depend on [Configuration][org.fnives.android.test.dockerfile.configuration.Configuration] and takes care of their creation and instances.
*/
interface ConfiguredServiceLocator {
fun contentGenerator(): DockerFileContentGenerator
fun imageWriter(): DockerFileWriter
fun imageBuildAndPush(): DockerBuildAndPush
fun copyScript(): ScriptCopier
}

View file

@ -0,0 +1,61 @@
package org.fnives.android.test.dockerfile.di
import org.fnives.android.test.dockerfile.configuration.Configuration
import org.fnives.android.test.dockerfile.generator.DockerFileContentConfig
import org.fnives.android.test.dockerfile.generator.DockerFileContentGenerator
import org.fnives.android.test.dockerfile.push.DockerBuildAndPush
import org.fnives.android.test.dockerfile.push.PushConfig
import org.fnives.android.test.dockerfile.util.resourceFileContent
import org.fnives.android.test.dockerfile.util.clock.Clock
import org.fnives.android.test.dockerfile.write.DockerFileWriter
import org.fnives.android.test.dockerfile.write.ScriptCopier
import java.io.File
/** Syntax Sugar */
fun ConfiguredServiceLocator(configuration: Configuration): ConfiguredServiceLocator =
DefaultConfiguredServiceLocator(configuration)
/**
* Actual [ConfiguredServiceLocator] keeping references and creating the Services that are dependent on [Configuration].
*/
class DefaultConfiguredServiceLocator(private val configuration: Configuration) : ConfiguredServiceLocator {
private val pushConfig
get() = PushConfig(
dockerRepository = configuration.dockerRepository,
dockerNamespace = configuration.dockerNamespace,
output = configuration.output
)
private val outputFolder get() = File(configuration.output)
private val processRunner get() = ServiceLocatorHolder.ServiceLocator.processRunner()
private val clock: Clock get() = ServiceLocatorHolder.ServiceLocator.clock()
private val scriptName: String get() = ServiceLocatorHolder.ServiceLocator.scriptToCopy()
private val imageBuildAndPush by lazy { DockerBuildAndPush(config = pushConfig, processRunner = processRunner) }
private val imageWriter by lazy { DockerFileWriter(outputFolder = outputFolder) }
private val copyScript by lazy { ScriptCopier(outputFolder = outputFolder, scriptName = scriptName) }
private val dockerFileContentConfig
get() = DockerFileContentConfig(
templateGeneric = resourceFileContent("Dockerfile.template"),
templateSpecific = resourceFileContent("Dockerfile.api.template"),
dockerRepository = configuration.dockerRepository,
dockerNamespace = configuration.dockerNamespace,
dockerTagPrefix = configuration.dockerTagPrefix,
)
private val contentGenerator by lazy {
DockerFileContentGenerator(
config = dockerFileContentConfig,
clock = clock
)
}
override fun contentGenerator(): DockerFileContentGenerator = contentGenerator
override fun imageWriter(): DockerFileWriter = imageWriter
override fun imageBuildAndPush(): DockerBuildAndPush = imageBuildAndPush
override fun copyScript(): ScriptCopier = copyScript
}

View file

@ -0,0 +1,28 @@
package org.fnives.android.test.dockerfile.di
import org.fnives.android.test.dockerfile.configuration.Configuration
import org.fnives.android.test.dockerfile.configuration.ConfigurationParser
import org.fnives.android.test.dockerfile.push.ProcessRunner
import org.fnives.android.test.dockerfile.push.SystemProcessRunner
import org.fnives.android.test.dockerfile.util.clock.Clock
import org.fnives.android.test.dockerfile.util.clock.SystemCock
/**
* Actual [ServiceLocator] keeping references and creating the Services.
*/
object DefaultServiceLocator : ServiceLocator {
private val parser by lazy { ConfigurationParser() }
private val processRunner by lazy { SystemProcessRunner() }
override fun loadConfiguration(configuration: Configuration): ConfiguredServiceLocator =
ConfiguredServiceLocator(configuration)
override fun parser(): ConfigurationParser = parser
override fun clock(): Clock = SystemCock
override fun scriptToCopy(): String = "startemulator"
override fun processRunner(): ProcessRunner = processRunner
}

View file

@ -0,0 +1,17 @@
package org.fnives.android.test.dockerfile.di
import org.fnives.android.test.dockerfile.configuration.Configuration
import org.fnives.android.test.dockerfile.configuration.ConfigurationParser
import org.fnives.android.test.dockerfile.push.ProcessRunner
import org.fnives.android.test.dockerfile.util.clock.Clock
/**
* Gives access to the Services and takes care of their creation and instances.
*/
interface ServiceLocator {
fun parser(): ConfigurationParser
fun clock(): Clock
fun scriptToCopy(): String
fun processRunner(): ProcessRunner
fun loadConfiguration(configuration: Configuration): ConfiguredServiceLocator
}

View file

@ -0,0 +1,32 @@
package org.fnives.android.test.dockerfile.di
/**
* Access point to [ServiceLocator][org.fnives.android.test.dockerfile.di.ServiceLocator] so it can be [swap]ed out in tests.
*/
object ServiceLocatorHolder {
/**
* Syntax Sugar
*/
val ServiceLocator get() = get()
val default: ServiceLocator = DefaultServiceLocator
private var actual: ServiceLocator = default
/**
* Change the [ServiceLocator][org.fnives.android.test.dockerfile.di.ServiceLocator] returned in [get] and used.
*/
fun swap(serviceLocator: ServiceLocator) {
actual = serviceLocator
}
/**
* Reset back to [default] [ServiceLocator][org.fnives.android.test.dockerfile.di.ServiceLocator] to be returned in [get]
*/
fun reset() = swap(default)
/**
* Returns the actual [ServiceLocator][org.fnives.android.test.dockerfile.di.ServiceLocator]
*/
fun get() = actual
}

View file

@ -0,0 +1,14 @@
package org.fnives.android.test.dockerfile.generator
/**
* Describes the content and necessary information to create a Dockerfile and DockerImage from that.
*
* @param name the name of the Dockerfile to be created
* @param tag the id of the DockerImage to be created
* @param content the commands which should be in the Dockerfile
*/
data class DockerFileContent(
val name: String,
val tag: String,
val content: String
)

View file

@ -0,0 +1,18 @@
package org.fnives.android.test.dockerfile.generator
/**
* Configuration for the [DockerFileContentGenerator]
*
* @param templateGeneric the Dockerfile template content for a Generic image
* @param templateSpecific the Dockerfile template content for a Specific image, which extends the Generic and adds additional installs.
* @param dockerRepository the repository in DockerHub to push into.
* @param dockerNamespace the namespace inside the [dockerRepository] defined on DockerHub.
* @param dockerTagPrefix the prefix which should be added to all DockerImage tags when creating [DockerFileContent]
*/
data class DockerFileContentConfig(
val templateGeneric: String,
val templateSpecific: String,
val dockerRepository: String,
val dockerNamespace: String,
val dockerTagPrefix: String,
)

View file

@ -0,0 +1,103 @@
package org.fnives.android.test.dockerfile.generator
import org.fnives.android.test.dockerfile.configuration.ImageConfiguration
import org.fnives.android.test.dockerfile.util.clock.Clock
import java.text.SimpleDateFormat
import java.util.Date
/**
* Creates [DockerFileContent] from an [ImageConfiguration] based on the [DockerFileContentConfig].
*
* Uses the templates of [DockerFileContentConfig] and replaces the specifics described in the [ImageConfiguration].
* The templates have keywords which are used to replace with the specific commands defined by [ImageConfiguration]
*/
class DockerFileContentGenerator(
private val config: DockerFileContentConfig,
private val clock: Clock
) {
fun create(imageConfiguration: ImageConfiguration): DockerFileContent {
val dockerfileName = dockerFileName(imageConfiguration.version())
val tag = "${config.dockerRepository}/${config.dockerNamespace}:${config.dockerTagPrefix}${tagSuffix(imageConfiguration.version())}"
val genericTag = "${config.dockerRepository}/${config.dockerNamespace}:${config.dockerTagPrefix}${tagSuffix(null)}"
val content = when (imageConfiguration) {
is ImageConfiguration.Generic ->
config.templateGeneric.setCommandLineToolsVersion(imageConfiguration.commandlineTools)
.setGradleVersion(imageConfiguration.gradleVersion)
.setInstalledSDKs(imageConfiguration.sdks)
.setInstalledBuildTools(imageConfiguration.buildTools)
.setDateStamp()
is ImageConfiguration.ApiVersion ->
config.templateSpecific.setFromTag(genericTag)
.setENVAPILevel(imageConfiguration.version)
.setDateStamp()
}
return DockerFileContent(
name = dockerfileName,
content = content,
tag = tag
)
}
private fun String.setCommandLineToolsVersion(version: String): String =
replace(COMMAND_LINES_KEY, version)
private fun String.setGradleVersion(version: String): String =
replace(GRADLE_VERSION_KEY, version)
private fun String.setInstalledSDKs(sdks: Set<Int>): String {
val sdkInstallCommand = sdks.joinToString(" && \\\n ") { getSdkInstallCommand(it) }
return replace(SDK_INSTALL_COMMANDS_KEY, sdkInstallCommand)
}
private fun String.setInstalledBuildTools(buildTools: Set<String>): String {
val buildToolsInstallCommand = buildTools.joinToString(" && \\\n ") { getBuildToolsInstallCommand(it) }
return replace(BUILD_TOOLS_INSTALL_COMMANDS_KEY, buildToolsInstallCommand)
}
private fun dockerFileName(version: Int?): String =
if (version == null) {
"Dockerfile"
} else {
"Dockerfile-api-$version"
}
private fun tagSuffix(version: Int?): String = if (version == null) "" else "-api-$version"
private fun String.setFromTag(tagPrefix: String): String {
val tag = tagPrefix + tagSuffix(null)
return replace(TAG_KEY, tag)
}
private fun String.setENVAPILevel(apiLevel: Int): String = replace(SET_API_LEVEL, apiLevel.toString())
private fun String.setDateStamp(): String {
val dateStamp = SimpleDateFormat("yyyy-MM-dd").format(Date(clock.currentTimeMillis()))
return replace(DATE_STAMP_KEY, "Generated on $dateStamp")
}
companion object {
private const val COMMAND_LINES_KEY = "{{COMMAND_LINE_TOOLS}}"
private const val GRADLE_VERSION_KEY = "{{GRADLE_VERSION}}"
private const val SDK_INSTALL_COMMANDS_KEY = "{{SDK_INSTALL_COMMANDS}}"
private const val BUILD_TOOLS_INSTALL_COMMANDS_KEY = "{{BUILD_TOOLS_INSTALL_COMMANDS}}"
private const val TAG_KEY = "{{IMG-TAG}}"
private const val SET_API_LEVEL = "{{API_LEVEL}}"
private const val DATE_STAMP_KEY = "{{GENERATED_AT}}"
private fun getSdkInstallCommand(sdk: Int) = "echo y | sdkmanager \"platforms;android-$sdk\""
private fun getBuildToolsInstallCommand(buildTool: String) = "echo y | sdkmanager \"build-tools;$buildTool\""
private fun ImageConfiguration.version() = when (this) {
is ImageConfiguration.ApiVersion -> version
is ImageConfiguration.Generic -> null
}
}
}

View file

@ -0,0 +1,38 @@
package org.fnives.android.test.dockerfile.push
import org.fnives.android.test.dockerfile.write.DockerFileDescriptor
import java.io.File
/**
* Runs the necessary docker commands to create, push and cleanup the DockerImage from the given Dockerfile.
*/
class DockerBuildAndPush(
private val config: PushConfig,
private val processRunner: ProcessRunner
) {
private val workDir get() = File(config.output)
fun build(dockerFileDescriptor: DockerFileDescriptor): String {
"docker build -t ${dockerFileDescriptor.tag} -f ${dockerFileDescriptor.filePath} .".runCommand(workDir)
return dockerFileDescriptor.tag
}
fun push(id: String) {
"docker push $id".runCommand(workDir)
}
fun removeLocalImage(id: String) {
"docker image rm -f $id".runCommand(workDir)
}
fun buildPushClean(dockerFileDescriptor: DockerFileDescriptor) {
val id = build(dockerFileDescriptor)
push(id)
removeLocalImage(id)
}
private fun String.runCommand(workingDir: File) =
processRunner.run(workingDirectory = workingDir, command = this)
}

View file

@ -0,0 +1,16 @@
package org.fnives.android.test.dockerfile.push
import java.io.File
import java.util.concurrent.TimeoutException
/**
* Process Runner is able to run a given command in the System process.
*/
interface ProcessRunner {
/**
* Runs the given [command] as a System process in [workingDirectory].
*/
@Throws(TimeoutException::class)
fun run(workingDirectory: File, command: String)
}

View file

@ -0,0 +1,14 @@
package org.fnives.android.test.dockerfile.push
/**
* Configuration for [DockerBuildAndPush].
*
* @param dockerRepository the repository in DockerHub to push into.
* @param dockerNamespace the namespace inside the [dockerRepository] defined on DockerHub.
* @param output the path to the output folder.
*/
data class PushConfig(
val dockerRepository: String,
val dockerNamespace: String,
val output: String,
)

View file

@ -0,0 +1,26 @@
package org.fnives.android.test.dockerfile.push
import java.io.File
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
/**
* Actual [ProcessRunner] which converts the command into the Process then starts and awaits it.
*/
class SystemProcessRunner(
private val timeout: Long = 60,
private val timeoutUnit: TimeUnit = TimeUnit.MINUTES
) : ProcessRunner {
override fun run(workingDirectory: File, command: String) {
val result = ProcessBuilder(*command.split(" ").toTypedArray())
.directory(workingDirectory)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.start()
.waitFor(timeout, timeoutUnit)
if (!result) {
throw TimeoutException("The command have not finished in $timeout ${timeoutUnit}! `$command`")
}
}
}

View file

@ -0,0 +1,24 @@
package org.fnives.android.test.dockerfile.util
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader
/**
* Helper function to read contents of files baked into the resources folder
*/
internal fun Any.resourceFileContent(filePath: String): String = try {
BufferedReader(InputStreamReader(resourceFileStream(filePath)))
.readLines().joinToString("\n")
} catch (nullPointerException: NullPointerException) {
throw IllegalArgumentException("$filePath file not found!", nullPointerException)
}
/**
* Helper function to access contents of files baked into the resources folder
*/
internal fun Any.resourceFileStream(filePath: String): InputStream = try {
this.javaClass.classLoader.getResourceAsStream(filePath)!!
} catch (nullPointerException: NullPointerException) {
throw IllegalArgumentException("$filePath file not found!", nullPointerException)
}

View file

@ -0,0 +1,9 @@
package org.fnives.android.test.dockerfile.util.clock
/**
* Simple Utility interface providing the [currentTimeMillis].
*/
interface Clock {
fun currentTimeMillis(): Long
}

View file

@ -0,0 +1,9 @@
package org.fnives.android.test.dockerfile.util.clock
/**
* Actual [Clock] returning the System time.
*/
object SystemCock : Clock {
override fun currentTimeMillis(): Long = System.currentTimeMillis()
}

View file

@ -0,0 +1,8 @@
package org.fnives.android.test.dockerfile.write
/**
* Describes [DockerFileContent][org.fnives.android.test.dockerfile.generator.DockerFileContent] which was written into a file.
* @param filePath is the absolut path of the File
* @param tag is the id of the DockerImage.
*/
data class DockerFileDescriptor(val filePath: String, val tag: String)

View file

@ -0,0 +1,21 @@
package org.fnives.android.test.dockerfile.write
import org.fnives.android.test.dockerfile.generator.DockerFileContent
import java.io.File
/**
* Simple configured class which can write a [DockerFileContent] into a file
* within the given [outputFolder] configuration, returns [DockerFileDescriptor] describing the written file.
*/
class DockerFileWriter(private val outputFolder: File) {
fun write(dockerFile: DockerFileContent): DockerFileDescriptor {
outputFolder.mkdirs()
val file = File(outputFolder, dockerFile.name)
file.createNewFile()
file.writeText(dockerFile.content)
return DockerFileDescriptor(filePath = file.absolutePath, tag = dockerFile.tag)
}
}

View file

@ -0,0 +1,16 @@
package org.fnives.android.test.dockerfile.write
import org.fnives.android.test.dockerfile.util.resourceFileStream
import java.io.File
class ScriptCopier(private val outputFolder: File, private val scriptName: String) {
fun invoke() {
outputFolder.mkdirs()
val outputScript = File(outputFolder, scriptName)
outputScript.createNewFile()
outputScript.outputStream().use {
resourceFileStream(scriptName).copyTo(it)
}
}
}

View file

@ -0,0 +1,8 @@
FROM {{IMG-TAG}}
# {{GENERATED_AT}}
ENV EMULATOR_API_LEVEL {{API_LEVEL}}
RUN /usr/local/bin/androidemulatorstart --buildOnly
CMD ["/bin/bash"]

View file

@ -0,0 +1,71 @@
FROM openjdk:11-jdk-slim
# {{GENERATED_AT}}
# installing usual required tools
# build-essential, ruby and bundler is needed for fastlane
RUN apt-get update && apt-get install -y \
sudo \
wget \
curl \
unzip \
android-sdk \
build-essential \
ruby-full && \
gem install bundler
# versions
# latest command lines version here: https://developer.android.com/studio#command-tools
ENV ANDROID_COMMAND_LINES_TOOLS_VERSION "{{COMMAND_LINE_TOOLS}}"
# gradle version can be found in projects gradle-wrapper.properties
ENV GRADLE_VERSION "{{GRADLE_VERSION}}"
# set homes and paths
ENV ANDROID_HOME "/usr/lib/android-sdk"
ENV ANDROID_SDK_ROOT $ANDROID_HOME
ENV CMDLINE_TOOLS_ROOT "${ANDROID_HOME}/cmdline-tools/latest/bin"
ENV AVD_HOME "/root/.android/avd"
ENV PATH "$ANDROID_HOME/cmdline-tools/latest/bin:${PATH}"
ENV PATH "$ANDROID_HOME/emulator:${PATH}"
ENV PATH "/usr/local/gradle-${GRADLE_VERSION}/bin:${PATH}"
WORKDIR /root/
# install gradle
RUN curl -sSL -o /tmp/gradle.zip https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip && \
unzip -d /usr/local /tmp/gradle.zip && \
rm -rf /tmp/gradle.zip && \
echo "1" | gradle init && \
/root/gradlew && \
rm -rf /root/* && \
rm /root/.gitignore
# install command line tools
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
unzip commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip -d cmdline-tools && \
rm commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
mv cmdline-tools/cmdline-tools/ cmdline-tools/latest && \
mv cmdline-tools $ANDROID_HOME/
# install emulator and setup sdkmanager
RUN yes | sdkmanager --licenses > /dev/null && \
sdkmanager --install emulator --channel=0 > /dev/null
# support libraries and google play services
RUN echo y | sdkmanager "extras;android;m2repository" && \
echo y | sdkmanager "extras;google;m2repository" && \
echo y | sdkmanager "extras;google;google_play_services"
# build tools versions and sdk versions can be found via sdkmanager --list and filtering it
# install build-tools
# example: `echo y | sdkmanager "build-tools;33.0.0"`
RUN {{BUILD_TOOLS_INSTALL_COMMANDS}}
# install sdks
# example: `echo y | sdkmanager "platforms;android-33"`
RUN {{SDK_INSTALL_COMMANDS}}
# copy script to install sdk, create emulator and boot it
COPY ./startemulator /usr/local/bin/androidemulatorstart
RUN chmod 744 /usr/local/bin/androidemulatorstart
CMD ["/bin/bash"]

View file

@ -0,0 +1,105 @@
#!/bin/bash
if [[ "$1" == "--help" ]]
then
echo "Creates default emulator and starts it."
echo "API level and Target can be customized, for more customization use the avdmanager directly!"
echo "API_level is read from EMULATOR_API_LEVEL variable"
echo "target is read from EMULATOR_TARGET variable or defaults to target"
echo "--buildOnly parameter means the emulator will be created, but not started"
exit 0;
fi
if [[ -z $EMULATOR_API_LEVEL ]]
then
echo "EMULATOR_API_LEVEL not set!" >&2
exit 1;
fi
if [[ -z $EMULATOR_TARGET ]]
then
if [[ $EMULATOR_API_LEVEL -ge 32 ]]
then
echo "EMULATOR_TARGET not set using \"google_apis\", \"default\" doesn't exists above 32"
EMULATOR_TARGET="google_apis"
else
echo "EMULATOR_TARGET not set using \"default\"" >&2
EMULATOR_TARGET="default"
fi
fi
if [[ -z $AVD_HOME ]]
then
AVD_HOME="$HOME/.android/avd"
echo "AVD_HOME not set, using fallback: $AVD_HOME"
fi
if [[ "$1" == "--buildOnly" ]]
then
echo "INFO: --buildOnly found, emulator will be created but not started!" >&2
fi
echo "INFO: installing sdk" >&2
sdkmanager --install "system-images;android-$EMULATOR_API_LEVEL;$EMULATOR_TARGET;x86_64" --channel=0 > /dev/null 2> /dev/null
echo "INFO: creating emulator" >&2
EMULATOR_NAME="test_$EMULATOR_API_LEVEL"
echo "no" | avdmanager create avd -n "$EMULATOR_NAME" --abi "$EMULATOR_TARGET/x86_64" --package "system-images;android-$EMULATOR_API_LEVEL;$EMULATOR_TARGET;x86_64"
echo "INFO: config of emulator:" >&2
cat $AVD_HOME/$EMULATOR_NAME.avd/config.ini >&2
echo "INFO: modifying config of emulator..." >&2
echo 'disk.dataPartition.size=2G\n' >> $AVD_HOME/$EMULATOR_NAME.avd/config.ini
if [[ "$1" == "--buildOnly" ]]
then
echo "INFO: emulator created!" >&2
exit 0;
fi
echo "INFO: checking devices, expecting nothing..." >&2
adb devices
echo "INFO: starting emulator..." >&2
emulator -avd $EMULATOR_NAME -no-snapshot-save -no-window -gpu off -noaudio -no-boot-anim -camera-back none > emulator.log &
echo "INFO: waiting for emulator..." >&2
sleep 30s
echo "INFO: emulator logs" >&2
cat emulator.log
echo "INFO: checking devices..." >&2
adb devices
echo "INFO: waiting for emulator to be booted..." >&2
BOOT_WAIT_LOG_MESSAGE="INFO: still waiting for emulator to boot"
SECONDS_PASSED=0;
BOOT_COMPLETE=`adb shell getprop sys.boot_completed`
while [[ -z $BOOT_COMPLETE ]];
do
sleep $BOOT_DELAY_IN_SECONDS;
SECONDS_PASSED=$(( $SECONDS_PASSED+$BOOT_DELAY_IN_SECONDS ));
echo "INFO: still waiting for emulator to boot ($SECONDS_PASSED seconds passed)"; >&2
BOOT_COMPLETE=`adb shell getprop sys.boot_completed`
done;
echo "INFO: emulator booted." >&2
# alternative: https://raw.githubusercontent.com/travis-ci/travis-cookbooks/0f497eb71291b52a703143c5cd63a217c8766dc9/community-cookbooks/android-sdk/files/default/android-wait-for-emulator
echo "INFO: clearing animations..." >&2
adb shell settings put global window_animation_scale 0.0
adb shell settings put global transition_animation_scale 0.0
adb shell settings put global animator_duration_scale 0.0
echo "INFO: waiting for emulator have sdk property..." >&2
SDK_VERSION=`adb shell getprop ro.build.version.sdk`
while [[ -z $SDK_VERSION ]];
do
sleep $BOOT_DELAY_IN_SECONDS;
echo "INFO: still waiting for emulator to have SDK property"; >&2
SDK_VERSION=`adb shell getprop ro.build.version.sdk`
done;
echo "INFO: unlocking screen..." >&2
adb shell input keyevent 82
echo "INFO: emulator ready!" >&2

View file

@ -0,0 +1,56 @@
package org.fnives.android.test.dockerfile.configuration
import org.fnives.android.test.dockerfile.util.resourceFileAsInputStream
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class ConfigurationParserTest {
private lateinit var parser: ConfigurationParser
@BeforeEach
fun setUp() {
parser = ConfigurationParser()
}
@Test
fun example() {
val expectedConfiguration = Configuration(
buildTools = setOf("33.0.0", "32.0.0", "31.0.0", "30.0.3", "30.0.2"),
sdks = setOf(30, 31, 32, 33),
commandlineTools = "8512546_latest",
gradleVersion = "7.3.3",
output = "outputs",
dockerRepository = "fknives",
dockerNamespace = "android-test-img",
dockerTagPrefix = "1.0.0",
apiLevelVariations = setOf(21, 22, 23)
)
val rawConfiguration = resourceFileAsInputStream("example_configuration.toml")
val actual = parser.invoke(rawConfiguration)
Assertions.assertEquals(expectedConfiguration, actual)
}
@Test
fun singleConfiguration() {
val expectedConfiguration = Configuration(
buildTools = setOf("33.0.0", "32.0.0", "31.0.0"),
sdks = setOf(32, 33),
commandlineTools = "8512546_latest",
gradleVersion = "7.3.3",
output = "outputs",
dockerRepository = "fknives",
dockerNamespace = "android-test-img",
dockerTagPrefix = "1.0.0",
apiLevelVariations = emptySet()
)
val rawConfiguration = resourceFileAsInputStream("single_configuration.toml")
val actual = parser.invoke(rawConfiguration)
Assertions.assertEquals(expectedConfiguration, actual)
}
}

View file

@ -0,0 +1,60 @@
package org.fnives.android.test.dockerfile.generator
import org.fnives.android.test.dockerfile.configuration.ImageConfiguration
import org.fnives.android.test.dockerfile.util.FixedClock
import org.fnives.android.test.dockerfile.util.readResourceFile
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class DockerFileContentGeneratorTest {
private lateinit var generator: DockerFileContentGenerator
@BeforeEach
fun setup() {
generator = DockerFileContentGenerator(
config = DockerFileContentConfig(
templateGeneric = readResourceFile("Dockerfile.template"),
templateSpecific = readResourceFile("Dockerfile.api.template"),
dockerRepository = "fknives",
dockerTagPrefix = "1.0",
dockerNamespace = "ati"
),
clock = FixedClock(year = 2021, month = 11, dayOfMonth = 15)
)
}
@Test
fun generic() {
val expected = DockerFileContent(
content = readResourceFile("expected_generic_dockerfile"),
name = "Dockerfile",
tag = "fknives/ati:1.0"
)
val config = ImageConfiguration.Generic(
buildTools = setOf("30.0.0", "32.0.0", "31.0.0", "30.0.3", "30.0.2"),
sdks = setOf(33, 32, 31, 30),
commandlineTools = "8512546_latest",
gradleVersion = "7.3.3"
)
val actual = generator.create(config)
Assertions.assertEquals(expected, actual)
}
@Test
fun api27() {
val expected = DockerFileContent(
content = readResourceFile("expected_api_27_dockerfile"),
name = "Dockerfile-api-27",
tag = "fknives/ati:1.0-api-27"
)
val config = ImageConfiguration.ApiVersion(27)
val actual = generator.create(config)
Assertions.assertEquals(expected, actual)
}
}

View file

@ -0,0 +1,148 @@
package org.fnives.android.test.dockerfile.integration
import org.fnives.android.test.dockerfile.Main
import org.fnives.android.test.dockerfile.di.ServiceLocatorHolder
import org.fnives.android.test.dockerfile.util.CapturingProcessRunner
import org.fnives.android.test.dockerfile.util.FixedClock
import org.fnives.android.test.dockerfile.util.OverrideableClockServiceLocator
import org.fnives.android.test.dockerfile.util.assertCollectionsEquals
import org.fnives.android.test.dockerfile.util.readResourceFile
import org.fnives.android.test.dockerfile.util.workDirAbsolutePathWithCommands
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import java.io.File
class IntegrationTest {
@TempDir
lateinit var tempDir: File
private lateinit var capturingProcessRunner: CapturingProcessRunner
@BeforeEach
fun setup() {
capturingProcessRunner = CapturingProcessRunner()
val mockServiceLocator = OverrideableClockServiceLocator()
mockServiceLocator.clock = FixedClock(year = 2016, month = 5, dayOfMonth = 28)
mockServiceLocator.processRunner = capturingProcessRunner
ServiceLocatorHolder.swap(mockServiceLocator)
}
@AfterEach
fun tearDown() {
ServiceLocatorHolder.reset()
}
@Test
fun noArgumentsGivesError() {
val expectedMessage = "Required argument is missing! Required argument is the config.toml file path and just that!"
val actual = Assertions.assertThrows(IllegalArgumentException::class.java) {
Main.main(emptyArray())
}
Assertions.assertEquals(expectedMessage, actual.message)
}
@Test
fun tooManyArgumentsGivesError() {
val expectedMessage = "More arguments than expected! Required argument is the config.toml file path and just that!"
val actual = Assertions.assertThrows(IllegalArgumentException::class.java) {
Main.main(arrayOf("", ""))
}
Assertions.assertEquals(expectedMessage, actual.message)
}
@Test
fun nonExistentConfigurationFileGivesError() {
val nonExistentFile = File(tempDir, "alma")
val actual = Assertions.assertThrows(IllegalArgumentException::class.java) {
Main.main(arrayOf(nonExistentFile.absolutePath))
}
Assertions.assertEquals("Can't find file at given path: ${nonExistentFile.absolutePath}", actual.message)
}
@Test
fun cannotParseConfigFile() {
val file = File(tempDir, "alma")
file.createNewFile()
val actual = Assertions.assertThrows(IllegalArgumentException::class.java) {
Main.main(arrayOf(file.absolutePath))
}
Assertions.assertEquals("Unparseable file at given path: ${file.absolutePath}", actual.message)
Assertions.assertNotNull(actual.cause)
}
@Test
fun singleConfiguration() {
val file = File(tempDir, "config.toml")
val outputDir = File(tempDir, "output")
outputDir.mkdir()
val updatedConfiguration = readResourceFile(resourceName = "single_configuration.toml")
.replace("output = \"outputs\"", "output = \"${outputDir.absolutePath}\"")
file.writeText(updatedConfiguration)
val expectedDockerCommands = listOf(
outputDir.absolutePath to "docker build -t fknives/android-test-img:1.0.0 -f ${File(outputDir, "Dockerfile").absolutePath} .",
outputDir.absolutePath to "docker push fknives/android-test-img:1.0.0",
outputDir.absolutePath to "docker image rm -f fknives/android-test-img:1.0.0"
)
val expectedDockerFileContent = readResourceFile("expected_single_configuration.Dockerfile")
val expectedScriptContent = readResourceFile("startemulator")
Main.main(arrayOf(file.absolutePath))
val actualDockerfileContent = File(outputDir, "Dockerfile").readText()
val actualScriptFileContent = File(outputDir, "startemulator").readText()
assertCollectionsEquals(expectedDockerCommands, capturingProcessRunner.workDirAbsolutePathWithCommands)
Assertions.assertEquals(expectedDockerFileContent, actualDockerfileContent)
Assertions.assertEquals(expectedScriptContent, actualScriptFileContent)
}
@Test
fun multiApiConfiguration() {
val file = File(tempDir, "config.toml")
val outputDir = File(tempDir, "output")
outputDir.mkdir()
val updatedConfiguration = readResourceFile(resourceName = "multi_api_configuration.toml")
.replace("output = \"outputs\"", "output = \"${outputDir.absolutePath}\"")
file.writeText(updatedConfiguration)
val expectedDockerCommands = listOf(
outputDir.absolutePath to "docker build -t fknives2/android-test-img-multi:1.0.1 -f ${File(outputDir, "Dockerfile").absolutePath} .",
outputDir.absolutePath to "docker push fknives2/android-test-img-multi:1.0.1",
outputDir.absolutePath to "docker image rm -f fknives2/android-test-img-multi:1.0.1",
outputDir.absolutePath to "docker build -t fknives2/android-test-img-multi:1.0.1-api-22 -f ${File(outputDir, "Dockerfile-api-22").absolutePath} .",
outputDir.absolutePath to "docker push fknives2/android-test-img-multi:1.0.1-api-22",
outputDir.absolutePath to "docker image rm -f fknives2/android-test-img-multi:1.0.1-api-22",
outputDir.absolutePath to "docker build -t fknives2/android-test-img-multi:1.0.1-api-30 -f ${File(outputDir, "Dockerfile-api-30").absolutePath} .",
outputDir.absolutePath to "docker push fknives2/android-test-img-multi:1.0.1-api-30",
outputDir.absolutePath to "docker image rm -f fknives2/android-test-img-multi:1.0.1-api-30"
)
val expectedDockerFileContent = readResourceFile("expected_multi_api_configuration.Dockerfile")
val expectedDocker22FileContent = readResourceFile("expected_multi_api_configuration.22.Dockerfile")
val expectedDocker30FileContent = readResourceFile("expected_multi_api_configuration.30.Dockerfile")
val expectedScriptContent = readResourceFile("startemulator")
Main.main(arrayOf(file.absolutePath))
val actualDockerfileContent = File(outputDir, "Dockerfile").readText()
val actualDocker22fileContent = File(outputDir, "Dockerfile-api-22").readText()
val actualDocker30fileContent = File(outputDir, "Dockerfile-api-30").readText()
val actualScriptFileContent = File(outputDir, "startemulator").readText()
assertCollectionsEquals(expectedDockerCommands, capturingProcessRunner.workDirAbsolutePathWithCommands)
Assertions.assertEquals(expectedDockerFileContent, actualDockerfileContent)
Assertions.assertEquals(expectedDocker22FileContent, actualDocker22fileContent)
Assertions.assertEquals(expectedDocker30FileContent, actualDocker30fileContent)
Assertions.assertEquals(expectedScriptContent, actualScriptFileContent)
}
}

View file

@ -0,0 +1,96 @@
package org.fnives.android.test.dockerfile.push
import org.fnives.android.test.dockerfile.util.CapturingProcessRunner
import org.fnives.android.test.dockerfile.util.assertCollectionsEquals
import org.fnives.android.test.dockerfile.util.workDirAbsolutePathWithCommands
import org.fnives.android.test.dockerfile.write.DockerFileDescriptor
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.io.File
internal class DockerBuildAndPushTest {
private lateinit var sut: DockerBuildAndPush
private lateinit var capturingProcessRunner: CapturingProcessRunner
private lateinit var expectedFolder: String
@BeforeEach
fun setup() {
capturingProcessRunner = CapturingProcessRunner(
defaultReturnValue = true,
fallbackToDefault = true
)
val folderName = "folder"
expectedFolder = File(folderName).absolutePath
sut = DockerBuildAndPush(
config = PushConfig(
dockerRepository = "repo",
dockerNamespace = "name",
output = folderName
),
processRunner = capturingProcessRunner
)
}
@Test
fun verifyBuildCommand() {
val expected = listOf(expectedFolder to "docker build -t 123 -f /banan/alma .")
sut.build(DockerFileDescriptor("/banan/alma", "123"))
val actual = capturingProcessRunner.workDirAbsolutePathWithCommands
assertCollectionsEquals(expected, actual)
}
@Test
fun verifyPushCommand() {
val expected = listOf(expectedFolder to "docker push id")
sut.push("id")
val actual = capturingProcessRunner.workDirAbsolutePathWithCommands
assertCollectionsEquals(expected, actual)
}
@Test
fun verifyCleanCommand() {
val expected = listOf(expectedFolder to "docker image rm -f id")
sut.removeLocalImage("id")
val actual = capturingProcessRunner.workDirAbsolutePathWithCommands
assertCollectionsEquals(expected, actual)
}
@Test
fun verifyBuildPushClean() {
val expected = listOf(
expectedFolder to "docker build -t 123 -f banan/alma .",
expectedFolder to "docker push 123",
expectedFolder to "docker image rm -f 123"
)
sut.buildPushClean(DockerFileDescriptor("banan/alma", "123"))
val actual = capturingProcessRunner.workDirAbsolutePathWithCommands
assertCollectionsEquals(expected, actual)
}
@Test
fun verifyMultipleBuildPushClean() {
val expected = listOf(
expectedFolder to "docker build -t 123 -f banan/alma .",
expectedFolder to "docker push 123",
expectedFolder to "docker image rm -f 123",
expectedFolder to "docker build -t fknives/421:512.3 -f banan/alma2 .",
expectedFolder to "docker push fknives/421:512.3",
expectedFolder to "docker image rm -f fknives/421:512.3"
)
sut.buildPushClean(DockerFileDescriptor("banan/alma", "123"))
sut.buildPushClean(DockerFileDescriptor("banan/alma2", "fknives/421:512.3"))
val actual = capturingProcessRunner.workDirAbsolutePathWithCommands
assertCollectionsEquals(expected, actual)
}
}

View file

@ -0,0 +1,7 @@
package org.fnives.android.test.dockerfile.util
import org.junit.jupiter.api.Assertions
inline fun <reified T> assertCollectionsEquals(expected: Collection<T>, actual: Collection<T>) {
Assertions.assertArrayEquals(expected.toTypedArray(), actual.toTypedArray())
}

View file

@ -0,0 +1,17 @@
package org.fnives.android.test.dockerfile.util
import org.fnives.android.test.dockerfile.push.ProcessRunner
import java.io.File
class CapturingProcessRunner : ProcessRunner {
private val _receivedCommands = mutableListOf<Pair<File, String>>()
val receivedCommands get() = _receivedCommands.toList()
override fun run(workingDirectory: File, command: String) {
_receivedCommands.add(workingDirectory to command)
}
}
val CapturingProcessRunner.workDirAbsolutePathWithCommands: List<Pair<String, String>>
get() = receivedCommands.map { (workdir, command) -> workdir.absolutePath to command }

View file

@ -0,0 +1,18 @@
package org.fnives.android.test.dockerfile.util
import org.fnives.android.test.dockerfile.util.clock.Clock
import java.util.Calendar
class FixedClock(val timeMillis: Long) : Clock {
override fun currentTimeMillis(): Long = timeMillis
}
fun FixedClock(year: Int, month: Int, dayOfMonth: Int): Clock {
val calendar = Calendar.getInstance()
calendar.set(Calendar.YEAR, year)
calendar.set(Calendar.MONTH, month - 1 + Calendar.JANUARY)
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
return FixedClock(calendar.timeInMillis)
}

View file

@ -0,0 +1,32 @@
package org.fnives.android.test.dockerfile.util
import org.fnives.android.test.dockerfile.configuration.Configuration
import org.fnives.android.test.dockerfile.configuration.ConfigurationParser
import org.fnives.android.test.dockerfile.di.ConfiguredServiceLocator
import org.fnives.android.test.dockerfile.di.ServiceLocator
import org.fnives.android.test.dockerfile.di.ServiceLocatorHolder
import org.fnives.android.test.dockerfile.push.ProcessRunner
import org.fnives.android.test.dockerfile.util.clock.Clock
class OverrideableClockServiceLocator(private val delegate: ServiceLocator = ServiceLocatorHolder.default) : ServiceLocator by delegate {
var clock: Clock? = null
var configuredServiceLocator: ConfiguredServiceLocator? = null
var configurationParser: ConfigurationParser? = null
var processRunner: ProcessRunner? = null
var scriptToCopy: String? = null
override fun clock(): Clock = clock ?: delegate.clock()
override fun loadConfiguration(configuration: Configuration): ConfiguredServiceLocator =
configuredServiceLocator ?: delegate.loadConfiguration(configuration)
override fun parser(): ConfigurationParser =
configurationParser ?: delegate.parser()
override fun processRunner(): ProcessRunner =
processRunner ?: delegate.processRunner()
override fun scriptToCopy(): String =
scriptToCopy ?: delegate.scriptToCopy()
}

View file

@ -0,0 +1,19 @@
package org.fnives.android.test.dockerfile.util
import java.io.BufferedReader
import java.io.File
import java.io.InputStream
import java.io.InputStreamReader
internal fun Any.readResourceFile(resourceName: String): String = try {
BufferedReader(InputStreamReader(resourceFileAsInputStream(resourceName)))
.readLines().joinToString("\n")
} catch (nullPointerException: NullPointerException) {
throw IllegalArgumentException("$resourceName file not found!", nullPointerException)
}
internal fun Any.resourceFileAsInputStream(resourceName: String): InputStream = try {
this.javaClass.classLoader.getResourceAsStream(resourceName)!!
} catch (nullPointerException: NullPointerException) {
throw IllegalArgumentException("$resourceName file not found!", nullPointerException)
}

View file

@ -0,0 +1,47 @@
package org.fnives.android.test.dockerfile.write
import org.fnives.android.test.dockerfile.generator.DockerFileContent
import org.fnives.android.test.dockerfile.util.assertCollectionsEquals
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import java.io.File
internal class DockerFileWriterTest {
@TempDir
lateinit var tempDirectory: File
@Test
fun createsFolder() {
val folderToCreate = File(tempDirectory, "childFolder")
val sut = DockerFileWriter(outputFolder = folderToCreate)
sut.write(DockerFileContent(name = "alma", content = "-", tag = "123"))
Assertions.assertTrue(folderToCreate.exists()) { "Temp folder does not exist!" }
Assertions.assertFalse(folderToCreate.isFile) { "Temp folder is a file not a Folder!" }
}
@Test
fun writesFileProperly() {
val sut = DockerFileWriter(outputFolder = tempDirectory)
val expected = listOf("123", "abc")
sut.write(DockerFileContent(name = "alma", content = "123\nabc", tag = "123"))
val actual = File(tempDirectory, "alma")
assertCollectionsEquals(expected, actual.readLines())
}
@Test
fun assertReturnsCorrectDescriptor() {
val sut = DockerFileWriter(outputFolder = tempDirectory)
val expectedFile = File(tempDirectory, "alma")
val expected = DockerFileDescriptor(filePath = expectedFile.absolutePath, tag = "123")
val actual = sut.write(DockerFileContent(name = "alma", content = "123\nabc", tag = "123"))
Assertions.assertEquals(expected, actual)
}
}

View file

@ -0,0 +1,53 @@
package org.fnives.android.test.dockerfile.write
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import java.io.File
internal class ScriptCopierTest {
@TempDir
lateinit var tempDirectory: File
@Test
fun copyExample() {
val sut = ScriptCopier(
outputFolder = tempDirectory,
scriptName = "example_to_copy"
)
val actual = File(tempDirectory, "example_to_copy")
sut.invoke()
val expected = arrayOf("123", "ab", "456")
Assertions.assertArrayEquals(expected, actual.readLines().toTypedArray())
}
@Test
fun checkActualCopyIsNotEmpty() {
val sut = ScriptCopier(
outputFolder = tempDirectory,
scriptName = "startemulator"
)
val actual = File(tempDirectory, "startemulator")
sut.invoke()
Assertions.assertTrue(actual.readLines().isNotEmpty()) { "Content is empty when trying to copy startemulator" }
}
@Test
fun createsFolder() {
val folderToCreate = File(tempDirectory, "childFolder")
val sut = ScriptCopier(
outputFolder = folderToCreate,
scriptName = "example_to_copy"
)
sut.invoke()
Assertions.assertTrue(folderToCreate.exists()) { "Temp folder does not exist!" }
Assertions.assertFalse(folderToCreate.isFile) { "Temp folder is a file not a Folder!" }
}
}

View file

@ -0,0 +1,11 @@
buildTools = ["33.0.0", "32.0.0", "31.0.0", "30.0.3", "30.0.2"]
sdks = [30, 31, 32, 33]
androidCommandlineTools = "8512546_latest"
gradleVersion = "7.3.3"
output = "outputs"
[variations]
apiLevels = [21, 22, 23]
[image]
repository = "fknives"
namespace = "android-test-img"
tagPrefix = "1.0.0"

View file

@ -0,0 +1,3 @@
123
ab
456

View file

@ -0,0 +1,8 @@
FROM fknives/ati:1.0
# Generated on 2021-11-15
ENV EMULATOR_API_LEVEL 27
RUN /usr/local/bin/androidemulatorstart --buildOnly
CMD ["/bin/bash"]

View file

@ -0,0 +1,78 @@
FROM openjdk:11-jdk-slim
# Generated on 2021-11-15
# installing usual required tools
# build-essential, ruby and bundler is needed for fastlane
RUN apt-get update && apt-get install -y \
sudo \
wget \
curl \
unzip \
android-sdk \
build-essential \
ruby-full && \
gem install bundler
# versions
# latest command lines version here: https://developer.android.com/studio#command-tools
ENV ANDROID_COMMAND_LINES_TOOLS_VERSION "8512546_latest"
# gradle version can be found in projects gradle-wrapper.properties
ENV GRADLE_VERSION "7.3.3"
# set homes and paths
ENV ANDROID_HOME "/usr/lib/android-sdk"
ENV ANDROID_SDK_ROOT $ANDROID_HOME
ENV CMDLINE_TOOLS_ROOT "${ANDROID_HOME}/cmdline-tools/latest/bin"
ENV AVD_HOME "/root/.android/avd"
ENV PATH "$ANDROID_HOME/cmdline-tools/latest/bin:${PATH}"
ENV PATH "$ANDROID_HOME/emulator:${PATH}"
ENV PATH "/usr/local/gradle-${GRADLE_VERSION}/bin:${PATH}"
WORKDIR /root/
# install gradle
RUN curl -sSL -o /tmp/gradle.zip https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip && \
unzip -d /usr/local /tmp/gradle.zip && \
rm -rf /tmp/gradle.zip && \
echo "1" | gradle init && \
/root/gradlew && \
rm -rf /root/* && \
rm /root/.gitignore
# install command line tools
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
unzip commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip -d cmdline-tools && \
rm commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
mv cmdline-tools/cmdline-tools/ cmdline-tools/latest && \
mv cmdline-tools $ANDROID_HOME/
# install emulator and setup sdkmanager
RUN yes | sdkmanager --licenses > /dev/null && \
sdkmanager --install emulator --channel=0 > /dev/null
# support libraries and google play services
RUN echo y | sdkmanager "extras;android;m2repository" && \
echo y | sdkmanager "extras;google;m2repository" && \
echo y | sdkmanager "extras;google;google_play_services"
# build tools versions and sdk versions can be found via sdkmanager --list and filtering it
# install build-tools
# example: `echo y | sdkmanager "build-tools;33.0.0"`
RUN echo y | sdkmanager "build-tools;30.0.0" && \
echo y | sdkmanager "build-tools;32.0.0" && \
echo y | sdkmanager "build-tools;31.0.0" && \
echo y | sdkmanager "build-tools;30.0.3" && \
echo y | sdkmanager "build-tools;30.0.2"
# install sdks
# example: `echo y | sdkmanager "platforms;android-33"`
RUN echo y | sdkmanager "platforms;android-33" && \
echo y | sdkmanager "platforms;android-32" && \
echo y | sdkmanager "platforms;android-31" && \
echo y | sdkmanager "platforms;android-30"
# copy script to install sdk, create emulator and boot it
COPY ./startemulator /usr/local/bin/androidemulatorstart
RUN chmod 744 /usr/local/bin/androidemulatorstart
CMD ["/bin/bash"]

View file

@ -0,0 +1,8 @@
FROM fknives2/android-test-img-multi:1.0.1
# Generated on 2016-05-28
ENV EMULATOR_API_LEVEL 22
RUN /usr/local/bin/androidemulatorstart --buildOnly
CMD ["/bin/bash"]

View file

@ -0,0 +1,8 @@
FROM fknives2/android-test-img-multi:1.0.1
# Generated on 2016-05-28
ENV EMULATOR_API_LEVEL 30
RUN /usr/local/bin/androidemulatorstart --buildOnly
CMD ["/bin/bash"]

View file

@ -0,0 +1,73 @@
FROM openjdk:11-jdk-slim
# Generated on 2016-05-28
# installing usual required tools
# build-essential, ruby and bundler is needed for fastlane
RUN apt-get update && apt-get install -y \
sudo \
wget \
curl \
unzip \
android-sdk \
build-essential \
ruby-full && \
gem install bundler
# versions
# latest command lines version here: https://developer.android.com/studio#command-tools
ENV ANDROID_COMMAND_LINES_TOOLS_VERSION "8512546_latest"
# gradle version can be found in projects gradle-wrapper.properties
ENV GRADLE_VERSION "7.3.3"
# set homes and paths
ENV ANDROID_HOME "/usr/lib/android-sdk"
ENV ANDROID_SDK_ROOT $ANDROID_HOME
ENV CMDLINE_TOOLS_ROOT "${ANDROID_HOME}/cmdline-tools/latest/bin"
ENV AVD_HOME "/root/.android/avd"
ENV PATH "$ANDROID_HOME/cmdline-tools/latest/bin:${PATH}"
ENV PATH "$ANDROID_HOME/emulator:${PATH}"
ENV PATH "/usr/local/gradle-${GRADLE_VERSION}/bin:${PATH}"
WORKDIR /root/
# install gradle
RUN curl -sSL -o /tmp/gradle.zip https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip && \
unzip -d /usr/local /tmp/gradle.zip && \
rm -rf /tmp/gradle.zip && \
echo "1" | gradle init && \
/root/gradlew && \
rm -rf /root/* && \
rm /root/.gitignore
# install command line tools
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
unzip commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip -d cmdline-tools && \
rm commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
mv cmdline-tools/cmdline-tools/ cmdline-tools/latest && \
mv cmdline-tools $ANDROID_HOME/
# install emulator and setup sdkmanager
RUN yes | sdkmanager --licenses > /dev/null && \
sdkmanager --install emulator --channel=0 > /dev/null
# support libraries and google play services
RUN echo y | sdkmanager "extras;android;m2repository" && \
echo y | sdkmanager "extras;google;m2repository" && \
echo y | sdkmanager "extras;google;google_play_services"
# build tools versions and sdk versions can be found via sdkmanager --list and filtering it
# install build-tools
# example: `echo y | sdkmanager "build-tools;33.0.0"`
RUN echo y | sdkmanager "build-tools;33.0.0" && \
echo y | sdkmanager "build-tools;32.0.0"
# install sdks
# example: `echo y | sdkmanager "platforms;android-33"`
RUN echo y | sdkmanager "platforms;android-30" && \
echo y | sdkmanager "platforms;android-31"
# copy script to install sdk, create emulator and boot it
COPY ./startemulator /usr/local/bin/androidemulatorstart
RUN chmod 744 /usr/local/bin/androidemulatorstart
CMD ["/bin/bash"]

View file

@ -0,0 +1,74 @@
FROM openjdk:11-jdk-slim
# Generated on 2016-05-28
# installing usual required tools
# build-essential, ruby and bundler is needed for fastlane
RUN apt-get update && apt-get install -y \
sudo \
wget \
curl \
unzip \
android-sdk \
build-essential \
ruby-full && \
gem install bundler
# versions
# latest command lines version here: https://developer.android.com/studio#command-tools
ENV ANDROID_COMMAND_LINES_TOOLS_VERSION "8512546_latest"
# gradle version can be found in projects gradle-wrapper.properties
ENV GRADLE_VERSION "7.3.3"
# set homes and paths
ENV ANDROID_HOME "/usr/lib/android-sdk"
ENV ANDROID_SDK_ROOT $ANDROID_HOME
ENV CMDLINE_TOOLS_ROOT "${ANDROID_HOME}/cmdline-tools/latest/bin"
ENV AVD_HOME "/root/.android/avd"
ENV PATH "$ANDROID_HOME/cmdline-tools/latest/bin:${PATH}"
ENV PATH "$ANDROID_HOME/emulator:${PATH}"
ENV PATH "/usr/local/gradle-${GRADLE_VERSION}/bin:${PATH}"
WORKDIR /root/
# install gradle
RUN curl -sSL -o /tmp/gradle.zip https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip && \
unzip -d /usr/local /tmp/gradle.zip && \
rm -rf /tmp/gradle.zip && \
echo "1" | gradle init && \
/root/gradlew && \
rm -rf /root/* && \
rm /root/.gitignore
# install command line tools
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
unzip commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip -d cmdline-tools && \
rm commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
mv cmdline-tools/cmdline-tools/ cmdline-tools/latest && \
mv cmdline-tools $ANDROID_HOME/
# install emulator and setup sdkmanager
RUN yes | sdkmanager --licenses > /dev/null && \
sdkmanager --install emulator --channel=0 > /dev/null
# support libraries and google play services
RUN echo y | sdkmanager "extras;android;m2repository" && \
echo y | sdkmanager "extras;google;m2repository" && \
echo y | sdkmanager "extras;google;google_play_services"
# build tools versions and sdk versions can be found via sdkmanager --list and filtering it
# install build-tools
# example: `echo y | sdkmanager "build-tools;33.0.0"`
RUN echo y | sdkmanager "build-tools;33.0.0" && \
echo y | sdkmanager "build-tools;32.0.0" && \
echo y | sdkmanager "build-tools;31.0.0"
# install sdks
# example: `echo y | sdkmanager "platforms;android-33"`
RUN echo y | sdkmanager "platforms;android-32" && \
echo y | sdkmanager "platforms;android-33"
# copy script to install sdk, create emulator and boot it
COPY ./startemulator /usr/local/bin/androidemulatorstart
RUN chmod 744 /usr/local/bin/androidemulatorstart
CMD ["/bin/bash"]

View file

@ -0,0 +1,11 @@
buildTools = ["33.0.0", "32.0.0"]
sdks = [30, 31]
androidCommandlineTools = "8512546_latest"
gradleVersion = "7.3.3"
output = "outputs"
[variations]
apiLevels = [22, 30]
[image]
repository = "fknives2"
namespace = "android-test-img-multi"
tagPrefix = "1.0.1"

View file

@ -0,0 +1,9 @@
buildTools = ["33.0.0", "32.0.0", "31.0.0"]
sdks = [32, 33]
androidCommandlineTools = "8512546_latest"
gradleVersion = "7.3.3"
output = "outputs"
[image]
repository = "fknives"
namespace = "android-test-img"
tagPrefix = "1.0.0"