From fe0acca81132dd18d910e7b887d42636273232d5 Mon Sep 17 00:00:00 2001 From: Julia Plewa Date: Mon, 28 Aug 2023 16:02:06 +0200 Subject: [PATCH] extract shared classes to separate SDK Signed-off-by: Julia Plewa --- .github/workflows/publish_sdk.yml | 27 + .github/workflows/publish_to_maven_local.yml | 15 +- sdk/build.gradle.kts | 124 +++++ .../main/kotlin/com/pulumi/kotlin/Common.kt | 115 ++++ .../pulumi/kotlin/PulumiNullFieldException.kt | 5 + .../com/pulumi/kotlin/ResourceMapping.kt | 78 +++ .../kotlin/com/pulumi/kotlin/options/Alias.kt | 211 ++++++++ .../kotlin/options/CustomResourceOptions.kt | 492 ++++++++++++++++++ .../pulumi/kotlin/options/CustomTimeouts.kt | 72 +++ .../kotlin/options/ResourceTransformation.kt | 57 ++ settings.gradle.kts | 3 +- 11 files changed, 1197 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/publish_sdk.yml create mode 100644 sdk/build.gradle.kts create mode 100644 sdk/src/main/kotlin/com/pulumi/kotlin/Common.kt create mode 100644 sdk/src/main/kotlin/com/pulumi/kotlin/PulumiNullFieldException.kt create mode 100644 sdk/src/main/kotlin/com/pulumi/kotlin/ResourceMapping.kt create mode 100644 sdk/src/main/kotlin/com/pulumi/kotlin/options/Alias.kt create mode 100644 sdk/src/main/kotlin/com/pulumi/kotlin/options/CustomResourceOptions.kt create mode 100644 sdk/src/main/kotlin/com/pulumi/kotlin/options/CustomTimeouts.kt create mode 100644 sdk/src/main/kotlin/com/pulumi/kotlin/options/ResourceTransformation.kt diff --git a/.github/workflows/publish_sdk.yml b/.github/workflows/publish_sdk.yml new file mode 100644 index 000000000..e241112ff --- /dev/null +++ b/.github/workflows/publish_sdk.yml @@ -0,0 +1,27 @@ +name: Prepare release of SDK + +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 packages + 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 diff --git a/.github/workflows/publish_to_maven_local.yml b/.github/workflows/publish_to_maven_local.yml index d3db93085..9fc47fc88 100644 --- a/.github/workflows/publish_to_maven_local.yml +++ b/.github/workflows/publish_to_maven_local.yml @@ -17,8 +17,21 @@ 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-kotlin to Maven Local Repository + name: Publish pulumi-kotlin provider SDKs to Maven Local Repository runs-on: [ self-hosted, active ] if: ${{ startsWith(github.head_ref, 'prepare-release') || github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} strategy: diff --git a/sdk/build.gradle.kts b/sdk/build.gradle.kts new file mode 100644 index 000000000..4fbefa405 --- /dev/null +++ b/sdk/build.gradle.kts @@ -0,0 +1,124 @@ +import org.jetbrains.dokka.gradle.DokkaTask + +plugins { + kotlin("jvm") + `java-library` + `maven-publish` + id("org.jetbrains.dokka") + signing +} + +group = "org.virtuslab" +version = "0.1.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("sourcesJar") { + group = "build" + from(sourceSets.main.get().allSource) + archiveClassifier.set("sources") +} + +tasks.withType { + moduleName.set("pulumi-kotlin") +} + +task("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("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()) { + signing { + sign(it) + } + } + } + } + } +} + +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("ddzikon@virtuslab.com") + organization.set("VirtusLab") + } + developer { + name.set("Michal Fudala") + email.set("mfudala@virtuslab.com") + organization.set("VirtusLab") + } + developer { + name.set("Julia Plewa") + email.set("jplewa@virtuslab.com") + 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") + } + } +} diff --git a/sdk/src/main/kotlin/com/pulumi/kotlin/Common.kt b/sdk/src/main/kotlin/com/pulumi/kotlin/Common.kt new file mode 100644 index 000000000..df49b604b --- /dev/null +++ b/sdk/src/main/kotlin/com/pulumi/kotlin/Common.kt @@ -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.applySuspend(block: T.() -> Unit): T { + block() + return this +} + +interface ConvertibleToJava { + 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 + get() = javaResource.urn() + + val pulumiChildResources: Set + get() = javaResource.pulumiChildResources() + .map { + GlobalResourceMapper.tryMap(it)!! + } + .toSet() + + protected constructor( + javaResource: JavaResource, + mapper: ResourceMapper, + ) : 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(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(javaResource, mapper) { + val id: Output + 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(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. + *

+ * 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)) diff --git a/sdk/src/main/kotlin/com/pulumi/kotlin/PulumiNullFieldException.kt b/sdk/src/main/kotlin/com/pulumi/kotlin/PulumiNullFieldException.kt new file mode 100644 index 000000000..87dce8134 --- /dev/null +++ b/sdk/src/main/kotlin/com/pulumi/kotlin/PulumiNullFieldException.kt @@ -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)", +) diff --git a/sdk/src/main/kotlin/com/pulumi/kotlin/ResourceMapping.kt b/sdk/src/main/kotlin/com/pulumi/kotlin/ResourceMapping.kt new file mode 100644 index 000000000..f9c08b1aa --- /dev/null +++ b/sdk/src/main/kotlin/com/pulumi/kotlin/ResourceMapping.kt @@ -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 { + /** + * 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> = 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?): 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?): Output? { + 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) = mappers.add(mapper) + + /** + * Removes every registered mapper from internal collection. + */ + internal fun clearMappers() = mappers.clear() +} diff --git a/sdk/src/main/kotlin/com/pulumi/kotlin/options/Alias.kt b/sdk/src/main/kotlin/com/pulumi/kotlin/options/Alias.kt new file mode 100644 index 000000000..ec78b0d1e --- /dev/null +++ b/sdk/src/main/kotlin/com/pulumi/kotlin/options/Alias.kt @@ -0,0 +1,211 @@ +package com.pulumi.kotlin.options + +import com.pulumi.core.Output +import com.pulumi.kotlin.ConvertibleToJava +import com.pulumi.kotlin.GlobalResourceMapper +import com.pulumi.kotlin.KotlinResource +import com.pulumi.kotlin.PulumiTagMarker +import com.pulumi.core.Alias as JavaAlias + +/** + * Alias is a description of prior name used for a resource. It can be processed in the + * context of resource creation to determine what the full aliased URN would be. + * + * The presence of a property indicates if its value should be used. + * If absent (i.e. "null"), then the value is not used. + * + * Note: because of the above, there needs to be special handling to indicate that the previous + * "parent" of a [KotlinResource] was "null". + * Specifically, pass in: [Alias.noParent] + * @see [CustomResourceOptions.aliases] + * @see [JavaAlias] + */ +class Alias internal constructor(private val javaBackingObject: JavaAlias) : ConvertibleToJava { + + /** + * The previous urn to alias to. If this is provided, no other properties in this type should be provided. + */ + val urn: String? + get() = javaBackingObject.urn.orElse(null) + + /** + * The previous name of the resource. + * If empty, the current name of the resource is used. + */ + val name: Output? + get() = javaBackingObject.name.orElse(null) + + /** + * The previous type of the resource. If empty, the current type of the resource is used. + */ + val type: Output? + get() = javaBackingObject.type.orElse(null) + + /** + * The previous stack of the resource. If null, defaults to the value of `Pulumi.IDeployment.StackName`. + */ + val stack: Output? + get() = javaBackingObject.stack.orElse(null) + + /** + * The previous project of the resource. If null, defaults to the value of `Pulumi.IDeployment.ProjectName`. + */ + val project: Output? + get() = javaBackingObject.project.orElse(null) + + /** + * The previous parent of the resource. If null, the current parent of the resource is used. + * + * Only specify one of [Alias.parent] or [Alias.parentUrn] or [Alias.noParent]. + */ + val parent: KotlinResource? + get() = GlobalResourceMapper.tryMap(javaBackingObject.parent) + + /** + * The previous parent of the resource. If null, the current parent of the resource is used. + * + * Only specify one of [Alias.parent] or [Alias.parentUrn] or [Alias.noParent]. + */ + val parentUrn: Output? + get() = javaBackingObject.parentUrn.orElse(null) + + /** + * Used to indicate the resource previously had no parent. If `false` this property is ignored. + * + * Only specify one of [Alias.parent] or [Alias.parentUrn] or [Alias.noParent]. + */ + val noParent: Boolean + get() = javaBackingObject.hasNoParent() + + override fun toJava(): JavaAlias = javaBackingObject +} + +/** + * Builder for [Alias]. + */ +@PulumiTagMarker +@Suppress("TooManyFunctions") // different overloads of method for the same property are required +class AliasBuilder internal constructor( + var name: Output? = null, + var type: Output? = null, + var stack: Output? = null, + var project: Output? = null, + var parent: KotlinResource? = null, + var parentUrn: Output? = null, +) { + + /** + * @see [Alias.name] + */ + fun name(value: Output?) { + this.name = value + } + + /** + * @see [Alias.name] + */ + fun name(value: String?) { + this.name = Output.ofNullable(value) + } + + /** + * @see [Alias.type] + */ + fun type(value: Output?) { + this.type = value + } + + /** + * @see [Alias.type] + */ + fun type(value: String?) { + this.type = Output.ofNullable(value) + } + + /** + * @see [Alias.stack] + */ + fun stack(value: Output?) { + this.stack = value + } + + /** + * @see [Alias.stack] + */ + fun stack(value: String?) { + this.stack = Output.ofNullable(value) + } + + /** + * @see [Alias.project] + */ + fun project(value: Output?) { + this.project = value + } + + /** + * @see [Alias.project] + */ + fun project(value: String?) { + this.project = Output.ofNullable(value) + } + + /** + * @see [Alias.parent] + */ + fun parent(value: KotlinResource?) { + // TODO verify why java implementation mentions about requiring null in parentUrn but checks null in name + // requireNullState(name, () -> "Alias should not specify Alias#parent when Alias#parentUrn is already."); + // @see https://github.com/VirtuslabRnD/jvm-lab/issues/54 + this.parent = value + } + + /** + * @see [Alias.parentUrn] + */ + fun parentUrn(value: Output?) { + // TODO verify why java implementation mentions about requiring null in parent but checks null in name + // requireNullState(name, () -> "Alias should not specify Alias#parent when Alias#parent is already."); + // @see https://github.com/VirtuslabRnD/jvm-lab/issues/54 + this.parentUrn = value + } + + internal fun build(): Alias { + val javaAliasBuilder = JavaAlias.builder() + + // FIXME null-checks are necessary workaround for requireNullState(name) in java implementation, + // correct this code after fixing the issue in java + if (name != null) javaAliasBuilder.name(name) + if (type != null) javaAliasBuilder.type(type) + if (stack != null) javaAliasBuilder.stack(stack) + if (project != null) javaAliasBuilder.project(project) + if (parent != null) javaAliasBuilder.parent(parent?.javaResource) + if (parentUrn != null) javaAliasBuilder.parentUrn(parentUrn) + + val javaAlias = javaAliasBuilder.build() + return Alias(javaAlias) + } +} + +/** + * Creates [Alias] with use of type-safe [AliasBuilder]. + */ +suspend fun alias(block: suspend AliasBuilder.() -> Unit): Alias { + val aliasBuilder = AliasBuilder() + block(aliasBuilder) + return aliasBuilder.build() +} + +/** + * Creates [Alias] with empty properties and without parent (`noParent` set to `true`). + * + * @see [Alias.noParent] + */ +fun noParent(): Alias = Alias(JavaAlias.noParent()) + +/** + * Creates [Alias] with given URN and other properties empty. + * + * @see [Alias.urn] + */ +fun withUrn(urn: String): Alias = Alias(JavaAlias.withUrn(urn)) diff --git a/sdk/src/main/kotlin/com/pulumi/kotlin/options/CustomResourceOptions.kt b/sdk/src/main/kotlin/com/pulumi/kotlin/options/CustomResourceOptions.kt new file mode 100644 index 000000000..a6aaae718 --- /dev/null +++ b/sdk/src/main/kotlin/com/pulumi/kotlin/options/CustomResourceOptions.kt @@ -0,0 +1,492 @@ +package com.pulumi.kotlin.options + +import com.pulumi.core.Output +import com.pulumi.kotlin.ConvertibleToJava +import com.pulumi.kotlin.GlobalResourceMapper +import com.pulumi.kotlin.KotlinProviderResource +import com.pulumi.kotlin.KotlinResource +import com.pulumi.kotlin.PulumiTagMarker +import com.pulumi.resources.CustomResourceOptions as JavaCustomResourceOptions + +/** + * A bag of optional settings that control a [KotlinResource] behavior. + */ +class CustomResourceOptions internal constructor( + private val javaBackingObject: JavaCustomResourceOptions, +) : ConvertibleToJava { + + /** + * The names of outputs for this resource that should be treated as secrets. This augments + * the list that the resource provider and pulumi engine already determine based on inputs + * to your resource. It can be used to mark certain outputs as a secrets on a per-resource + * basis. + */ + val additionalSecretOutputs: List? + get() = javaBackingObject.additionalSecretOutputs + + /** + * List of aliases for a resource or component resource. If you're changing the name, type, + * or parent path of a resource or component resource, you can add the old name to the list of aliases + * for a resource to ensure that existing resources will be migrated to the new name instead of being deleted + * and replaced with the new named resource. + * + * The aliases option accepts a list of old identifiers. If a resource has been renamed multiple times, + * it can have many aliases. The list of aliases may contain old [Alias] objects and/or old resource URNs. + */ + val aliases: List>? + get() = javaBackingObject.aliases?.map { it.applyValue { javaAlias -> Alias(javaAlias) } } + + /** + * Set of custom timeouts for create, update, and delete operations on a resource. + * These timeouts are specified using a duration string such as “5m” (5 minutes), “40s” (40 seconds), + * or “1d” (1 day). Supported duration units are “ns”, “us” (or “µs”), “ms”, “s”, “m”, and “h” + * (nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively). + * + * For the most part, Pulumi automatically waits for operations to complete and times out appropriately. + * In some circumstances, such as working around bugs in the infrastructure provider, + * custom timeouts may be necessary. + * + * The [customTimeouts] resource option does not apply to component resources, + * and will not have the intended effect. + */ + val customTimeouts: CustomTimeouts? + get() = javaBackingObject.customTimeouts.map { CustomTimeouts(it) }.orElse(null) + + /** + * A resource may need to be replaced if an immutable property changes. In these cases, + * cloud providers do not support updating an existing resource so a new instance will be created + * and the old one deleted. By default, to minimize downtime, Pulumi creates new instances of resources + * before deleting old ones. + * + * Setting the [deleteBeforeReplace] option to true means that Pulumi will delete the existing resource + * before creating its replacement. Be aware that this behavior has a cascading impact on dependencies + * so more resources may be replaced, which can lead to downtime. However, this option may be necessary + * for some resources that manage scarce resources behind the scenes, + * and/or resources that cannot exist side-by-side. + */ + val deleteBeforeReplace: Boolean + get() = javaBackingObject.deleteBeforeReplace + + /** + * The [dependsOn] resource option creates a list of explicit dependencies between resources. + * + * Pulumi automatically tracks dependencies between resources when you supply an input argument + * that came from another resource's output properties. In some cases, however, + * you may need to explicitly specify additional dependencies that Pulumi doesn't know about + * but must still respect. This might happen if a dependency is external to the infrastructure itself, + * such as an application dependency—or is implied due to an ordering or eventual consistency requirement. + * The [dependsOn] option ensures that resource creation, update, and deletion operations + * are done in the correct order. + */ + val dependsOn: Output>? + get() = javaBackingObject.dependsOn?.applyValue { list -> list.mapNotNull { GlobalResourceMapper.tryMap(it) } } + + /** + * An optional existing ID to load, rather than create. + */ + val id: Output? + get() = javaBackingObject.id.orElse(null) + + /** + * The [ignoreChanges] resource option specifies a list of properties + * that Pulumi will ignore when it updates existing resources. Any properties specified in this list + * that are also specified in the resource's arguments will only be used when creating the resource. + */ + val ignoreChanges: List? + get() = javaBackingObject.ignoreChanges + + /** + * The [importId] resource option imports an existing cloud resource so that Pulumi can manage it. + * Imported resources can have been provisioned by any other method, including manually in the cloud console + * or with the cloud CLI. + * + * To import a resource, first specify the [importId] option with the resource's ID. + * This ID is the same as would be returned by the id property for any resource created by Pulumi; + * the ID is resource-specific. Pulumi reads the current state of the resource + * with the given ID from the cloud provider. Next, you must specify all required arguments + * to the resource constructor so that it exactly matches the state to import. By doing this, + * you end up with a Pulumi program that will accurately generate a matching desired state. + */ + val importId: String? + get() = javaBackingObject.importId?.orElse(null) + + /** + * Specifies a parent for a resource. It is used to associate children with the parents that encapsulate + * or are responsible for them. + */ + val parent: KotlinResource? + get() = GlobalResourceMapper.tryMap(javaBackingObject.parent.orElse(null)) + + /** + * An optional URL, corresponding to the url from which the provider plugin that should be + * used when operating on this resource is downloaded from. This URL overrides the download URL + * inferred from the current package and should rarely be used. + */ + val pluginDownloadURL: String? + get() = javaBackingObject.pluginDownloadURL.orElse(null) + + /** + * Marks a resource as protected. A protected resource cannot be deleted directly, and it will be an error + * to do a Pulumi deployment which tries to delete a protected resource for any reason. + * + * To delete a protected resource, it must first be unprotected. There are two ways to unprotect a resource: + * * Set `protect(false)` and then run `pulumi up` + * * Use the `pulumi state unprotect` command + * + * Once the resource is unprotected, it can be deleted as part of a following update. + * + * The default is to inherit this value from the parent resource, and false for resources without a parent. + */ + val protect: Boolean + get() = javaBackingObject.isProtect + + /** + * An optional provider to use for this resource's CRUD operations. If no provider is + * supplied, the default provider for the resource's package will be used. The default + * provider is pulled from the parent's provider bag. + */ + val provider: KotlinProviderResource? + get() = GlobalResourceMapper.tryMap(javaBackingObject.provider.orElse(null)) as KotlinProviderResource? + + /** + * Indicates that changes to certain properties on a resource should force a replacement of the resource + * instead of an in-place update. + */ + val replaceOnChanges: List? + get() = javaBackingObject.replaceOnChanges + + /** + * Provides a list of transformations to apply to a resource and all of its children. + * This option is used to override or modify the inputs to the child resources of a component resource. + * One example is to use the option to add other resource options (such as ignoreChanges or protect). + * Another example is to modify an input property (such as adding to tags or changing a property + * that is not directly configurable). + * + * Each transformation is a callback that gets invoked by the Pulumi runtime. + * It receives the resource type, name, input properties, resource options, and the resource instance object itself. + * The callback returns a new set of resource input properties and resource options that will be used + * to construct the resource instead of the original values. + */ + val resourceTransformations: List? + get() = javaBackingObject.resourceTransformations?.map { jrt -> + ResourceTransformation { + jrt.apply(it).orElse(null) + } + } + + /** + * Marks a resource to be retained. If this option is set then Pulumi will not call through + * to the resource provider's `Delete` method when deleting or replacing the resource during `pulumi up` + * or `pulumi destroy`. As a result, the resource will not be deleted from the backing cloud provider, + * but will be removed from the Pulumi state. + * + * If a retained resource is deleted by Pulumi and you later want to actually delete it from the backing cloud + * provider you will either need to use your provider's manual interface to find and delete the resource, + * or import the resource back into Pulumi to unset [retainOnDelete] and delete it again fully. + * + * To actually delete a retained resource, this setting must first be set to `false`. + */ + val retainOnDelete: Boolean + get() = javaBackingObject.isRetainOnDelete + + /** + * The URN of a previously-registered resource of this type to read from the engine. + */ + val urn: String? + get() = javaBackingObject.urn.orElse(null) + + /** + * Specifies a provider version to use when operating on a resource. This version overrides the version + * information inferred from the current package. This option should be used rarely. + */ + val version: String? + get() = javaBackingObject.version.orElse(null) + + // generated resources use no args constructor as default value for options + constructor() : this(JavaCustomResourceOptions.Empty) + + override fun toJava(): JavaCustomResourceOptions = javaBackingObject +} + +/** + * Builder for [CustomResourceOptions]. + */ +@PulumiTagMarker +@Suppress("TooManyFunctions") // different overloads of method for the same property are required +data class CustomResourceOptionsBuilder( + var additionalSecretOutputs: List? = null, + var aliases: List>? = null, + var customTimeouts: CustomTimeouts? = null, + var deleteBeforeReplace: Boolean = false, + var dependsOn: Output>? = null, + var id: Output? = null, + var ignoreChanges: List? = null, + var importId: String? = null, + var parent: KotlinResource? = null, + var pluginDownloadURL: String? = null, + var protect: Boolean = false, + var provider: KotlinProviderResource? = null, + var replaceOnChanges: List? = null, + var resourceTransformations: List? = null, + var retainOnDelete: Boolean = false, + var urn: String? = null, + var version: String? = null, + var mergeWith: CustomResourceOptions? = null, +) { + + /** + * @see [CustomResourceOptions.additionalSecretOutputs] + */ + fun additionalSecretOutputs(value: List?) { + this.additionalSecretOutputs = value + } + + /** + * @see [CustomResourceOptions.additionalSecretOutputs] + */ + fun additionalSecretOutputs(vararg values: String) { + this.additionalSecretOutputs = values.toList() + } + + /** + * @see [CustomResourceOptions.aliases] + */ + fun aliases(vararg values: Alias) { + aliases(values.map { Output.of(it) }) + } + + /** + * @see [CustomResourceOptions.aliases] + */ + fun aliases(vararg values: Output) { + aliases(values.toList()) + } + + /** + * @see [CustomResourceOptions.aliases] + */ + fun aliases(value: List>?) { + this.aliases = value + } + + /** + * @see [CustomResourceOptions.aliases] + */ + suspend fun aliases(vararg blocks: suspend AliasBuilder.() -> Unit) { + this.aliases = blocks.map { alias(it) }.map { Output.of(it) } + } + + /** + * @see [CustomResourceOptions.customTimeouts] + */ + fun customTimeouts(value: CustomTimeouts?) { + this.customTimeouts = value + } + + /** + * @see [CustomResourceOptions.customTimeouts] + */ + suspend fun customTimeouts(block: suspend CustomTimeoutsBuilder.() -> Unit) { + val customTimeoutsBuilder = CustomTimeoutsBuilder() + block(customTimeoutsBuilder) + this.customTimeouts = customTimeoutsBuilder.build() + } + + /** + * @see [CustomResourceOptions.deleteBeforeReplace] + */ + fun deleteBeforeReplace(value: Boolean) { + this.deleteBeforeReplace = value + } + + /** + * @see [CustomResourceOptions.dependsOn] + */ + fun dependsOn(vararg values: KotlinResource) { + dependsOn(values.asList()) + } + + /** + * @see [CustomResourceOptions.dependsOn] + */ + fun dependsOn(values: List?) { + this.dependsOn = Output.ofNullable(values) + } + + /** + * @see [CustomResourceOptions.dependsOn] + */ + fun dependsOn(value: Output>?) { + this.dependsOn = value + } + + /** + * @see [CustomResourceOptions.id] + */ + fun id(value: String?) { + id(Output.ofNullable(value)) + } + + /** + * @see [CustomResourceOptions.id] + */ + fun id(value: Output?) { + this.id = value + } + + /** + * @see [CustomResourceOptions.ignoreChanges] + */ + fun ignoreChanges(vararg values: String) { + ignoreChanges(values.toList()) + } + + /** + * @see [CustomResourceOptions.ignoreChanges] + */ + fun ignoreChanges(value: List?) { + this.ignoreChanges = value + } + + /** + * @see [CustomResourceOptions.importId] + */ + fun importId(value: String?) { + this.importId = value + } + + /** + * @see [CustomResourceOptions.parent] + */ + fun parent(value: KotlinResource?) { + this.parent = value + } + + /** + * @see [CustomResourceOptions.pluginDownloadURL] + */ + fun pluginDownloadURL(value: String?) { + this.pluginDownloadURL = value + } + + /** + * @see [CustomResourceOptions.protect] + */ + fun protect(value: Boolean) { + this.protect = value + } + + /** + * @see [CustomResourceOptions.provider] + */ + fun provider(value: KotlinProviderResource?) { + this.provider = value + } + + /** + * @see [CustomResourceOptions.replaceOnChanges] + */ + fun replaceOnChanges(vararg values: String) { + replaceOnChanges(values.toList()) + } + + /** + * @see [CustomResourceOptions.replaceOnChanges] + */ + fun replaceOnChanges(value: List?) { + this.replaceOnChanges = value + } + + /** + * @see [CustomResourceOptions.resourceTransformations] + */ + fun resourceTransformations(vararg values: ResourceTransformation) { + resourceTransformations(values.toList()) + } + + /** + * @see [CustomResourceOptions.resourceTransformations] + */ + fun resourceTransformations(values: List) { + this.resourceTransformations = values + } + + /** + * @see [CustomResourceOptions.retainOnDelete] + */ + fun retainOnDelete(value: Boolean) { + this.retainOnDelete = value + } + + /** + * @see [CustomResourceOptions.urn] + */ + fun urn(value: String?) { + this.urn = value + } + + /** + * @see [CustomResourceOptions.version] + */ + fun version(value: String?) { + this.version = value + } + + /** + * Takes existing [CustomResourceOptions] values and prepares type-safe builder + * to merge current [CustomResourceOptions] over the same properties of given [CustomResourceOptions]. + * + * The original options objects will be unchanged. + * + * Conceptually property merging follows these basic rules: + * * If the property is a collection, the final value will be a collection containing the + * values from each option object. + * * Simple scalar values from "options2" (i.e. "string", "int", "bool") will replace the values of "options1". + * * "null" values in "options2" will be ignored. + */ + fun mergeWith(opts: CustomResourceOptions?) { + mergeWith = opts + } + + internal fun build(): CustomResourceOptions { + val javaBackingObject = JavaCustomResourceOptions.builder() + .additionalSecretOutputs(additionalSecretOutputs) + .aliases(aliases?.map { output -> output.applyValue { it.toJava() } }) + .customTimeouts(customTimeouts?.toJava()) + .deleteBeforeReplace(deleteBeforeReplace) + .dependsOn(dependsOn?.applyValue { list -> list.map { it.javaResource } }) + .id(id) + .ignoreChanges(ignoreChanges) + .importId(importId) + .parent(parent?.javaResource) + .pluginDownloadURL(pluginDownloadURL) + .protect(protect) + .provider(provider?.javaResource) + .replaceOnChanges(replaceOnChanges) + .resourceTransformations(resourceTransformations?.map { it.toJava() }) + .retainOnDelete(retainOnDelete) + .urn(urn) + .version(version) + .build() + + return if (mergeWith == null) { + CustomResourceOptions(javaBackingObject) + } else { + CustomResourceOptions( + JavaCustomResourceOptions.merge( + mergeWith?.toJava(), + javaBackingObject, + ), + ) + } + } +} + +/** + * Creates [CustomResourceOptions] with use of type-safe [CustomResourceOptionsBuilder]. + */ +suspend fun opts(block: suspend CustomResourceOptionsBuilder.() -> Unit): CustomResourceOptions { + val customResourceOptionsBuilder = CustomResourceOptionsBuilder() + block(customResourceOptionsBuilder) + return customResourceOptionsBuilder.build() +} diff --git a/sdk/src/main/kotlin/com/pulumi/kotlin/options/CustomTimeouts.kt b/sdk/src/main/kotlin/com/pulumi/kotlin/options/CustomTimeouts.kt new file mode 100644 index 000000000..a9f8f78a1 --- /dev/null +++ b/sdk/src/main/kotlin/com/pulumi/kotlin/options/CustomTimeouts.kt @@ -0,0 +1,72 @@ +package com.pulumi.kotlin.options + +import com.pulumi.kotlin.ConvertibleToJava +import com.pulumi.kotlin.PulumiTagMarker +import java.util.Optional +import kotlin.time.Duration +import kotlin.time.toJavaDuration +import kotlin.time.toKotlinDuration +import com.pulumi.resources.CustomTimeouts as JavaCustomTimeouts + +/** + * Optional timeouts to supply in as [CustomResourceOptions.customTimeouts]. + * + * @see [CustomResourceOptions.customTimeouts] + * @see [JavaCustomTimeouts] + */ +class CustomTimeouts internal constructor(private val javaBackingObject: JavaCustomTimeouts) : + ConvertibleToJava { + val create: Duration? + get() = javaBackingObject.create.orElse(null)?.toKotlinDuration() + val update: Duration? + get() = javaBackingObject.update.orElse(null)?.toKotlinDuration() + val delete: Duration? + get() = javaBackingObject.delete.orElse(null)?.toKotlinDuration() + + override fun toJava(): JavaCustomTimeouts { + return javaBackingObject + } + + companion object { + fun golangString(duration: Duration?): String { + return JavaCustomTimeouts.golangString(Optional.ofNullable(duration?.toJavaDuration())) + } + } +} + +/** + * Builder for [CustomTimeouts] + */ +@PulumiTagMarker +class CustomTimeoutsBuilder(var create: Duration? = null, var update: Duration? = null, var delete: Duration? = null) { + fun create(value: Duration?) { + this.create = value + } + + fun update(value: Duration?) { + this.update = value + } + + fun delete(value: Duration?) { + this.delete = value + } + + internal fun build(): CustomTimeouts { + return CustomTimeouts( + JavaCustomTimeouts( + create?.toJavaDuration(), + update?.toJavaDuration(), + delete?.toJavaDuration(), + ), + ) + } +} + +/** + * Creates [CustomTimeouts] with use of type-safe [CustomTimeoutsBuilder]. + */ +suspend fun customTimeouts(block: suspend CustomTimeoutsBuilder.() -> Unit): CustomTimeouts { + val customTimeoutsBuilder = CustomTimeoutsBuilder() + block(customTimeoutsBuilder) + return customTimeoutsBuilder.build() +} diff --git a/sdk/src/main/kotlin/com/pulumi/kotlin/options/ResourceTransformation.kt b/sdk/src/main/kotlin/com/pulumi/kotlin/options/ResourceTransformation.kt new file mode 100644 index 000000000..62d515170 --- /dev/null +++ b/sdk/src/main/kotlin/com/pulumi/kotlin/options/ResourceTransformation.kt @@ -0,0 +1,57 @@ +package com.pulumi.kotlin.options + +import java.util.Optional +import com.pulumi.resources.ResourceArgs as JavaResourceArgs +import com.pulumi.resources.ResourceOptions as JavaResourceOptions +import com.pulumi.resources.ResourceTransformation as JavaResourceTransformation +import com.pulumi.resources.ResourceTransformation.Args as JavaResourceTransformationArgs +import com.pulumi.resources.ResourceTransformation.Result as JavaResourceTransformationResult + +/** + * The callback signature for the [CustomResourceOptions.resourceTransformations] option. + * + * @see [JavaResourceTransformation] + */ +fun interface ResourceTransformation { + fun apply(args: JavaResourceTransformationArgs): JavaResourceTransformationResult? +} + +internal fun ResourceTransformation.toJava(): JavaResourceTransformation { + return JavaResourceTransformation { javaArgs -> Optional.ofNullable(this.apply(javaArgs)) } +} + +internal fun JavaResourceTransformation.toKotlin(): ResourceTransformation { + return ResourceTransformation { this.apply(it).orElse(null) } +} + +/** + * Builder for [JavaResourceTransformationResult]. + */ +class ResourceTransformationResultBuilder( + var args: JavaResourceArgs? = null, + var options: JavaResourceOptions? = null, +) { + + fun args(value: JavaResourceArgs) { + this.args = value + } + + fun options(value: JavaResourceOptions) { + this.options = value + } + + @Suppress("UnsafeCallOnNullableType") // NPE calls will be removed in the future https://github.com/VirtuslabRnD/pulumi-kotlin/issues/62 + internal fun build(): JavaResourceTransformationResult = JavaResourceTransformationResult(args!!, options!!) +} + +/** + * Creates [JavaResourceTransformationResult] + * with use of type-safe [ResourceTransformationResultBuilder]. + */ +fun transformationResult( + block: ResourceTransformationResultBuilder.() -> Unit, +): JavaResourceTransformationResult { + val builder = ResourceTransformationResultBuilder() + block(builder) + return builder.build() +} diff --git a/settings.gradle.kts b/settings.gradle.kts index cc7ddfeb6..7b961faff 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,2 @@ -rootProject.name = "pulumi-kotlin" +rootProject.name = "pulumi-kotlin-generator" +include("sdk")