Skip to content

Commit 03e5d08

Browse files
authored
feat(cli): add mock s3 server
* feat(cli): mock s3 server Signed-off-by: melodicore <[email protected]> * feat(cli): reachability metadata Signed-off-by: melodicore <[email protected]> * chore(cli): change wording in s3 subcommand options Signed-off-by: melodicore <[email protected]> * test(cli): add test case for mock s3 Signed-off-by: melodicore <[email protected]> --------- Signed-off-by: melodicore <[email protected]>
1 parent c8c853d commit 03e5d08

File tree

5 files changed

+495
-1
lines changed

5 files changed

+495
-1
lines changed

packages/cli/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,9 @@ dependencies {
657657
implementation(libs.jib.core)
658658
implementation(libs.jib.extension.commons)
659659

660+
// Mock S3
661+
implementation(libs.locals3)
662+
660663
// Tests
661664
testImplementation(libs.kotlin.test.junit5)
662665
testImplementation(projects.packages.test)

packages/cli/src/main/kotlin/elide/tool/cli/Elide.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import elide.tool.cli.cmd.pkl.ToolPklCommand
5656
import elide.tool.cli.cmd.project.ToolProjectCommand
5757
import elide.tool.cli.cmd.tool.ToolInvokeCommand
5858
import elide.tool.cli.cmd.repl.ToolShellCommand
59+
import elide.tool.cli.cmd.s3.ToolS3Command
5960
import elide.tool.cli.cmd.secrets.ToolSecretsCommand
6061
import elide.tool.cli.cmd.tool.jar.JarToolAdapter
6162
import elide.tool.cli.cmd.tool.javac.JavaCompilerAdapter
@@ -132,6 +133,7 @@ internal const val ELIDE_HEADER = ("@|bold,fg(magenta)%n" +
132133
McpCommand::class,
133134
Elide.Completions::class,
134135
ToolSecretsCommand::class,
136+
ToolS3Command::class,
135137
],
136138
customSynopsis = [
137139
"",
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (c) 2024-2025 Elide Technologies, Inc.
3+
*
4+
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
5+
* with the License. You may obtain a copy of the License at
6+
*
7+
* https://opensource.org/license/mit/
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
* License for the specific language governing permissions and limitations under the License.
12+
*/
13+
package elide.tool.cli.cmd.s3
14+
15+
import com.robothy.s3.rest.LocalS3
16+
import com.robothy.s3.rest.bootstrap.LocalS3Mode
17+
import elide.tool.cli.CommandContext
18+
import elide.tool.cli.CommandResult
19+
import elide.tool.cli.ProjectAwareSubcommand
20+
import elide.tool.cli.ToolState
21+
import io.micronaut.core.annotation.Introspected
22+
import io.micronaut.core.annotation.ReflectiveAccess
23+
import kotlin.coroutines.cancellation.CancellationException
24+
import kotlin.time.Duration.Companion.seconds
25+
import kotlinx.coroutines.awaitCancellation
26+
import kotlinx.coroutines.delay
27+
import picocli.CommandLine
28+
import picocli.CommandLine.Command
29+
30+
/** TBD. */
31+
@Command(
32+
name = "s3",
33+
description = ["Run a local S3 server"],
34+
mixinStandardHelpOptions = true,
35+
)
36+
@Introspected
37+
@ReflectiveAccess
38+
internal class ToolS3Command : ProjectAwareSubcommand<ToolState, CommandContext>() {
39+
/** Specifies the directory where files should be served. */
40+
@CommandLine.Option(
41+
names = ["--directory", "--dir", "-d"],
42+
description = ["Root directory of the server. Current directory will be used if omitted."],
43+
)
44+
internal var directory: String? = null
45+
46+
/** Specifies the port of the server. */
47+
@CommandLine.Option(
48+
names = ["--port", "-P"],
49+
description = ["Port of the server."],
50+
defaultValue = "8080",
51+
)
52+
internal var port: Int = 8080
53+
54+
@CommandLine.Option(
55+
names = ["--in-memory", "--memory", "-m"],
56+
description = ["Run the server in memory. Files will be read from the directory, but will not be modified."],
57+
defaultValue = "false",
58+
)
59+
internal var memory: Boolean = false
60+
61+
@CommandLine.Option(
62+
names = ["--shutdown-after"],
63+
description = ["Automatically shut down the server after this many seconds."],
64+
defaultValue = "-1",
65+
)
66+
internal var timeout: Int = -1
67+
68+
/** @inheritDoc */
69+
override suspend fun CommandContext.invoke(state: ToolContext<ToolState>): CommandResult {
70+
val mode = if (memory) LocalS3Mode.IN_MEMORY else LocalS3Mode.PERSISTENCE
71+
val userDir = System.getProperty("user.dir")
72+
val dataPath = directory?.let { if (it.isCharAt(0, '/') || it.isCharAt(1, ':')) it else "$userDir/$it" } ?: userDir
73+
val server = LocalS3.builder().port(port).mode(mode).dataPath(dataPath).build()
74+
server.start()
75+
if (timeout > 0) {
76+
delay(timeout.seconds)
77+
server.shutdown()
78+
return CommandResult.success()
79+
}
80+
try {
81+
awaitCancellation()
82+
} catch (t: Throwable) {
83+
return if (t is CancellationException) CommandResult.success() else CommandResult.err(1, exc = t)
84+
} finally {
85+
server.shutdown()
86+
}
87+
}
88+
89+
private fun String.isCharAt(index: Int, char: Char): Boolean = length > index && this[index] == char
90+
}

0 commit comments

Comments
 (0)