From 7f5f4865f6bd4cff9426588c5db255b94f1e42a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gi=C5=BCka?= Date: Tue, 9 Jul 2024 16:34:05 +0200 Subject: [PATCH] Aliases support --- besom-cfg/k8s/project.scala | 2 +- .../scala/besom/internal/ResourceAlias.scala | 48 +++++++++++++++++++ .../scala/besom/internal/ResourceOps.scala | 37 ++++++++++---- .../besom/internal/ResourceOptions.scala | 29 +++++++---- integration-tests/CoreTests.test.scala | 6 ++- .../resources/random-example/Main.scala | 10 +++- 6 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 core/src/main/scala/besom/internal/ResourceAlias.scala diff --git a/besom-cfg/k8s/project.scala b/besom-cfg/k8s/project.scala index 5523a985..669fb8cb 100644 --- a/besom-cfg/k8s/project.scala +++ b/besom-cfg/k8s/project.scala @@ -2,7 +2,7 @@ //> 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.14.0-core.0.4-SNAPSHOT +//> using dep org.virtuslab::besom-kubernetes:4.15.0-core.0.4-SNAPSHOT //> using dep com.lihaoyi::fansi::0.5.0 //> using dep com.lihaoyi::fastparse:3.1.0 diff --git a/core/src/main/scala/besom/internal/ResourceAlias.scala b/core/src/main/scala/besom/internal/ResourceAlias.scala new file mode 100644 index 00000000..738ea71c --- /dev/null +++ b/core/src/main/scala/besom/internal/ResourceAlias.scala @@ -0,0 +1,48 @@ +package besom.internal + +/** Allows to specify details of parent of resource for alias. + */ +sealed trait ResourceAliasParent + +/** Allows to specify urn of parent. + * @param urn + * of parent + */ +final case class ResourceAliasParentUrn(urn: String) extends ResourceAliasParent + +/** Allows to indicate that resource previously had no parent. This property is ignored when set to false. + * @param noParent + * set to true when no parent + */ +final case class ResourceAliasNoParent(noParent: Boolean) extends ResourceAliasParent + +/** The alias for a resource or component resource. It can be any combination of the old name, type, parent, stack, and/or project values. + * Alternatively, you can just specify the URN directly. + */ +sealed trait ResourceAlias + +/** Allows to specify urn of resource. + * @param urn + * of resource + */ +final case class UrnResourceAlias(urn: String) extends ResourceAlias + +/** Allows to specify any combination of old name, type, parent, stack, and/or project values. + * @param name + * of the old resource + * @param `type` + * of the old resource + * @param stack + * of the old resource + * @param project + * of the old resource + * @param parent + * of the old resource + */ +final case class SpecResourceAlias( + name: Option[String] = None, + `type`: Option[String] = None, + stack: Option[String] = None, + project: Option[String] = None, + parent: Option[ResourceAliasParent] = None +) extends ResourceAlias diff --git a/core/src/main/scala/besom/internal/ResourceOps.scala b/core/src/main/scala/besom/internal/ResourceOps.scala index 01498fcc..98a7e92c 100644 --- a/core/src/main/scala/besom/internal/ResourceOps.scala +++ b/core/src/main/scala/besom/internal/ResourceOps.scala @@ -324,7 +324,7 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): providerId: Option[String], providerRefs: Map[String, String], depUrns: AggregatedDependencyUrns, - aliases: List[String], + aliases: List[ResourceAlias], options: ResolvedResourceOptions ) @@ -349,9 +349,6 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): private[internal] def resolveDeletedWithUrn(options: ResolvedResourceOptions): Result[Option[URN]] = options.deletedWith.map(_.urn.getValue).getOrElse(Result.pure(None)) - private[internal] def resolveAliases(@unused resource: Resource): Result[List[String]] = - Result.pure(List.empty) // TODO aliases - private[internal] def resolveProviderRegistrationId(state: ResourceState): Result[Option[String]] = state match case prs: ProviderResourceState => @@ -388,10 +385,17 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): providerRefs <- resolveProviderReferences(state) _ <- log.trace(s"Preparing inputs: resolved provider refs, aggregating dep URNs") depUrns <- aggregateDependencyUrns(directDeps, serResult.propertyToDependentResources, ctx.resources) - _ <- log.trace(s"Preparing inputs: aggregated dep URNs, resolving aliases") - aliases <- resolveAliases(resource) _ <- log.trace(s"Preparing inputs: resolved aliases, done") - yield PreparedInputs(serResult.serialized, parentUrnOpt, deletedWithUrnOpt, provIdOpt, providerRefs, depUrns, aliases, options) + yield PreparedInputs( + serResult.serialized, + parentUrnOpt, + deletedWithUrnOpt, + provIdOpt, + providerRefs, + depUrns, + options.aliases.toList, + options + ) private[internal] def executeReadResourceRequest[R <: Resource]( state: ResourceState, @@ -471,8 +475,23 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): case CustomResolvedResourceOptions(_, _, _, _, importId) => importId.getOrElse("") case _ => "" , - aliases = inputs.aliases.map { alias => - pulumirpc.alias.Alias(pulumirpc.alias.Alias.Alias.Urn(alias)) + aliases = inputs.aliases.map { + case UrnResourceAlias(urn) => pulumirpc.alias.Alias(pulumirpc.alias.Alias.Alias.Urn(urn)) + case SpecResourceAlias(name, resourceType, stack, project, parent) => + pulumirpc.alias.Alias( + pulumirpc.alias.Alias.Alias.Spec( + pulumirpc.alias.Alias.Spec( + name = name.getOrElse(""), + `type` = resourceType.getOrElse(""), + stack = stack.getOrElse(""), + project = project.getOrElse(""), + parent = parent match + case Some(ResourceAliasParentUrn(urn)) => pulumirpc.alias.Alias.Spec.Parent.ParentUrn(urn) + case Some(ResourceAliasNoParent(noParent)) => pulumirpc.alias.Alias.Spec.Parent.NoParent(noParent) + case None => pulumirpc.alias.Alias.Spec.Parent.Empty + ) + ) + ) }, remote = remote, customTimeouts = Some( diff --git a/core/src/main/scala/besom/internal/ResourceOptions.scala b/core/src/main/scala/besom/internal/ResourceOptions.scala index 320a4582..96c746d5 100644 --- a/core/src/main/scala/besom/internal/ResourceOptions.scala +++ b/core/src/main/scala/besom/internal/ResourceOptions.scala @@ -27,7 +27,7 @@ sealed trait ResolvedResourceOptions: def version: Option[String] def customTimeouts: Option[CustomTimeouts] // def resourceTransformations: Iterable[ResourceTransformation], // TODO - // def aliases: Iterable[Output[Alias]], // TODO + def aliases: Iterable[ResourceAlias] def urn: Option[URN] def replaceOnChanges: Iterable[String] def retainOnDelete: Boolean @@ -47,7 +47,7 @@ case class CommonResolvedResourceOptions( version: Option[String], customTimeouts: Option[CustomTimeouts], // resourceTransformations: Iterable[ResourceTransformation], // TODO - // aliases: Iterable[Output[Alias]], // TODO + aliases: Iterable[ResourceAlias], urn: Option[URN], replaceOnChanges: Iterable[String], retainOnDelete: Boolean, @@ -84,7 +84,7 @@ trait CommonResourceOptions: def version: Output[Option[String]] def customTimeouts: Output[Option[CustomTimeouts]] // def resourceTransformations: Iterable[ResourceTransformation], // TODO - // def aliases: Iterable[Output[Alias]], // TODO + def aliases: Output[Iterable[ResourceAlias]] // TODO this is only necessary for Resource deserialization, dependency resources and multi-language remote components def urn: Output[Option[URN]] def replaceOnChanges: Output[Iterable[String]] @@ -103,6 +103,7 @@ extension (cro: CommonResourceOptions) ignoreChanges <- cro.ignoreChanges.getData version <- cro.version.getData customTimeouts <- cro.customTimeouts.getData + aliases <- cro.aliases.getData urn <- cro.urn.getData replaceOnChanges <- cro.replaceOnChanges.getData retainOnDelete <- cro.retainOnDelete.getData @@ -116,6 +117,7 @@ extension (cro: CommonResourceOptions) ignoreChanges = ignoreChanges.getValueOrElse(Iterable.empty), version = version.getValueOrElse(None), customTimeouts = customTimeouts.getValueOrElse(None), + aliases = aliases.getValueOrElse(Iterable.empty), urn = urn.getValueOrElse(None), replaceOnChanges = replaceOnChanges.getValueOrElse(Iterable.empty), retainOnDelete = retainOnDelete.getValueOrElse(false), @@ -131,7 +133,7 @@ final case class CommonResourceOptionsImpl( version: Output[Option[String]], customTimeouts: Output[Option[CustomTimeouts]], // resourceTransformations: Iterable[ResourceTransformation], // TODO - // aliases: Iterable[Output[Alias]], // TODO + aliases: Output[Iterable[ResourceAlias]], urn: Output[Option[URN]], replaceOnChanges: Output[Iterable[String]], retainOnDelete: Output[Boolean], @@ -240,7 +242,7 @@ trait CustomResourceOptionsFactory: provider: Input.Optional[ProviderResource] = None, customTimeouts: Input.Optional[CustomTimeouts] = None, // resourceTransformations: Iterable[ResourceTransformation], // TODO - // aliases: Iterable[Output[Alias]], // TODO + aliases: Input.OneOrIterable[ResourceAlias] = Iterable.empty, urn: Input.Optional[URN] = None, replaceOnChanges: Input.OneOrIterable[String] = Iterable.empty, retainOnDelete: Input[Boolean] = false, @@ -257,6 +259,7 @@ trait CustomResourceOptionsFactory: version = version.asOptionOutput(), provider = provider.asOptionOutput(), customTimeouts = customTimeouts.asOptionOutput(), + aliases = aliases.asManyOutput(), urn = urn.asOptionOutput(), replaceOnChanges = replaceOnChanges.asManyOutput(), retainOnDelete = retainOnDelete.asOutput(), @@ -277,7 +280,7 @@ object CustomResourceOptions: provider: Input.Optional[ProviderResource] = None, customTimeouts: Input.Optional[CustomTimeouts] = None, // resourceTransformations: Iterable[ResourceTransformation], // TODO - // aliases: Iterable[Output[Alias]], // TODO + aliases: Input.OneOrIterable[ResourceAlias] = Iterable.empty, urn: Input.Optional[URN] = None, replaceOnChanges: Input.OneOrIterable[String] = Iterable.empty, retainOnDelete: Input[Boolean] = false, @@ -293,6 +296,7 @@ object CustomResourceOptions: ignoreChanges = ignoreChanges.asManyOutput(), version = version.asOptionOutput(), customTimeouts = customTimeouts.asOptionOutput(), + aliases = aliases.asManyOutput(), urn = urn.asOptionOutput(), replaceOnChanges = replaceOnChanges.asManyOutput(), retainOnDelete = retainOnDelete.asOutput(), @@ -306,6 +310,7 @@ object CustomResourceOptions: additionalSecretOutputs = additionalSecretOutputs.asManyOutput(), importId = importId.asOptionOutput() ) + end apply end CustomResourceOptions trait ComponentResourceOptionsFactory: @@ -318,7 +323,7 @@ trait ComponentResourceOptionsFactory: version: Input.Optional[NonEmptyString] = None, customTimeouts: Input.Optional[CustomTimeouts] = None, // resourceTransformations: Iterable[ResourceTransformation], // TODO - // aliases: Iterable[Output[Alias]], // TODO + aliases: Input.OneOrIterable[ResourceAlias] = Iterable.empty, urn: Input.Optional[URN] = None, replaceOnChanges: Input.OneOrIterable[String] = Iterable.empty, retainOnDelete: Input[Boolean] = false, @@ -332,6 +337,7 @@ trait ComponentResourceOptionsFactory: ignoreChanges = ignoreChanges.asManyOutput(), version = version.asOptionOutput(), customTimeouts = customTimeouts.asOptionOutput(), + aliases = aliases.asManyOutput(), urn = urn.asOptionOutput(), replaceOnChanges = replaceOnChanges.asManyOutput(), retainOnDelete = retainOnDelete.asOutput(), @@ -349,7 +355,7 @@ object ComponentResourceOptions: version: Input.Optional[NonEmptyString] = None, customTimeouts: Input.Optional[CustomTimeouts] = None, // resourceTransformations: Iterable[ResourceTransformation], // TODO - // aliases: Iterable[Output[Alias]], // TODO + aliases: Input.OneOrIterable[ResourceAlias] = Iterable.empty, urn: Input.Optional[URN] = None, replaceOnChanges: Input.OneOrIterable[String] = Iterable.empty, retainOnDelete: Input[Boolean] = false, @@ -363,6 +369,7 @@ object ComponentResourceOptions: ignoreChanges = ignoreChanges.asManyOutput(), version = version.asOptionOutput(), customTimeouts = customTimeouts.asOptionOutput(), + aliases = aliases.asManyOutput(), urn = urn.asOptionOutput(), replaceOnChanges = replaceOnChanges.asManyOutput(), retainOnDelete = retainOnDelete.asOutput(), @@ -380,7 +387,7 @@ object StackReferenceResourceOptions: version: Input.Optional[NonEmptyString] = None, customTimeouts: Input.Optional[CustomTimeouts] = None, // resourceTransformations: Iterable[ResourceTransformation], // TODO - // aliases: Iterable[Output[Alias]], // TODO + aliases: Input.OneOrIterable[ResourceAlias] = Iterable.empty, urn: Input.Optional[URN] = None, replaceOnChanges: Input.OneOrIterable[String] = Iterable.empty, retainOnDelete: Input[Boolean] = false, @@ -395,6 +402,7 @@ object StackReferenceResourceOptions: ignoreChanges = ignoreChanges.asManyOutput(), version = version.asOptionOutput(), customTimeouts = customTimeouts.asOptionOutput(), + aliases = aliases.asManyOutput(), urn = urn.asOptionOutput(), replaceOnChanges = replaceOnChanges.asManyOutput(), retainOnDelete = retainOnDelete.asOutput(), @@ -412,7 +420,7 @@ trait StackReferenceResourceOptionsFactory: version: Input.Optional[NonEmptyString] = None, customTimeouts: Input.Optional[CustomTimeouts] = None, // resourceTransformations: Iterable[ResourceTransformation], // TODO - // aliases: Iterable[Output[Alias]], // TODO + aliases: Input.OneOrIterable[ResourceAlias] = Iterable.empty, urn: Input.Optional[URN] = None, replaceOnChanges: Input.OneOrIterable[String] = Iterable.empty, retainOnDelete: Input[Boolean] = false, @@ -427,6 +435,7 @@ trait StackReferenceResourceOptionsFactory: ignoreChanges = ignoreChanges.asManyOutput(), version = version.asOptionOutput(), customTimeouts = customTimeouts.asOptionOutput(), + aliases = aliases.asManyOutput(), urn = urn.asOptionOutput(), replaceOnChanges = replaceOnChanges.asManyOutput(), retainOnDelete = retainOnDelete.asOutput(), diff --git a/integration-tests/CoreTests.test.scala b/integration-tests/CoreTests.test.scala index 4361c4da..524feec4 100644 --- a/integration-tests/CoreTests.test.scala +++ b/integration-tests/CoreTests.test.scala @@ -86,10 +86,14 @@ class CoreTests extends munit.FunSuite { ) }, teardown = pulumi.fixture.teardown - ).test("random provider and memoization should work") { ctx => + ).test("random provider, memoization and aliases should work") { ctx => val result = pulumi.up(ctx.stackName).call(cwd = ctx.programDir, env = ctx.env) val output = result.out.text() assert(output.contains("randomString:"), s"Output:\n$output\n") + + val previewResult = pulumi.preview(ctx.stackName).call(cwd = ctx.programDir, env = ctx.env + ("TEST_ALIAS" -> "true")) + val previewOutput = previewResult.out.text() + assert(previewOutput.contains("2 unchanged"), s"Output:\n$previewOutput\n") } FunFixture[pulumi.FixtureContext]( diff --git a/integration-tests/resources/random-example/Main.scala b/integration-tests/resources/random-example/Main.scala index ac6486dd..d9e89bb5 100644 --- a/integration-tests/resources/random-example/Main.scala +++ b/integration-tests/resources/random-example/Main.scala @@ -1,14 +1,20 @@ import besom.* import besom.api.random.* +import besom.internal.SpecResourceAlias @main def main(): Unit = Pulumi.run { + val oldName = "random-string" + val newName = "random-string-2" + + val testAlias = sys.env.getOrElse("TEST_ALIAS", "false").toBoolean def strOutput = RandomString( - name = "random-string", + name = if (testAlias) newName else oldName, args = RandomStringArgs( length = 10 - ) + ), + opts = opts(aliases = if (testAlias) List(SpecResourceAlias(name = Some(oldName))) else List.empty) ) val str = strOutput