-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
besom-cfg initial checkin to monorepo #494
Changes from all commits
600f9a7
ce90383
995dfe1
598e7c2
b3b996b
8a411ab
67e46f6
9a0c3a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this will be replaced by current besom version next time you run the version bump script, that's why I was suggesting the version alignment ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so there has to be a way to provide exceptions in the version bump script |
||
//> 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" |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is the entrypoint, I'd strongly advice adding even the most barebones scaladoc :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll strongly consider that once besom-cfg is beyond a prototype |
||
|
||
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 |
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume there is a reason behind versioning cfg separately, but wouldn't it make sense to align the versions, just for the sake of simplicity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, it wouldn't, this stuff has and has to have a completely separate lifecycle