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

Extract shared classes to a separate SDK #300

Merged
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
27 changes: 27 additions & 0 deletions .github/workflows/publish_sdk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Prepare release of SDK
jplewa marked this conversation as resolved.
Show resolved Hide resolved

on:
workflow_dispatch:

jobs:
publish:
name: Publish package to Maven Central
runs-on: ubuntu-latest
steps:
- name: Check out project sources
uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
distribution: adopt
java-version: 11
- name: Publish package
run: |
./gradlew publishPulumiKotlinSdkPublicationToMavenCentralRepository \
-Psigning.enabled=true \
-Psigning.key="${{ secrets.GPG_KEY }}" \
-Psigning.key.password="${{ secrets.GPG_KEY_PASSWORD }}" \
-Psonatype.username="${{ secrets.SONATYPE_USERNAME }}" \
-Psonatype.password="${{ secrets.SONATYPE_PASSWORD }}" \
-Dorg.gradle.daemon=false \
-q
13 changes: 13 additions & 0 deletions .github/workflows/publish_to_maven_local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ on:
workflow_dispatch:

jobs:
publish-sdk:
name: Publish pulumi-kotlin SDK to Maven Local Repository
runs-on: ubuntu-latest
steps:
- name: Check out project sources
uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
distribution: adopt
java-version: 11
- name: Publish to Maven Local
run: ./gradlew sdk:publishPulumiKotlinSdkPublicationToMavenLocal -Dorg.gradle.daemon=false -q
publish:
name: Publish pulumi-${{ matrix.provider }}-kotlin (${{ matrix.majorVersion }}) to Maven Local Repository
runs-on: [ self-hosted, active ]
Expand Down
128 changes: 128 additions & 0 deletions sdk/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import org.jetbrains.dokka.gradle.DokkaTask

plugins {
kotlin("jvm")
`java-library`
`maven-publish`
id("org.jetbrains.dokka")
signing
}

group = "org.virtuslab"
version = "0.9.4.0-SNAPSHOT"
base.archivesName.set("pulumi-kotlin-sdk")

repositories {
mavenCentral()
}

dependencies {
api("com.pulumi:pulumi:0.9.4")
api("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.7.2")
}

tasks.test {
useJUnitPlatform()
}

task<Jar>("sourcesJar") {
group = "build"
from(sourceSets.main.get().allSource)
archiveClassifier.set("sources")
}

tasks.withType<DokkaTask> {
moduleName.set("pulumi-kotlin")
}

task<Jar>("dokkaJavadocJar") {
dependsOn(tasks["dokkaHtml"])
group = "documentation"
from(tasks["dokkaHtml"])
archiveClassifier.set("javadoc")
}

publishing {
repositories {
maven {
name = "MavenCentral"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = findProperty("sonatype.username") as String?
password = findProperty("sonatype.password") as String?
}
}
}
publications {
create<MavenPublication>("pulumiKotlinSdk") {
artifact(tasks.named("sourcesJar"))
artifact(tasks.named("dokkaJavadocJar"))
from(components["java"])
artifactId = "pulumi-kotlin"
}

publications
.forEach {
if (it is MavenPublication) {
configurePom(it)
if ((findProperty("signing.enabled") as String).toBoolean()) {
val signingKey = findProperty("signing.key") as String?
val signingKeyPassword = findProperty("signing.key.password") as String?

signing {
sign(it)
useInMemoryPgpKeys(signingKey, signingKeyPassword)
}
}
}
}
}
}

fun configurePom(mavenPublication: MavenPublication) {
mavenPublication.pom {
name.set("Pulumi Kotlin")
description.set(
"Build cloud applications and infrastructure by combining the safety and reliability of infrastructure " +
"as code with the power of the Kotlin programming language.",
)
url.set("https://github.com/VirtuslabRnD/pulumi-kotlin")
inceptionYear.set("2022")

issueManagement {
system.set("GitHub")
url.set("https://github.com/VirtuslabRnD/pulumi-kotlin/issues")
}

licenses {
license {
name.set("The Apache License, Version 2.0")
url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
}
}

developers {
developer {
name.set("Dariusz Dzikon")
email.set("[email protected]")
organization.set("VirtusLab")
}
developer {
name.set("Michal Fudala")
email.set("[email protected]")
organization.set("VirtusLab")
}
developer {
name.set("Julia Plewa")
email.set("[email protected]")
organization.set("VirtusLab")
}
}

scm {
url.set("https://github.com/VirtuslabRnD/pulumi-kotlin/tree/v$version")
connection.set("scm:git:git://github.com/VirtuslabRnD/pulumi-kotlin.git")
developerConnection.set("scm:git:ssh://github.com:VirtuslabRnD/pulumi-kotlin.git")
}
}
}
115 changes: 115 additions & 0 deletions sdk/src/main/kotlin/com/pulumi/kotlin/Common.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.pulumi.kotlin

import com.pulumi.Context
import com.pulumi.core.Output
import kotlinx.coroutines.runBlocking
import com.pulumi.resources.ComponentResource as JavaComponentResource
import com.pulumi.resources.CustomResource as JavaCustomResource
import com.pulumi.resources.ProviderResource as JavaProviderResource
import com.pulumi.resources.Resource as JavaResource

@DslMarker
annotation class PulumiTagMarker

@Suppress("RedundantSuspendModifier")
suspend inline fun <T> T.applySuspend(block: T.() -> Unit): T {
block()
return this
}

interface ConvertibleToJava<T> {
fun toJava(): T
}

/**
* Parent class for resources within Kotlin SDK - equivalent to [JavaResource].
*
* Each resource within Kotlin SDK should have corresponding [ResourceMapper],
* in order to properly translate Java resources to Kotlin representation.
*
* This class serves only as parent for all resources and should not be instantiated,
* it cannot be sealed, because generated subclasses will be placed in other packages.
*/
@Suppress("UnnecessaryAbstractClass")
abstract class KotlinResource private constructor(internal open val javaResource: JavaResource) {

val pulumiResourceName: String
get() = javaResource.pulumiResourceName()

val pulumiResourceType: String
get() = javaResource.pulumiResourceType()

val urn: Output<String>
get() = javaResource.urn()

val pulumiChildResources: Set<KotlinResource>
get() = javaResource.pulumiChildResources()
.map {
GlobalResourceMapper.tryMap(it)!!
}
.toSet()

protected constructor(
javaResource: JavaResource,
mapper: ResourceMapper<KotlinResource>,
) : this(javaResource) {
GlobalResourceMapper.registerMapper(mapper)
}
}

/**
* Parent class for component resources within Kotlin SDK - equivalent to [JavaComponentResource].
*/
@Suppress("UnnecessaryAbstractClass")
abstract class KotlinComponentResource private constructor(
override val javaResource: JavaComponentResource,
mapper: ResourceMapper<KotlinResource>,
) : KotlinResource(javaResource, mapper)

/**
* Parent class for custom resources within Kotlin SDK - equivalent to [JavaCustomResource].
*/
@Suppress("UnnecessaryAbstractClass")
abstract class KotlinCustomResource internal constructor(
override val javaResource: JavaCustomResource,
mapper: ResourceMapper<KotlinResource>,
) : KotlinResource(javaResource, mapper) {
val id: Output<String>
get() = javaResource.id()
}

/**
* Parent class for provider resources within Kotlin SDK - equivalent to [JavaProviderResource].
*/
@Suppress("UnnecessaryAbstractClass")
abstract class KotlinProviderResource internal constructor(
override val javaResource: JavaProviderResource,
mapper: ResourceMapper<KotlinResource>,
) : KotlinResource(javaResource, mapper)

object Pulumi {

/**
* Run a Pulumi stack callback and wait for result.
* In case of an error terminates the process with [System.exit].
*
* @param block the stack to run in Pulumi runtime
*/
fun run(block: suspend (Context) -> Unit) {
com.pulumi.Pulumi.run {
runBlocking {
block(it)
}
}
}
}

/**
* Append a value wrapped in an [Output] to exported stack outputs.
* <p>
* This method mutates the context internal state.
* @param name name of the [Output]
* @param value the value to be wrapped in [Output]
* @return the current [Context]
*/
fun Context.export(name: String, value: Any): Context = export(name, Output.of(value))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.pulumi.kotlin

class PulumiNullFieldException(name: String) : RuntimeException(
"Field $name is required but was not set (or was set to null)",
)
78 changes: 78 additions & 0 deletions sdk/src/main/kotlin/com/pulumi/kotlin/ResourceMapping.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.pulumi.kotlin

import com.pulumi.core.Output
import java.util.Optional
import com.pulumi.resources.Resource as JavaResource

/**
* Interface for creation of mappers for particular resource types.
* It assumes that only subtypes of [JavaResource] from Java SDK can be mapped to Kotlin types.
*
* Type [T] is a specific subtype of [KotlinResource] representing provider's resource in Kotlin SDK,
* mapper will produce objects of this type.
*/
interface ResourceMapper<out T : KotlinResource> {
/**
* Returns `true` if given subtype of [JavaResource] matches the type
* of [KotlinResource]'s backing object (can be mapped to type [T]), `false` otherwise.
*/
fun supportsMappingOfType(javaResource: JavaResource): Boolean

/**
* Creates new instance of corresponding [KotlinResource] for given [JavaResource],
* with given [javaResource] as backing object.
*/
fun map(javaResource: JavaResource): T
}

/**
* General mapper for mapping Resources backed by java objects ([JavaResource]).
*
* **In order to work properly, a Kotlin resource should be declared first with use of type-safe builder.
* Only then a corresponding mapper will be registered in application's context.**
*/
internal object GlobalResourceMapper {
private val mappers: MutableList<ResourceMapper<KotlinResource>> = mutableListOf()

/**
* Looks for corresponding [ResourceMapper] to given [javaResource] and maps it to proper [KotlinResource].
* Returns null, if given [javaResource] is null.
*/
internal fun tryMap(javaResource: JavaResource?): KotlinResource? {
if (javaResource == null) return null

val mapper = requireNotNull(mappers.find { it.supportsMappingOfType(javaResource) }) {
"mapper for a type ${javaResource::class.java} was either not declared or not instantiated"
}

return mapper.map(javaResource)
}

/**
* If given [optionalJavaResource] is present, looks for corresponding [ResourceMapper]
* and maps it to proper [KotlinResource]. Otherwise, returns null.
*/
internal fun tryMap(optionalJavaResource: Optional<JavaResource>?): KotlinResource? {
return if (optionalJavaResource?.isPresent == true) tryMap(optionalJavaResource.get()) else null
}

/**
* Looks for corresponding [ResourceMapper] to given [outputJavaResource]
* and transforms it to proper [Output] with [KotlinResource].
* Returned [Output] can be empty if given [outputJavaResource] is empty.
*/
internal fun tryMap(outputJavaResource: Output<JavaResource>?): Output<KotlinResource?>? {
return outputJavaResource?.applyValue { tryMap(it) }
}

/**
* Adds given mapper to set of available mappers.
* Returns `true` if mapper was successfully added, `false` if it already existed within the internal collection.
*/
internal fun registerMapper(mapper: ResourceMapper<KotlinResource>) = mappers.add(mapper)

/**
* Removes every registered mapper from internal collection.
*/
internal fun clearMappers() = mappers.clear()
}
Loading