-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
besom-cfg initial checkin to monorepo (#494)
* besom-cfg initial checkin to monorepo
- Loading branch information
Showing
27 changed files
with
2,212 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
version = 3.5.2 | ||
runner.dialect = scala3 | ||
project.git = true | ||
align = most | ||
align.openParenCallSite = false | ||
align.openParenDefnSite = false | ||
align.tokens = [{code = "=>", owner = "Case"}, "<-", "%", "%%", "="] | ||
indent.defnSite = 2 | ||
maxColumn = 140 | ||
|
||
rewrite.scala3.insertEndMarkerMinLines = 40 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
//> using scala 3.3.3 | ||
|
||
//> using dep com.lihaoyi::os-lib::0.9.3 | ||
//> using dep org.virtuslab::besom-cfg:0.2.0-SNAPSHOT | ||
//> using dep org.virtuslab::besom-kubernetes::4.10.0-core.0.4-SNAPSHOT | ||
//> using dep com.lihaoyi::fansi::0.5.0 | ||
//> using dep com.lihaoyi::fastparse:3.1.0 | ||
|
||
//> using test.resourceDir ./src/test/resources | ||
|
||
//> using test.dep com.lihaoyi::pprint:0.6.6 | ||
//> using test.dep org.scalameta::munit:1.0.0-M11 | ||
|
||
//> using publish.name "besom-cfg-k8s" | ||
//> using publish.organization "org.virtuslab" | ||
//> using publish.url "https://github.com/VirtusLab/besom" | ||
//> using publish.vcs "github:VirtusLab/besom" | ||
//> using publish.license "Apache-2.0" | ||
//> using publish.repository "central" | ||
//> using publish.developer "lbialy|Łukasz Biały|https://github.com/lbialy" | ||
//> using publish.developer "prolativ|Michał Pałka|https://github.com/prolativ" | ||
//> using publish.developer "KacperFKorban|Kacper Korban|https://github.com/KacperFKorban" | ||
//> using publish.developer "pawelprazak|Paweł Prażak|https://github.com/pawelprazak" |
172 changes: 172 additions & 0 deletions
172
besom-cfg/k8s/src/main/scala/ConfiguredContainerArgs.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
package besom.cfg.k8s | ||
|
||
import besom.cfg.internal.* | ||
import besom.types.{Input, Context, Output} | ||
import besom.cfg.* | ||
import besom.json.* | ||
import besom.cfg.containers.* | ||
import besom.api.kubernetes.core.v1.inputs.* | ||
|
||
import scala.util.* | ||
import scala.quoted.* | ||
import besom.cfg.k8s.syntax.* | ||
|
||
// this is besom-cfg-kubernetes entrypoint | ||
|
||
object syntax: | ||
extension (s: Struct) | ||
def foldedToEnvVarArgs(using Context): Output[List[EnvVarArgs]] = | ||
s.foldToEnv.map(_.map { case (k, v) => EnvVarArgs(name = k, value = v) }) | ||
|
||
object ConfiguredContainerArgs: | ||
|
||
private val NL = System.lineSeparator() | ||
|
||
inline def apply[C <: Struct]( | ||
name: String, | ||
image: String, | ||
configuration: C, | ||
args: Input.Optional[List[Input[String]]] = None, | ||
command: Input.Optional[List[Input[String]]] = None, | ||
env: Input.Optional[List[Input[EnvVarArgs]]] = None, | ||
envFrom: Input.Optional[List[Input[EnvFromSourceArgs]]] = None, | ||
imagePullPolicy: Input.Optional[String] = None, | ||
lifecycle: Input.Optional[LifecycleArgs] = None, | ||
livenessProbe: Input.Optional[ProbeArgs] = None, | ||
ports: Input.Optional[List[Input[ContainerPortArgs]]] = None, | ||
readinessProbe: Input.Optional[ProbeArgs] = None, | ||
resizePolicy: Input.Optional[List[Input[ContainerResizePolicyArgs]]] = None, | ||
resources: Input.Optional[ResourceRequirementsArgs] = None, | ||
restartPolicy: Input.Optional[String] = None, | ||
securityContext: Input.Optional[SecurityContextArgs] = None, | ||
startupProbe: Input.Optional[ProbeArgs] = None, | ||
stdin: Input.Optional[Boolean] = None, | ||
stdinOnce: Input.Optional[Boolean] = None, | ||
terminationMessagePath: Input.Optional[String] = None, | ||
terminationMessagePolicy: Input.Optional[String] = None, | ||
tty: Input.Optional[Boolean] = None, | ||
volumeDevices: Input.Optional[List[Input[VolumeDeviceArgs]]] = None, | ||
volumeMounts: Input.Optional[List[Input[VolumeMountArgs]]] = None, | ||
workingDir: Input.Optional[String] = None | ||
)(using ctx: Context) = ${ | ||
applyImpl( | ||
'name, | ||
'image, | ||
'configuration, | ||
'args, | ||
'command, | ||
'env, | ||
'envFrom, | ||
'imagePullPolicy, | ||
'lifecycle, | ||
'livenessProbe, | ||
'ports, | ||
'readinessProbe, | ||
'resizePolicy, | ||
'resources, | ||
'restartPolicy, | ||
'securityContext, | ||
'startupProbe, | ||
'stdin, | ||
'stdinOnce, | ||
'terminationMessagePath, | ||
'terminationMessagePolicy, | ||
'tty, | ||
'volumeDevices, | ||
'volumeMounts, | ||
'workingDir, | ||
'ctx | ||
) | ||
} | ||
|
||
def applyImpl[C <: Struct: Type]( | ||
name: Expr[String], | ||
image: Expr[String], | ||
configuration: Expr[C], | ||
args: Expr[Input.Optional[List[Input[String]]]], | ||
command: Expr[Input.Optional[List[Input[String]]]], | ||
env: Expr[Input.Optional[List[Input[EnvVarArgs]]]], | ||
envFrom: Expr[Input.Optional[List[Input[EnvFromSourceArgs]]]], | ||
imagePullPolicy: Expr[Input.Optional[String]], | ||
lifecycle: Expr[Input.Optional[LifecycleArgs]], | ||
livenessProbe: Expr[Input.Optional[ProbeArgs]], | ||
ports: Expr[Input.Optional[List[Input[ContainerPortArgs]]]], | ||
readinessProbe: Expr[Input.Optional[ProbeArgs]], | ||
resizePolicy: Expr[Input.Optional[List[Input[ContainerResizePolicyArgs]]]], | ||
resources: Expr[Input.Optional[ResourceRequirementsArgs]], | ||
restartPolicy: Expr[Input.Optional[String]], | ||
securityContext: Expr[Input.Optional[SecurityContextArgs]], | ||
startupProbe: Expr[Input.Optional[ProbeArgs]], | ||
stdin: Expr[Input.Optional[Boolean]], | ||
stdinOnce: Expr[Input.Optional[Boolean]], | ||
terminationMessagePath: Expr[Input.Optional[String]], | ||
terminationMessagePolicy: Expr[Input.Optional[String]], | ||
tty: Expr[Input.Optional[Boolean]], | ||
volumeDevices: Expr[Input.Optional[List[Input[VolumeDeviceArgs]]]], | ||
volumeMounts: Expr[Input.Optional[List[Input[VolumeMountArgs]]]], | ||
workingDir: Expr[Input.Optional[String]], | ||
context: Expr[Context] | ||
)(using Quotes): Expr[ContainerArgs] = | ||
import quotes.reflect.* | ||
|
||
val contName = name.value match | ||
case None => report.errorAndAbort("Container name has to be a literal!", name) | ||
case Some(value) => value | ||
|
||
val dockerImage = image.value match | ||
case None => report.errorAndAbort("Image name has to be a literal!", image) | ||
case Some(value) => value | ||
|
||
val schema = getDockerImageMetadata(dockerImage) match | ||
case Left(throwable) => report.errorAndAbort(s"Failed to get metadata for image $dockerImage:$NL${pprint(throwable)}", image) | ||
case Right(schema) => schema | ||
|
||
Diff.performDiff(schema, configuration) match | ||
case Left(prettyDiff) => // TODO maybe strip all the ansi codes if in CI? | ||
report.errorAndAbort( | ||
s"Configuration provided for container $contName ($dockerImage) is invalid:$NL$NL$prettyDiff", | ||
configuration | ||
) | ||
|
||
case Right(()) => | ||
val envExpr = '{ | ||
val envOutput = ${ env }.asOptionOutput()(using ${ context }) | ||
val conf = ${ configuration } | ||
val configurationAsEnvVarArgs = conf.foldedToEnvVarArgs(using $context) | ||
envOutput.zip(configurationAsEnvVarArgs).map { | ||
case (Some(envVarArgsList), envVarArgsListFromConf) => envVarArgsList ++ envVarArgsListFromConf | ||
case (None, envVarArgsListFromConf) => envVarArgsListFromConf | ||
} | ||
} | ||
|
||
'{ | ||
ContainerArgs( | ||
args = $args, | ||
command = $command, | ||
env = $envExpr, | ||
envFrom = $envFrom, | ||
image = $image, | ||
imagePullPolicy = $imagePullPolicy, | ||
lifecycle = $lifecycle, | ||
livenessProbe = $livenessProbe, | ||
name = ${ Expr(contName) }, | ||
ports = $ports, | ||
readinessProbe = $readinessProbe, | ||
resizePolicy = $resizePolicy, | ||
resources = $resources, | ||
restartPolicy = $restartPolicy, | ||
securityContext = $securityContext, | ||
startupProbe = $startupProbe, | ||
stdin = $stdin, | ||
stdinOnce = $stdinOnce, | ||
terminationMessagePath = $terminationMessagePath, | ||
terminationMessagePolicy = $terminationMessagePolicy, | ||
tty = $tty, | ||
volumeDevices = $volumeDevices, | ||
volumeMounts = $volumeMounts, | ||
workingDir = $workingDir | ||
)(using $context) | ||
} | ||
end match | ||
end applyImpl | ||
end ConfiguredContainerArgs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package besom.cfg.containers | ||
|
||
// this should be a separate package, base for all container integrations | ||
|
||
import besom.cfg.internal.Schema | ||
import besom.json.* | ||
import scala.util.Try | ||
|
||
val cacheDir = sys.props.get("java.io.tmpdir").getOrElse("/tmp") | ||
|
||
def sanitizeImageName(image: String): String = | ||
image | ||
.replace("/", "_") | ||
.replace(":", "_") | ||
|
||
def fetchFromCache(image: String): Option[String] = | ||
if image.endsWith(":latest") then None | ||
else | ||
val sanitized = sanitizeImageName(image) | ||
os.makeDir.all(os.Path(s"$cacheDir/besom-cfg")) | ||
Try(os.read(os.Path(s"$cacheDir/besom-cfg/$sanitized"))).toOption | ||
|
||
def saveToCache(image: String, content: String): Unit = | ||
if !image.endsWith(":latest") then | ||
val sanitized = sanitizeImageName(image) | ||
os.makeDir.all(os.Path(s"$cacheDir/besom-cfg")) | ||
os.write.over(os.Path(s"$cacheDir/besom-cfg/$sanitized"), content) | ||
|
||
def resolveMetadataFromImage(image: String): String = | ||
lazy val sbtNativePackagerFormatCall = | ||
os | ||
.proc("docker", "run", "--rm", "--entrypoint", "java", image, "-cp", "/opt/docker/lib/*", "besom.cfg.SummonConfiguration") | ||
.call(check = false) | ||
|
||
lazy val customDockerFormatCall = | ||
os | ||
.proc("docker", "run", "--rm", "--entrypoint", "java", image, "-cp", "/app/main", "besom.cfg.SummonConfiguration") | ||
.call(check = false) | ||
|
||
if sbtNativePackagerFormatCall.exitCode == 0 then sbtNativePackagerFormatCall.out.text().trim() | ||
else if customDockerFormatCall.exitCode == 0 then customDockerFormatCall.out.text().trim() | ||
else throw RuntimeException(s"Failed to get configuration from $image") | ||
|
||
def getDockerImageMetadata(image: String): Either[Throwable, Schema] = | ||
Try { | ||
// 1. cache result per image in /tmp DONE | ||
// 2. verify the version of the library used, fail macro if we are older than it | ||
// 3. parse the json to correct structure DONE | ||
// next: | ||
// - support different image setups, autodetect which one is used somehow? somewhat DONE | ||
// - cp argument should be configurable | ||
val json = fetchFromCache(image) match { | ||
case Some(cachedJson) => cachedJson | ||
case None => | ||
val json = resolveMetadataFromImage(image) | ||
|
||
saveToCache(image, json) | ||
|
||
json | ||
} | ||
|
||
summon[JsonFormat[Schema]].read(json.parseJson) | ||
}.toEither |
Oops, something went wrong.