Skip to content
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

Add resource methods to codegen #289

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ clean-test-markdown:
####################

power-wash: clean-all
rm -rf ~/.ivy2/local/org.virtuslab/
git clean -i -d -x
killall -9 java

Expand Down
2 changes: 1 addition & 1 deletion codegen/project.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//> using scala 2.13.12
//> using options "-release:11"

//> using dep org.scalameta::scalameta:4.8.12
//> using dep org.scalameta::scalameta:4.8.13
//> using dep com.lihaoyi::upickle:3.1.3
//> using dep com.lihaoyi::os-lib:0.9.2
//> using test.dep org.scalameta::munit::1.0.0-M10
Expand Down
288 changes: 191 additions & 97 deletions codegen/src/CodeGen.scala

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions codegen/src/CodeGen.test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package besom.codegen

//noinspection ScalaFileName
class CodeGenTest extends munit.FunSuite {
// TODO: test the generated code
}
33 changes: 13 additions & 20 deletions codegen/src/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,30 @@ object Main {
args.toList match {
case "named" :: name :: version :: Nil =>
implicit val codegenConfig: CodegenConfig = CodegenConfig()
generator.generatePackageSources(
schemaOrMetadata = Right(PackageMetadata(name, version))
)
generator.generatePackageSources(metadata = PackageMetadata(name, version))
case "named" :: name :: version :: outputDir :: Nil =>
implicit val codegenConfig: CodegenConfig = CodegenConfig(outputDir = Some(os.rel / outputDir))
generator.generatePackageSources(
schemaOrMetadata = Right(PackageMetadata(name, version))
)
generator.generatePackageSources(metadata = PackageMetadata(name, version))
case "metadata" :: metadataPath :: Nil =>
implicit val codegenConfig: CodegenConfig = CodegenConfig()
generator.generatePackageSources(Right(PackageMetadata.fromJsonFile(os.Path(metadataPath))))
generator.generatePackageSources(metadata = PackageMetadata.fromJsonFile(os.Path(metadataPath)))
case "metadata" :: metadataPath :: outputDir :: Nil =>
implicit val codegenConfig: CodegenConfig = CodegenConfig(outputDir = Some(os.rel / outputDir))
generator.generatePackageSources(Right(PackageMetadata.fromJsonFile(os.Path(metadataPath))))
case "schema" :: providerName :: schemaVersion :: Nil =>
implicit val codegenConfig: CodegenConfig = CodegenConfig()
generator.generatePackageSources(
schemaOrMetadata = Right(PackageMetadata(providerName, schemaVersion))
)
case "schema" :: schemaPath :: Nil =>
generator.generatePackageSources(metadata = PackageMetadata.fromJsonFile(os.Path(metadataPath)))
case "schema" :: name :: version :: schemaPath :: Nil =>
implicit val codegenConfig: CodegenConfig = CodegenConfig()
generator.generatePackageSources(
schemaOrMetadata = Left(os.Path(schemaPath))
metadata = PackageMetadata(name, version),
schema = Some(os.Path(schemaPath))
)
case _ =>
System.err.println(
s"""|Unknown arguments: '${args.mkString(" ")}'
|
|Usage:
| named <name> <version> [outputDir] - Generate package from name and version
| metadata <metadataPath> [outputDir] - Generate package from metadata file
| schema <schemaPath> [outputDir] - Generate package from schema file
| named <name> <version> [outputDir] - Generate package from name and version
| metadata <metadataPath> [outputDir] - Generate package from metadata file
| schema <name> <version> <schemaPath> [outputDir] - Generate package from schema file
|""".stripMargin
)
sys.exit(1)
Expand All @@ -59,7 +51,8 @@ object generator {

// noinspection ScalaWeakerAccess
def generatePackageSources(
schemaOrMetadata: Either[SchemaFile, PackageMetadata]
metadata: PackageMetadata,
schema: Option[SchemaFile] = None
)(implicit config: CodegenConfig): Result = {
implicit val logger: Logger = new Logger(config.logLevel)
implicit val schemaProvider: DownloadingSchemaProvider = new DownloadingSchemaProvider(
Expand All @@ -72,7 +65,7 @@ object generator {
logger.warn("Setting GITHUB_TOKEN environment variable might solve some problems")
)

val (pulumiPackage, packageInfo) = schemaProvider.packageInfo(schemaOrMetadata)
val (pulumiPackage, packageInfo) = schemaProvider.packageInfo(metadata, schema)
val packageName = packageInfo.name
val packageVersion = packageInfo.version

Expand Down
42 changes: 24 additions & 18 deletions codegen/src/PackageMetadata.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import besom.codegen.UpickleApi._

case class PackageMetadata private (
name: PackageMetadata.SchemaName,
version: PackageVersion.PackageVersion,
version: Option[PackageVersion.PackageVersion] = None,
server: Option[String] = None
) {
def withUrl(url: String): PackageMetadata = {
Expand All @@ -26,8 +26,16 @@ object PackageMetadata {
type SchemaVersion = String
type SchemaFile = os.Path

def apply(name: SchemaName, version: SchemaVersion, server: Option[String] = None): PackageMetadata = {
new PackageMetadata(name, PackageVersion(version), server)
def apply(name: SchemaName, version: SchemaVersion): PackageMetadata = {
new PackageMetadata(name, PackageVersion.parse(version), None)
}

def apply(name: SchemaName, version: SchemaVersion, server: String): PackageMetadata = {
new PackageMetadata(name, PackageVersion.parse(version), Some(server))
}

def apply(name: SchemaName, version: Option[SchemaVersion] = None, server: Option[String] = None): PackageMetadata = {
new PackageMetadata(name, PackageVersion.parse(version), server)
}

def fromJson(json: String): PackageMetadata = {
Expand All @@ -54,30 +62,28 @@ object PackageVersion {
private val SemverRegex =
"""^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$""".r

def apply(version: String): PackageVersion = parse(version).getOrElse(
throw GeneralCodegenException(s"Invalid version format: '$version'")
)
def parse(version: Option[String]): Option[PackageVersion] = version.flatMap(parse)

def parse(version: String): Option[PackageVersion] = version.trim.stripPrefix("v") match {
case "" => None
case DefaultVersion => None
case v @ SemverRegex(_*) => Some(new PackageVersion(v))
case v @ SemverRegexPartial1(_) => Some(new PackageVersion(v + ".0.0")) // add minor and patch versions if missing
case v @ SemverRegexPartial2(_, _) => Some(new PackageVersion(v + ".0")) // add patch version if missing
case _ => None
}

implicit class PackageVersionOps(version: PackageVersion.PackageVersion) {
def isDefault: Boolean = version == DefaultVersion

def reconcile(otherVersion: PackageVersion.PackageVersion)(implicit logger: Logger): PackageVersion = {
(version.isDefault, otherVersion.isDefault) match {
case (false, true) => version
case (false, false) => version // we ignore the other version
case (true, false) => PackageVersion(otherVersion) // make sure we pass strings through the apply method
case (true, true) =>
// this code should be only reached in tests
logger.warn(s"Using default version '${DefaultVersion}' for the package - suitable only for tests")
PackageVersion.default
implicit class PackageVersionOps(version: Option[PackageVersion]) {
def orDefault: PackageVersion = version.getOrElse(DefaultVersion)
def isDefault: Boolean = version.isEmpty || version.contains(DefaultVersion)

def reconcile(otherVersion: Option[PackageVersion]): Option[PackageVersion] = {
// make sure we parse first to normalize the versions
(parse(version), parse(otherVersion)) match {
case (Some(v), None) => Some(v)
case (Some(v), Some(_)) => Some(v) // we ignore the other version
case (None, Some(v)) => Some(v)
case (None, None) => None
}
}
}
Expand Down
55 changes: 22 additions & 33 deletions codegen/src/PackageMetadata.test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package besom.codegen

//noinspection ScalaFileName,TypeAnnotation
class PackageMetadataTest extends munit.FunSuite {
import besom.codegen.PackageVersion._

test("fromJson") {
val json = """{"name":"aws","version":"v6.7.0"}"""
Expand All @@ -17,7 +18,7 @@ class PackageMetadataTest extends munit.FunSuite {
test("toJson") {
val metadata = PackageMetadata("aws", "v6.7.0")
assertEquals(
metadata.version,
metadata.version.orDefault,
"6.7.0"
)

Expand All @@ -27,18 +28,18 @@ class PackageMetadataTest extends munit.FunSuite {
)

assertEquals(
PackageMetadata("aci", "0.0.6", Some("github://api.github.com/netascode/pulumi-aci")).toJson,
PackageMetadata("aci", "0.0.6", "github://api.github.com/netascode/pulumi-aci").toJson,
"""{"name":"aci","version":"0.0.6","server":"github://api.github.com/netascode/pulumi-aci"}"""
)
}

test("withUrl") {
val actual1 = PackageMetadata("aws", "6.7.0").withUrl("https://github.com/pulumi/pulumi-aws")
val expected1 = PackageMetadata("aws", "6.7.0", None)
val expected1 = PackageMetadata("aws", "6.7.0")
assertEquals(actual1, expected1)

val actual2 = PackageMetadata("aci", "0.0.6").withUrl("https://github.com/netascode/pulumi-aci")
val expected2 = PackageMetadata("aci", "0.0.6", Some("github://api.github.com/netascode/pulumi-aci"))
val expected2 = PackageMetadata("aci", "0.0.6", "github://api.github.com/netascode/pulumi-aci")
assertEquals(actual2, expected2)
}
}
Expand All @@ -47,22 +48,18 @@ class PackageVersionTest extends munit.FunSuite {

implicit val logger: Logger = new Logger

test("apply") {
assertEquals(PackageVersion("v6.7.0"), "6.7.0")
assertEquals(PackageVersion("v6.7.0"), "6.7.0")
assertEquals(PackageVersion("0.123.1"), "0.123.1")
assertEquals(PackageVersion("1.0"), "1.0.0")
assertEquals(PackageVersion("1"), "1.0.0")
assertEquals(PackageVersion("v1"), "1.0.0")
assertEquals(PackageVersion("v1.0.0-alpha.1"), "1.0.0-alpha.1")
assertEquals(PackageVersion("v1.0.0-0.3.7"), "1.0.0-0.3.7")
assertEquals(PackageVersion("v1.0.0-alpha+001"), "1.0.0-alpha+001")
interceptMessage[Exception]("Invalid version format: ''") {
PackageVersion("")
}
interceptMessage[Exception]("Invalid version format: 'v6.7.0.0'") {
PackageVersion("v6.7.0.0")
}
test("parse") {
assertEquals(PackageVersion.parse("v6.7.0"), Some("6.7.0"))
assertEquals(PackageVersion.parse("v6.7.0"), Some("6.7.0"))
assertEquals(PackageVersion.parse("0.123.1"), Some("0.123.1"))
assertEquals(PackageVersion.parse("1.0"), Some("1.0.0"))
assertEquals(PackageVersion.parse("1"), Some("1.0.0"))
assertEquals(PackageVersion.parse("v1"), Some("1.0.0"))
assertEquals(PackageVersion.parse("v1.0.0-alpha.1"), Some("1.0.0-alpha.1"))
assertEquals(PackageVersion.parse("v1.0.0-0.3.7"), Some("1.0.0-0.3.7"))
assertEquals(PackageVersion.parse("v1.0.0-alpha+001"), Some("1.0.0-alpha+001"))
assertEquals(PackageVersion.parse(""), None)
assertEquals(PackageVersion.parse("v6.7.0.0"), None)
}

test("parse") {
Expand All @@ -74,21 +71,13 @@ class PackageVersionTest extends munit.FunSuite {
assertEquals(PackageVersion.parse(""), None)
}

test("isDefault") {
import PackageVersion._

assertEquals(PackageVersion.default.isDefault, true)
assertEquals(PackageVersion("6.7.0").isDefault, false)
assertEquals(PackageVersion("v6.7.0").isDefault, false)
}

test("reconcile") {
import PackageVersion._

assertEquals(PackageVersion("v6.7.0").reconcile("6.7.0"), "6.7.0")
assertEquals(PackageVersion("v6.7.0").reconcile("1.2.0"), "6.7.0")
assertEquals(PackageVersion("v6.7.0").reconcile(PackageVersion.default), "6.7.0")
assertEquals(PackageVersion.default.reconcile("v6.7.0"), "6.7.0")
assertEquals(PackageVersion.default.reconcile(PackageVersion.default), "0.0.0")
assertEquals(PackageVersion.parse("v6.7.0").reconcile(Some("6.7.0")), Some("6.7.0"))
assertEquals(PackageVersion.parse("v6.7.0").reconcile(Some("1.2.0")), Some("6.7.0"))
assertEquals(PackageVersion.parse("v6.7.0").reconcile(Some(PackageVersion.default)), Some("6.7.0"))
assertEquals(Some(PackageVersion.default).reconcile(Some("v6.7.0")), Some("6.7.0"))
assertEquals(Some(PackageVersion.default).reconcile(Some(PackageVersion.default)), None)
}
}
40 changes: 30 additions & 10 deletions codegen/src/PulumiDefinitionCoordinates.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,35 +35,55 @@ case class PulumiDefinitionCoordinates private (
)
}

def topLevelMethod(implicit logger: Logger): ScalaDefinitionCoordinates =
private def splitMethodName(definitionName: String): (Seq[String], String) = {
val methodNameParts = definitionName.split("/")
val methodName = methodNameParts.last
val methodPrefix = methodNameParts.init.filterNot(_ == methodName) // filter out the function name duplication
(methodPrefix, methodName)
}

def resourceMethod(implicit logger: Logger): ScalaDefinitionCoordinates = {
val (methodPrefix, methodName) = splitMethodName(definitionName)
ScalaDefinitionCoordinates(
providerPackageParts = providerPackageParts,
modulePackageParts = modulePackageParts,
definitionName = mangleMethodName(definitionName)
modulePackageParts = modulePackageParts ++ methodPrefix,
definitionName = mangleMethodName(methodName)
)

def methodArgsClass(implicit logger: Logger): ScalaDefinitionCoordinates =
}
def methodArgsClass(implicit logger: Logger): ScalaDefinitionCoordinates = {
val (methodPrefix, methodName) = splitMethodName(definitionName)
ScalaDefinitionCoordinates(
providerPackageParts = providerPackageParts,
modulePackageParts = modulePackageParts,
definitionName = mangleTypeName(capitalize(definitionName)) + "Args"
definitionName = (methodPrefix :+ mangleTypeName(methodName)).mkString("") + "Args"
)

def methodResultClass(implicit logger: Logger): ScalaDefinitionCoordinates =
}
def methodResultClass(implicit logger: Logger): ScalaDefinitionCoordinates = {
val (methodPrefix, methodName) = splitMethodName(definitionName)
ScalaDefinitionCoordinates(
providerPackageParts = providerPackageParts,
modulePackageParts = modulePackageParts,
definitionName = mangleTypeName(capitalize(definitionName)) + "Result"
definitionName = (methodPrefix :+ mangleTypeName(methodName)).mkString("") + "Result"
)
}
}

object PulumiDefinitionCoordinates {

def fromRawToken(
typeToken: String,
moduleToPackageParts: String => Seq[String],
providerToPackageParts: String => Seq[String]
): PulumiDefinitionCoordinates = {
val PulumiToken(providerName, modulePortion, definitionName) = PulumiToken(typeToken)
fromToken(PulumiToken(typeToken), moduleToPackageParts, providerToPackageParts)
}

def fromToken(
typeToken: PulumiToken,
moduleToPackageParts: String => Seq[String],
providerToPackageParts: String => Seq[String]
): PulumiDefinitionCoordinates = {
val PulumiToken(providerName, modulePortion, definitionName) = typeToken
PulumiDefinitionCoordinates(
providerPackageParts = providerToPackageParts(providerName),
modulePackageParts = moduleToPackageParts(modulePortion),
Expand Down
Loading