renamed project to "Remote Cache Build Server" (RBCS)
This commit is contained in:
101
rbcs-cli/build.gradle
Normal file
101
rbcs-cli/build.gradle
Normal file
@@ -0,0 +1,101 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
alias catalog.plugins.kotlin.jvm
|
||||
alias catalog.plugins.envelope
|
||||
alias catalog.plugins.sambal
|
||||
alias catalog.plugins.graalvm.native.image
|
||||
alias catalog.plugins.graalvm.jlink
|
||||
alias catalog.plugins.jpms.check
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
import net.woggioni.gradle.envelope.EnvelopeJarTask
|
||||
import net.woggioni.gradle.graalvm.NativeImageConfigurationTask
|
||||
import net.woggioni.gradle.graalvm.NativeImagePlugin
|
||||
import net.woggioni.gradle.graalvm.NativeImageTask
|
||||
import net.woggioni.gradle.graalvm.JlinkPlugin
|
||||
import net.woggioni.gradle.graalvm.JlinkTask
|
||||
|
||||
Property<String> mainModuleName = objects.property(String.class)
|
||||
mainModuleName.set('net.woggioni.rbcs.cli')
|
||||
Property<String> mainClassName = objects.property(String.class)
|
||||
mainClassName.set('net.woggioni.rbcs.cli.RemoteBuildCacheServerCli')
|
||||
|
||||
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
|
||||
options.javaModuleMainClass = mainClassName
|
||||
}
|
||||
|
||||
configurations {
|
||||
release {
|
||||
transitive = false
|
||||
canBeConsumed = true
|
||||
canBeResolved = true
|
||||
visible = true
|
||||
}
|
||||
}
|
||||
|
||||
envelopeJar {
|
||||
mainModule = mainModuleName
|
||||
mainClass = mainClassName
|
||||
|
||||
extraClasspath = ["plugins"]
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation catalog.jwo
|
||||
implementation catalog.slf4j.api
|
||||
implementation catalog.netty.codec.http
|
||||
implementation catalog.picocli
|
||||
|
||||
implementation project(':rbcs-client')
|
||||
implementation project(':rbcs-server')
|
||||
|
||||
// runtimeOnly catalog.slf4j.jdk14
|
||||
runtimeOnly catalog.logback.classic
|
||||
// runtimeOnly catalog.slf4j.simple
|
||||
}
|
||||
|
||||
Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named('envelopeJar', EnvelopeJarTask.class) {
|
||||
// systemProperties['java.util.logging.config.class'] = 'net.woggioni.rbcs.LoggingConfig'
|
||||
// systemProperties['log.config.source'] = 'net/woggioni/rbcs/cli/logging.properties'
|
||||
// systemProperties['java.util.logging.config.file'] = 'classpath:net/woggioni/rbcs/cli/logging.properties'
|
||||
systemProperties['logback.configurationFile'] = 'classpath:net/woggioni/rbcs/cli/logback.xml'
|
||||
systemProperties['io.netty.leakDetectionLevel'] = 'DISABLED'
|
||||
|
||||
// systemProperties['org.slf4j.simpleLogger.showDateTime'] = 'true'
|
||||
// systemProperties['org.slf4j.simpleLogger.defaultLogLevel'] = 'debug'
|
||||
// systemProperties['org.slf4j.simpleLogger.log.com.google.code.yanf4j'] = 'warn'
|
||||
// systemProperties['org.slf4j.simpleLogger.log.net.rubyeye.xmemcached'] = 'warn'
|
||||
// systemProperties['org.slf4j.simpleLogger.dateTimeFormat'] = 'yyyy-MM-dd\'T\'HH:mm:ss.SSSZ'
|
||||
}
|
||||
|
||||
tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) {
|
||||
mainClass = mainClassName
|
||||
mainModule = mainModuleName
|
||||
}
|
||||
|
||||
tasks.named(NativeImagePlugin.NATIVE_IMAGE_TASK_NAME, NativeImageTask) {
|
||||
mainClass = mainClassName
|
||||
mainModule = mainModuleName
|
||||
useMusl = true
|
||||
buildStaticImage = true
|
||||
}
|
||||
|
||||
tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) {
|
||||
mainClass = mainClassName
|
||||
mainModule = 'net.woggioni.rbcs.cli'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
release(envelopeJarTaskProvider)
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
artifact envelopeJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
2
rbcs-cli/native-image/native-image.properties
Normal file
2
rbcs-cli/native-image/native-image.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
Args=-H:Optimize=3 --gc=serial --initialize-at-run-time=io.netty
|
||||
#-H:TraceClassInitialization=io.netty.handler.ssl.BouncyCastleAlpnSslUtils
|
17
rbcs-cli/src/main/java/module-info.java
Normal file
17
rbcs-cli/src/main/java/module-info.java
Normal file
@@ -0,0 +1,17 @@
|
||||
module net.woggioni.rbcs.cli {
|
||||
requires org.slf4j;
|
||||
requires net.woggioni.rbcs.server;
|
||||
requires info.picocli;
|
||||
requires net.woggioni.rbcs.common;
|
||||
requires net.woggioni.rbcs.client;
|
||||
requires kotlin.stdlib;
|
||||
requires net.woggioni.jwo;
|
||||
requires net.woggioni.rbcs.api;
|
||||
|
||||
exports net.woggioni.rbcs.cli.impl.converters to info.picocli;
|
||||
opens net.woggioni.rbcs.cli.impl.commands to info.picocli;
|
||||
opens net.woggioni.rbcs.cli.impl to info.picocli;
|
||||
opens net.woggioni.rbcs.cli to info.picocli, net.woggioni.rbcs.common;
|
||||
|
||||
exports net.woggioni.rbcs.cli;
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
package net.woggioni.rbcs.cli
|
||||
|
||||
import net.woggioni.rbcs.cli.impl.AbstractVersionProvider
|
||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||
import net.woggioni.rbcs.cli.impl.commands.BenchmarkCommand
|
||||
import net.woggioni.rbcs.cli.impl.commands.ClientCommand
|
||||
import net.woggioni.rbcs.cli.impl.commands.GetCommand
|
||||
import net.woggioni.rbcs.cli.impl.commands.HealthCheckCommand
|
||||
import net.woggioni.rbcs.cli.impl.commands.PasswordHashCommand
|
||||
import net.woggioni.rbcs.cli.impl.commands.PutCommand
|
||||
import net.woggioni.rbcs.cli.impl.commands.ServerCommand
|
||||
import net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory
|
||||
import net.woggioni.rbcs.common.contextLogger
|
||||
import net.woggioni.jwo.Application
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Model.CommandSpec
|
||||
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "rbcs", versionProvider = RemoteBuildCacheServerCli.VersionProvider::class
|
||||
)
|
||||
class RemoteBuildCacheServerCli : RbcsCommand() {
|
||||
|
||||
class VersionProvider : AbstractVersionProvider()
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun main(vararg args: String) {
|
||||
val currentClassLoader = RemoteBuildCacheServerCli::class.java.classLoader
|
||||
Thread.currentThread().contextClassLoader = currentClassLoader
|
||||
if(currentClassLoader.javaClass.name == "net.woggioni.envelope.loader.ModuleClassLoader") {
|
||||
//We're running in an envelope jar and custom URL protocols won't work
|
||||
RbcsUrlStreamHandlerFactory.install()
|
||||
}
|
||||
val log = contextLogger()
|
||||
val app = Application.builder("rbcs")
|
||||
.configurationDirectoryEnvVar("RBCS_CONFIGURATION_DIR")
|
||||
.configurationDirectoryPropertyKey("net.woggioni.rbcs.conf.dir")
|
||||
.build()
|
||||
val rbcsCli = RemoteBuildCacheServerCli()
|
||||
val commandLine = CommandLine(rbcsCli)
|
||||
commandLine.setExecutionExceptionHandler { ex, cl, parseResult ->
|
||||
log.error(ex.message, ex)
|
||||
CommandLine.ExitCode.SOFTWARE
|
||||
}
|
||||
commandLine.addSubcommand(ServerCommand(app))
|
||||
commandLine.addSubcommand(PasswordHashCommand())
|
||||
commandLine.addSubcommand(
|
||||
CommandLine(ClientCommand(app)).apply {
|
||||
addSubcommand(BenchmarkCommand())
|
||||
addSubcommand(PutCommand())
|
||||
addSubcommand(GetCommand())
|
||||
addSubcommand(HealthCheckCommand())
|
||||
})
|
||||
System.exit(commandLine.execute(*args))
|
||||
}
|
||||
}
|
||||
|
||||
@CommandLine.Option(names = ["-V", "--version"], versionHelp = true)
|
||||
var versionHelp = false
|
||||
private set
|
||||
|
||||
@CommandLine.Spec
|
||||
private lateinit var spec: CommandSpec
|
||||
|
||||
|
||||
override fun run() {
|
||||
spec.commandLine().usage(System.out);
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package net.woggioni.rbcs.cli.impl
|
||||
|
||||
import picocli.CommandLine
|
||||
import java.util.jar.Attributes
|
||||
import java.util.jar.JarFile
|
||||
import java.util.jar.Manifest
|
||||
|
||||
|
||||
abstract class AbstractVersionProvider : CommandLine.IVersionProvider {
|
||||
private val version: String
|
||||
private val vcsHash: String
|
||||
|
||||
init {
|
||||
val mf = Manifest()
|
||||
javaClass.module.getResourceAsStream(JarFile.MANIFEST_NAME).use { `is` ->
|
||||
mf.read(`is`)
|
||||
}
|
||||
val mainAttributes = mf.mainAttributes
|
||||
version = mainAttributes.getValue(Attributes.Name.SPECIFICATION_VERSION) ?: throw RuntimeException("Version information not found in manifest")
|
||||
vcsHash = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION) ?: throw RuntimeException("Version information not found in manifest")
|
||||
}
|
||||
|
||||
override fun getVersion(): Array<String?> {
|
||||
return if (version.endsWith("-SNAPSHOT")) {
|
||||
arrayOf(version, vcsHash)
|
||||
} else {
|
||||
arrayOf(version)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package net.woggioni.rbcs.cli.impl
|
||||
|
||||
import net.woggioni.jwo.Application
|
||||
import picocli.CommandLine
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
abstract class RbcsCommand : Runnable {
|
||||
|
||||
@CommandLine.Option(names = ["-h", "--help"], usageHelp = true)
|
||||
var usageHelp = false
|
||||
private set
|
||||
|
||||
protected fun findConfigurationFile(app: Application, fileName : String): Path {
|
||||
val confDir = app.computeConfigurationDirectory()
|
||||
val configurationFile = confDir.resolve(fileName)
|
||||
return configurationFile
|
||||
}
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
package net.woggioni.rbcs.cli.impl.commands
|
||||
|
||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||
import net.woggioni.rbcs.client.RemoteBuildCacheClient
|
||||
import net.woggioni.rbcs.common.contextLogger
|
||||
import net.woggioni.rbcs.common.error
|
||||
import net.woggioni.rbcs.common.info
|
||||
import net.woggioni.jwo.JWO
|
||||
import picocli.CommandLine
|
||||
import java.security.SecureRandom
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.Semaphore
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.random.Random
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "benchmark",
|
||||
description = ["Run a load test against the server"],
|
||||
showDefaultValues = true
|
||||
)
|
||||
class BenchmarkCommand : RbcsCommand() {
|
||||
private val log = contextLogger()
|
||||
|
||||
@CommandLine.Spec
|
||||
private lateinit var spec: CommandLine.Model.CommandSpec
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-e", "--entries"],
|
||||
description = ["Total number of elements to be added to the cache"],
|
||||
paramLabel = "NUMBER_OF_ENTRIES"
|
||||
)
|
||||
private var numberOfEntries = 1000
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-s", "--size"],
|
||||
description = ["Size of a cache value in bytes"],
|
||||
paramLabel = "SIZE"
|
||||
)
|
||||
private var size = 0x1000
|
||||
|
||||
override fun run() {
|
||||
val clientCommand = spec.parent().userObject() as ClientCommand
|
||||
val profile = clientCommand.profileName.let { profileName ->
|
||||
clientCommand.configuration.profiles[profileName]
|
||||
?: throw IllegalArgumentException("Profile $profileName does not exist in configuration")
|
||||
}
|
||||
RemoteBuildCacheClient(profile).use { client ->
|
||||
|
||||
val entryGenerator = sequence {
|
||||
val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong())
|
||||
while (true) {
|
||||
val key = JWO.bytesToHex(random.nextBytes(16))
|
||||
val content = random.nextInt().toByte()
|
||||
val value = ByteArray(size, { _ -> content })
|
||||
yield(key to value)
|
||||
}
|
||||
}
|
||||
|
||||
log.info {
|
||||
"Starting insertion"
|
||||
}
|
||||
val entries = let {
|
||||
val completionCounter = AtomicLong(0)
|
||||
val completionQueue = LinkedBlockingQueue<Pair<String, ByteArray>>(numberOfEntries)
|
||||
val start = Instant.now()
|
||||
val semaphore = Semaphore(profile.maxConnections * 3)
|
||||
val iterator = entryGenerator.take(numberOfEntries).iterator()
|
||||
while (completionCounter.get() < numberOfEntries) {
|
||||
if (iterator.hasNext()) {
|
||||
val entry = iterator.next()
|
||||
semaphore.acquire()
|
||||
val future = client.put(entry.first, entry.second).thenApply { entry }
|
||||
future.whenComplete { result, ex ->
|
||||
if (ex != null) {
|
||||
log.error(ex.message, ex)
|
||||
} else {
|
||||
completionQueue.put(result)
|
||||
}
|
||||
semaphore.release()
|
||||
completionCounter.incrementAndGet()
|
||||
}
|
||||
} else {
|
||||
Thread.sleep(0)
|
||||
}
|
||||
}
|
||||
|
||||
val inserted = completionQueue.toList()
|
||||
val end = Instant.now()
|
||||
log.info {
|
||||
val elapsed = Duration.between(start, end).toMillis()
|
||||
val opsPerSecond = String.format("%.2f", numberOfEntries.toDouble() / elapsed * 1000)
|
||||
"Insertion rate: $opsPerSecond ops/s"
|
||||
}
|
||||
inserted
|
||||
}
|
||||
log.info {
|
||||
"Inserted ${entries.size} entries"
|
||||
}
|
||||
log.info {
|
||||
"Starting retrieval"
|
||||
}
|
||||
if (entries.isNotEmpty()) {
|
||||
val completionCounter = AtomicLong(0)
|
||||
val semaphore = Semaphore(profile.maxConnections * 3)
|
||||
val start = Instant.now()
|
||||
val it = entries.iterator()
|
||||
while (completionCounter.get() < entries.size) {
|
||||
if (it.hasNext()) {
|
||||
val entry = it.next()
|
||||
val future = client.get(entry.first).thenApply {
|
||||
if (it == null) {
|
||||
log.error {
|
||||
"Missing entry for key '${entry.first}'"
|
||||
}
|
||||
} else if (!entry.second.contentEquals(it)) {
|
||||
log.error {
|
||||
"Retrieved a value different from what was inserted for key '${entry.first}'"
|
||||
}
|
||||
}
|
||||
}
|
||||
future.whenComplete { _, _ ->
|
||||
completionCounter.incrementAndGet()
|
||||
semaphore.release()
|
||||
}
|
||||
} else {
|
||||
Thread.sleep(0)
|
||||
}
|
||||
}
|
||||
val end = Instant.now()
|
||||
log.info {
|
||||
val elapsed = Duration.between(start, end).toMillis()
|
||||
val opsPerSecond = String.format("%.2f", entries.size.toDouble() / elapsed * 1000)
|
||||
"Retrieval rate: $opsPerSecond ops/s"
|
||||
}
|
||||
} else {
|
||||
log.error("Skipping retrieval benchmark as it was not possible to insert any entry in the cache")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package net.woggioni.rbcs.cli.impl.commands
|
||||
|
||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||
import net.woggioni.rbcs.client.RemoteBuildCacheClient
|
||||
import net.woggioni.jwo.Application
|
||||
import picocli.CommandLine
|
||||
import java.nio.file.Path
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "client",
|
||||
description = ["RBCS client"],
|
||||
showDefaultValues = true
|
||||
)
|
||||
class ClientCommand(app : Application) : RbcsCommand() {
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-c", "--configuration"],
|
||||
description = ["Path to the client configuration file"],
|
||||
paramLabel = "CONFIGURATION_FILE"
|
||||
)
|
||||
private var configurationFile : Path = findConfigurationFile(app, "rbcs-client.xml")
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-p", "--profile"],
|
||||
description = ["Name of the client profile to be used"],
|
||||
paramLabel = "PROFILE",
|
||||
required = true
|
||||
)
|
||||
var profileName : String? = null
|
||||
|
||||
val configuration : RemoteBuildCacheClient.Configuration by lazy {
|
||||
RemoteBuildCacheClient.Configuration.parse(configurationFile)
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
println("Available profiles:")
|
||||
configuration.profiles.forEach { (profileName, _) ->
|
||||
println(profileName)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package net.woggioni.rbcs.cli.impl.commands
|
||||
|
||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||
import net.woggioni.rbcs.client.RemoteBuildCacheClient
|
||||
import net.woggioni.rbcs.common.contextLogger
|
||||
import picocli.CommandLine
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "get",
|
||||
description = ["Fetch a value from the cache with the specified key"],
|
||||
showDefaultValues = true
|
||||
)
|
||||
class GetCommand : RbcsCommand() {
|
||||
private val log = contextLogger()
|
||||
|
||||
@CommandLine.Spec
|
||||
private lateinit var spec: CommandLine.Model.CommandSpec
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-k", "--key"],
|
||||
description = ["The key for the new value"],
|
||||
paramLabel = "KEY"
|
||||
)
|
||||
private var key : String = ""
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-v", "--value"],
|
||||
description = ["Path to a file where the retrieved value will be written (defaults to stdout)"],
|
||||
paramLabel = "VALUE_FILE",
|
||||
)
|
||||
private var output : Path? = null
|
||||
|
||||
override fun run() {
|
||||
val clientCommand = spec.parent().userObject() as ClientCommand
|
||||
val profile = clientCommand.profileName.let { profileName ->
|
||||
clientCommand.configuration.profiles[profileName]
|
||||
?: throw IllegalArgumentException("Profile $profileName does not exist in configuration")
|
||||
}
|
||||
RemoteBuildCacheClient(profile).use { client ->
|
||||
client.get(key).thenApply { value ->
|
||||
value?.let {
|
||||
(output?.let(Files::newOutputStream) ?: System.out).use {
|
||||
it.write(value)
|
||||
}
|
||||
} ?: throw NoSuchElementException("No value found for key $key")
|
||||
}.get()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
package net.woggioni.rbcs.cli.impl.commands
|
||||
|
||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||
import net.woggioni.rbcs.client.RemoteBuildCacheClient
|
||||
import net.woggioni.rbcs.common.contextLogger
|
||||
import picocli.CommandLine
|
||||
import java.security.SecureRandom
|
||||
import kotlin.random.Random
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "health",
|
||||
description = ["Check server health"],
|
||||
showDefaultValues = true
|
||||
)
|
||||
class HealthCheckCommand : RbcsCommand() {
|
||||
private val log = contextLogger()
|
||||
|
||||
@CommandLine.Spec
|
||||
private lateinit var spec: CommandLine.Model.CommandSpec
|
||||
|
||||
override fun run() {
|
||||
val clientCommand = spec.parent().userObject() as ClientCommand
|
||||
val profile = clientCommand.profileName.let { profileName ->
|
||||
clientCommand.configuration.profiles[profileName]
|
||||
?: throw IllegalArgumentException("Profile $profileName does not exist in configuration")
|
||||
}
|
||||
RemoteBuildCacheClient(profile).use { client ->
|
||||
val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong())
|
||||
val nonce = ByteArray(0xa0)
|
||||
random.nextBytes(nonce)
|
||||
client.healthCheck(nonce).thenApply { value ->
|
||||
if(value == null) {
|
||||
throw IllegalStateException("Empty response from server")
|
||||
}
|
||||
for(i in 0 until nonce.size) {
|
||||
for(j in value.size - nonce.size until nonce.size) {
|
||||
if(nonce[i] != value[j]) {
|
||||
throw IllegalStateException("Server nonce does not match")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.get()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package net.woggioni.rbcs.cli.impl.commands
|
||||
|
||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||
import net.woggioni.rbcs.cli.impl.converters.OutputStreamConverter
|
||||
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
|
||||
import net.woggioni.jwo.UncloseableOutputStream
|
||||
import picocli.CommandLine
|
||||
import java.io.OutputStream
|
||||
import java.io.OutputStreamWriter
|
||||
import java.io.PrintWriter
|
||||
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "password",
|
||||
description = ["Generate a password hash to add to RBCS configuration file"],
|
||||
showDefaultValues = true
|
||||
)
|
||||
class PasswordHashCommand : RbcsCommand() {
|
||||
@CommandLine.Option(
|
||||
names = ["-o", "--output-file"],
|
||||
description = ["Write the output to a file instead of stdout"],
|
||||
converter = [OutputStreamConverter::class],
|
||||
showDefaultValue = CommandLine.Help.Visibility.NEVER,
|
||||
paramLabel = "OUTPUT_FILE"
|
||||
)
|
||||
private var outputStream: OutputStream = UncloseableOutputStream(System.out)
|
||||
|
||||
override fun run() {
|
||||
val password1 = String(System.console().readPassword("Type your password:"))
|
||||
val password2 = String(System.console().readPassword("Type your password again for confirmation:"))
|
||||
if(password1 != password2) throw IllegalArgumentException("Passwords do not match")
|
||||
|
||||
PrintWriter(OutputStreamWriter(outputStream, Charsets.UTF_8)).use {
|
||||
it.println(hashPassword(password1))
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
package net.woggioni.rbcs.cli.impl.commands
|
||||
|
||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||
import net.woggioni.rbcs.cli.impl.converters.InputStreamConverter
|
||||
import net.woggioni.rbcs.client.RemoteBuildCacheClient
|
||||
import net.woggioni.rbcs.common.contextLogger
|
||||
import picocli.CommandLine
|
||||
import java.io.InputStream
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "put",
|
||||
description = ["Add or replace a value to the cache with the specified key"],
|
||||
showDefaultValues = true
|
||||
)
|
||||
class PutCommand : RbcsCommand() {
|
||||
private val log = contextLogger()
|
||||
|
||||
@CommandLine.Spec
|
||||
private lateinit var spec: CommandLine.Model.CommandSpec
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-k", "--key"],
|
||||
description = ["The key for the new value"],
|
||||
paramLabel = "KEY"
|
||||
)
|
||||
private var key : String = ""
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-v", "--value"],
|
||||
description = ["Path to a file containing the value to be added (defaults to stdin)"],
|
||||
paramLabel = "VALUE_FILE",
|
||||
converter = [InputStreamConverter::class]
|
||||
)
|
||||
private var value : InputStream = System.`in`
|
||||
|
||||
override fun run() {
|
||||
val clientCommand = spec.parent().userObject() as ClientCommand
|
||||
val profile = clientCommand.profileName.let { profileName ->
|
||||
clientCommand.configuration.profiles[profileName]
|
||||
?: throw IllegalArgumentException("Profile $profileName does not exist in configuration")
|
||||
}
|
||||
RemoteBuildCacheClient(profile).use { client ->
|
||||
value.use {
|
||||
client.put(key, it.readAllBytes())
|
||||
}.get()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
package net.woggioni.rbcs.cli.impl.commands
|
||||
|
||||
import net.woggioni.rbcs.cli.impl.RbcsCommand
|
||||
import net.woggioni.rbcs.cli.impl.converters.DurationConverter
|
||||
import net.woggioni.rbcs.common.contextLogger
|
||||
import net.woggioni.rbcs.common.debug
|
||||
import net.woggioni.rbcs.common.info
|
||||
import net.woggioni.rbcs.server.RemoteBuildCacheServer
|
||||
import net.woggioni.rbcs.server.RemoteBuildCacheServer.Companion.DEFAULT_CONFIGURATION_URL
|
||||
import net.woggioni.jwo.Application
|
||||
import net.woggioni.jwo.JWO
|
||||
import picocli.CommandLine
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "server",
|
||||
description = ["RBCS server"],
|
||||
showDefaultValues = true
|
||||
)
|
||||
class ServerCommand(app : Application) : RbcsCommand() {
|
||||
|
||||
private val log = contextLogger()
|
||||
|
||||
private fun createDefaultConfigurationFile(configurationFile: Path) {
|
||||
log.info {
|
||||
"Creating default configuration file at '$configurationFile'"
|
||||
}
|
||||
val defaultConfigurationFileResource = DEFAULT_CONFIGURATION_URL
|
||||
Files.newOutputStream(configurationFile).use { outputStream ->
|
||||
defaultConfigurationFileResource.openStream().use { inputStream ->
|
||||
JWO.copy(inputStream, outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-t", "--timeout"],
|
||||
description = ["Exit after the specified time"],
|
||||
paramLabel = "TIMEOUT",
|
||||
converter = [DurationConverter::class]
|
||||
)
|
||||
private var timeout: Duration? = null
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-c", "--config-file"],
|
||||
description = ["Read the application configuration from this file"],
|
||||
paramLabel = "CONFIG_FILE"
|
||||
)
|
||||
private var configurationFile: Path = findConfigurationFile(app, "rbcs-server.xml")
|
||||
|
||||
override fun run() {
|
||||
if (!Files.exists(configurationFile)) {
|
||||
Files.createDirectories(configurationFile.parent)
|
||||
createDefaultConfigurationFile(configurationFile)
|
||||
}
|
||||
|
||||
val configuration = RemoteBuildCacheServer.loadConfiguration(configurationFile)
|
||||
log.debug {
|
||||
ByteArrayOutputStream().also {
|
||||
RemoteBuildCacheServer.dumpConfiguration(configuration, it)
|
||||
}.let {
|
||||
"Server configuration:\n${String(it.toByteArray())}"
|
||||
}
|
||||
}
|
||||
val server = RemoteBuildCacheServer(configuration)
|
||||
server.run().use { server ->
|
||||
timeout?.let {
|
||||
Thread.sleep(it)
|
||||
server.shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package net.woggioni.rbcs.cli.impl.converters
|
||||
|
||||
import picocli.CommandLine
|
||||
import java.time.Duration
|
||||
|
||||
|
||||
class DurationConverter : CommandLine.ITypeConverter<Duration> {
|
||||
override fun convert(value: String): Duration {
|
||||
return Duration.parse(value)
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package net.woggioni.rbcs.cli.impl.converters
|
||||
|
||||
import picocli.CommandLine
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
|
||||
|
||||
class InputStreamConverter : CommandLine.ITypeConverter<InputStream> {
|
||||
override fun convert(value: String): InputStream {
|
||||
return Files.newInputStream(Paths.get(value))
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package net.woggioni.rbcs.cli.impl.converters
|
||||
|
||||
import picocli.CommandLine
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
|
||||
|
||||
class OutputStreamConverter : CommandLine.ITypeConverter<OutputStream> {
|
||||
override fun convert(value: String): OutputStream {
|
||||
return Files.newOutputStream(Paths.get(value))
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration>
|
||||
|
||||
<configuration>
|
||||
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
|
||||
<import class="ch.qos.logback.core.ConsoleAppender"/>
|
||||
|
||||
<appender name="console" class="ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
<encoder class="PatternLayoutEncoder">
|
||||
<pattern>%d [%highlight(%-5level)] \(%thread\) %logger{36} -%kvp- %msg %n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="console"/>
|
||||
</root>
|
||||
</configuration>
|
@@ -0,0 +1,9 @@
|
||||
.level=FINEST
|
||||
|
||||
handlers = java.util.logging.ConsoleHandler
|
||||
|
||||
java.util.logging.ConsoleHandler.level = FINER
|
||||
java.util.logging.ConsoleHandler.filter =
|
||||
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
|
||||
java.util.logging.SimpleFormatter.format = %1$tF %1$tT [%4$s] %2$s %5$s %6$s%n
|
||||
java.util.logging.ConsoleHandler.encoding =
|
Reference in New Issue
Block a user