Skip to content

Commit

Permalink
Add provider name to provider resource (#241)
Browse files Browse the repository at this point in the history
## Task

Resolves: #228 

## Description

Disclaimer: for providers with complex names (such as `aws-native`), the
names have the following format: `AzurenativeProviderResource`. I'll try
to figure out if there's an easy way to make the name
`AzureNativeProviderResource` instead, but the easiest way was to ignore
the capitalization of complex names.

### TODO

- [x] ~~Add tests~~ Fix existing tests
- [x] If the current solution is determined to be not good enough,
figure out a way to capitalize complex names
  • Loading branch information
jplewa authored Jul 26, 2023
1 parent bbf26da commit f0f99b4
Show file tree
Hide file tree
Showing 14 changed files with 730 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package project

import com.pulumi.core.Output
import com.pulumi.gcp.compute.kotlin.instance
import com.pulumi.gcp.kotlin.Provider
import com.pulumi.gcp.kotlin.provider
import com.pulumi.gcp.kotlin.GcpProvider
import com.pulumi.gcp.kotlin.gcpProvider
import com.pulumi.kotlin.Pulumi

private val commonTags = listOf("gcp-provider-sample-project", "foo", "bar")
Expand Down Expand Up @@ -51,15 +51,15 @@ fun main() {
}

private suspend fun createProvider(resourceName: String, projectName: String, region: String, zone: String) =
provider(resourceName) {
gcpProvider(resourceName) {
args {
project(projectName)
region(region)
zone(zone)
}
}

private suspend fun createInstanceWithProvider(resourceName: String, provider: Provider, tags: List<String>) =
private suspend fun createInstanceWithProvider(resourceName: String, provider: GcpProvider, tags: List<String>) =
instance(resourceName) {
args {
machineType("e2-micro")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ object IntermediateRepresentationGenerator {
createResource(DEFAULT_PROVIDER_TOKEN, schema.provider, context, typeMap, isProvider = true)
} else {
createResources(typeMap, context)
}.filterNotNull()
}
.filterNotNull()

return IntermediateRepresentation(
types = types,
Expand All @@ -76,12 +77,13 @@ object IntermediateRepresentationGenerator {
fun <V> createTypes(
map: Map<String, V>,
usageKind: UsageKind? = null,
isProvider: Boolean = false,
propertyExtractor: (V) -> RootTypeProperty?,
) = map
.mapValues { propertyExtractor(it.value) }
.filterNotNullValues()
.flatMap { (name, value) ->
createRootTypes(context, name, value, listOfNotNull(usageKind))
createRootTypes(context, name, value, listOfNotNull(usageKind), isProvider)
}

val syntheticTypes = listOf(
Expand All @@ -98,14 +100,16 @@ object IntermediateRepresentationGenerator {
createTypes(
mapOf(DEFAULT_PROVIDER_TOKEN to schema.provider),
UsageKind(Root, Resource, Input),
isProvider = true,
) { resource ->
ObjectProperty(
properties = resource.inputProperties,
description = resource.description,
deprecationMessage = resource.deprecationMessage,
)
}
}.orEmpty(),
}
.orEmpty(),
)

val regularTypes = listOf(
Expand All @@ -117,7 +121,7 @@ object IntermediateRepresentationGenerator {

private fun createResources(types: Map<TypeKey, RootType>, context: Context): List<ResourceType> {
return context.schema.resources.mapNotNull { (typeToken, resource) ->
createResource(typeToken, resource, context, types)
createResource(typeToken, resource, context, types, isProvider = false)
}
}

Expand All @@ -126,7 +130,7 @@ object IntermediateRepresentationGenerator {
resource: SchemaModel.Resource,
context: Context,
types: Map<TypeKey, RootType>,
isProvider: Boolean = false,
isProvider: Boolean,
): ResourceType? {
val resultFields = resource.properties
.letIf(isProvider, ::filterStringProperties)
Expand All @@ -143,7 +147,7 @@ object IntermediateRepresentationGenerator {
}

return try {
val pulumiName = PulumiName.from(typeToken, context.namingConfiguration)
val pulumiName = PulumiName.from(typeToken, context.namingConfiguration, isProvider = isProvider)
val inputUsageKind = UsageKind(Root, Resource, Input)
val argumentType =
findTypeAsReference<ReferencedComplexType>(
Expand All @@ -160,7 +164,7 @@ object IntermediateRepresentationGenerator {
private fun createFunctions(types: Map<TypeKey, RootType>, context: Context): List<FunctionType> {
return context.schema.functions.mapNotNull { (typeName, function) ->
try {
val pulumiName = PulumiName.from(typeName, context.namingConfiguration)
val pulumiName = PulumiName.from(typeName, context.namingConfiguration, isProvider = false)

val inputUsageKind = UsageKind(Root, Function, Input)
val argumentType = findTypeOrEmptyComplexType(
Expand Down Expand Up @@ -201,6 +205,7 @@ object IntermediateRepresentationGenerator {
typeName: String,
rootType: RootTypeProperty,
forcedUsageKinds: List<UsageKind> = emptyList(),
isProvider: Boolean,
): List<RootType> {
val usages = forcedUsageKinds.ifEmpty {
val allUsagesForTypeName = context.referenceFinder.getUsages(typeName)
Expand All @@ -214,7 +219,7 @@ object IntermediateRepresentationGenerator {
}

try {
val pulumiName = PulumiName.from(typeName, context.namingConfiguration)
val pulumiName = PulumiName.from(typeName, context.namingConfiguration, isProvider)
return usages.map { usage ->
when (rootType) {
is ObjectProperty -> ComplexType(
Expand Down Expand Up @@ -302,7 +307,7 @@ object IntermediateRepresentationGenerator {
} else if (context.referencedStringTypesResolver.shouldGenerateStringType(referencedTypeName)) {
StringType
} else {
val pulumiName = PulumiName.from(referencedTypeName, context.namingConfiguration)
val pulumiName = PulumiName.from(referencedTypeName, context.namingConfiguration, isProvider = false)
when (context.referenceFinder.resolve(referencedTypeName)) {
is ObjectProperty -> ReferencedComplexType(
TypeMetadata(pulumiName, usageKind, getKDoc(property)),
Expand Down Expand Up @@ -396,8 +401,11 @@ object IntermediateRepresentationGenerator {
name = with(name) {
PulumiName(
providerName.lowercase(),
namespace.map { it.lowercase() },
providerNameOverride?.lowercase(),
baseNamespace.map { it.lowercase() },
moduleName?.lowercase(),
name.lowercase(),
isProvider = isProvider,
)
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,21 @@ import org.virtuslab.pulumikotlin.codegen.utils.decapitalize

data class PulumiName(
val providerName: String,
val namespace: List<String>,
val providerNameOverride: String?,
val baseNamespace: List<String>,
val moduleName: String?,
val name: String,
val isProvider: Boolean,
) {
val namespace: List<String>
get() {
val effectiveProviderName = (providerNameOverride ?: providerName)
val namespaceSections = baseNamespace + effectiveProviderName + moduleName
return namespaceSections
.filterNotNull()
.filter { it.isNotBlank() }
.map { it.replace("-", "") }
}

private data class Modifiers(
val nameSuffix: String,
Expand Down Expand Up @@ -175,19 +187,19 @@ data class PulumiName(
}

fun toFunctionGroupObjectName(namingFlags: NamingFlags): String {
return when (namingFlags.language) {
Kotlin, Java -> {
if (namespace.isEmpty()) {
providerName.capitalize() + "Functions"
} else {
namespace.last().replace(".", "_").capitalize() + "Functions"
}
}
return if (moduleName != null) {
moduleName.replace(".", "_").capitalize() + "Functions"
} else {
getProviderPrefix(namingFlags.language).replace(".", "_").capitalize() + "Functions"
}
}

fun toResourceName(namingFlags: NamingFlags): String {
return name.capitalize()
return if (namingFlags.language == Kotlin && isProvider) {
"${getProviderPrefix(namingFlags.language)}${name.capitalize()}"
} else {
name.capitalize()
}
}

fun toClassName(namingFlags: NamingFlags): String {
Expand All @@ -212,6 +224,14 @@ data class PulumiName(
}
}

private fun getProviderPrefix(languageType: LanguageType): String {
val splitProviderName = providerName.split("-")
return when (languageType) {
Kotlin -> splitProviderName.joinToString("") { it.capitalize() }
Java -> splitProviderName.joinToString("").capitalize()
}
}

private fun packageToString(packageList: List<String>): String {
return packageList.joinToString(".")
}
Expand Down Expand Up @@ -248,44 +268,57 @@ data class PulumiName(
companion object {
private const val EXPECTED_NUMBER_OF_SEGMENTS_IN_TOKEN = 3

fun from(token: String, namingConfiguration: PulumiNamingConfiguration): PulumiName {
fun from(
token: String,
namingConfiguration: PulumiNamingConfiguration,
isProvider: Boolean = false,
): PulumiName {
// token = pkg ":" module ":" member

val segments = token.split(":")

require(segments.size == EXPECTED_NUMBER_OF_SEGMENTS_IN_TOKEN) { "Malformed token $token" }

fun substituteWithOverride(name: String) = namingConfiguration.packageOverrides[name] ?: name
fun substituteWithOverride(name: String) = namingConfiguration.packageOverrides[name]

val module = when (segments[1]) {
"providers" -> ""
"providers" -> null
else -> {
val moduleMatches = namingConfiguration.moduleFormatRegex.matchEntire(segments[1])
?.groupValues
.orEmpty()

if (moduleMatches.size < 2 || moduleMatches[1].startsWith("index")) {
""
null
} else {
moduleMatches[1]
}
}
}

val providerName = substituteWithOverride(namingConfiguration.providerName)
val moduleName = substituteWithOverride(module)

val namespace = (namingConfiguration.baseNamespace + providerName + moduleName)
.filter { it.isNotBlank() }
.map { it.replace("-", "") }
val providerNameOverride = substituteWithOverride(namingConfiguration.providerName)
val moduleName = module?.let { substituteWithOverride(it) } ?: module

val name = segments[2]

if (name.contains("/")) {
val namespace = (
namingConfiguration.baseNamespace +
(providerNameOverride ?: namingConfiguration.providerName) +
moduleName
)
.filterNotNull()
throw InvalidPulumiName(name, namespace)
}

return PulumiName(providerName, namespace, name)
return PulumiName(
namingConfiguration.providerName,
providerNameOverride,
namingConfiguration.baseNamespace,
moduleName,
name,
isProvider,
)
}
}
}
Expand Down
17 changes: 9 additions & 8 deletions src/test/kotlin/org/virtuslab/pulumikotlin/TestUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ internal fun <T> extractOutputValue(output: Output<T>?): T? {
return value
}

internal fun <T> concat(iterableOfIterables: Iterable<Iterable<T>>?): List<T> =
iterableOfIterables?.flatten().orEmpty()

internal fun <T> concat(vararg iterables: Iterable<T>?): List<T> =
concat(iterables.filterNotNull().asIterable())

internal fun namingConfigurationWithSlashInModuleFormat(providerName: String) =
PulumiNamingConfiguration.create(providerName = providerName, moduleFormat = "(.*)(?:/[^/]*)")
internal fun namingConfigurationWithSlashInModuleFormat(
providerName: String,
packageOverrides: Map<String, String> = emptyMap(),
) =
PulumiNamingConfiguration.create(
providerName = providerName,
moduleFormat = "(.*)(?:/[^/]*)",
packageOverrides = packageOverrides,
)

private fun messagePrefix(message: String?) = if (message == null) "" else "$message. "

Expand Down
Loading

0 comments on commit f0f99b4

Please sign in to comment.