Skip to content

Commit f2b012c

Browse files
authored
Enable listing serverless databases in the explorer (#2407)
1 parent 096faf5 commit f2b012c

File tree

14 files changed

+253
-89
lines changed

14 files changed

+253
-89
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "feature",
3+
"description" : "RDS serverless databases are now visible in the RDS node in the explorer"
4+
}

jetbrains-core/src/software/aws/toolkits/jetbrains/core/utils/CollectionUtils.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ package software.aws.toolkits.jetbrains.core.utils
77
* Replace with [kotlin.collections.buildList] when experimental is removed
88
*/
99
inline fun <E> buildList(builderAction: MutableList<E>.() -> Unit): List<E> = ArrayList<E>().apply(builderAction)
10+
11+
/*
12+
* Replace with [kotlin.collections.buildMap] when experimental is removed
13+
*/
14+
inline fun <T, U> buildMap(builderAction: MutableMap<T, U>.() -> Unit): Map<T, U> = mutableMapOf<T, U>().apply(builderAction)

jetbrains-ultimate/src-201/software/aws/toolkits/jetbrains/services/rds/resources/RdsResources.kt

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44
package software.aws.toolkits.jetbrains.services.rds.resources
55

66
import software.amazon.awssdk.services.rds.RdsClient
7-
import software.amazon.awssdk.services.rds.model.DBInstance
87
import software.amazon.awssdk.services.rds.model.Filter
98
import software.aws.toolkits.jetbrains.core.ClientBackedCachedResource
109
import software.aws.toolkits.jetbrains.core.Resource
1110
import software.aws.toolkits.jetbrains.services.rds.AuroraMySql
11+
import software.aws.toolkits.jetbrains.services.rds.Endpoint
12+
import software.aws.toolkits.jetbrains.services.rds.RdsDatabase
1213
import software.aws.toolkits.jetbrains.services.rds.RdsEngine
1314

1415
// FIX_WHEN_MIN_IS_202 remove this one and merge the 202+ one into RdsResources
1516
// Filters are also just a string
1617
private const val ENGINE_FILTER = "engine"
1718

18-
val LIST_SUPPORTED_INSTANCES: Resource.Cached<List<DBInstance>> = ClientBackedCachedResource(RdsClient::class, "rds.list_supported_instances") {
19+
val LIST_SUPPORTED_INSTANCES: Resource.Cached<List<RdsDatabase>> = ClientBackedCachedResource(RdsClient::class, "rds.list_supported_instances") {
1920
describeDBInstancesPaginator {
2021
it.filters(
2122
Filter.builder()
@@ -28,5 +29,48 @@ val LIST_SUPPORTED_INSTANCES: Resource.Cached<List<DBInstance>> = ClientBackedCa
2829
.flatMap { e -> e.engines }
2930
).build()
3031
)
31-
}.dbInstances().toList()
32+
}.dbInstances()
33+
.map {
34+
RdsDatabase(
35+
// if it has a cluster (aurora), use that as the identifier so it will de-dupe
36+
identifier = it.dbClusterIdentifier() ?: it.dbInstanceIdentifier(),
37+
arn = it.dbInstanceArn(),
38+
engine = it.engine(),
39+
iamDatabaseAuthenticationEnabled = it.iamDatabaseAuthenticationEnabled(),
40+
masterUsername = it.masterUsername(),
41+
endpoint = Endpoint(
42+
host = it.endpoint().address(),
43+
port = it.endpoint().port()
44+
)
45+
)
46+
}.toList()
47+
}
48+
49+
val LIST_SUPPORTED_CLUSTERS: Resource.Cached<List<RdsDatabase>> = ClientBackedCachedResource(RdsClient::class, "rds.list_supported_cluster") {
50+
describeDBClustersPaginator {
51+
it.filters(
52+
Filter.builder()
53+
.name(ENGINE_FILTER)
54+
.values(
55+
RdsEngine
56+
.values()
57+
// Filter out AuroraMySql because it is only supported on 202+
58+
.filterNot { e -> e == AuroraMySql }
59+
.flatMap { e -> e.engines }
60+
).build()
61+
)
62+
}.dbClusters()
63+
.map {
64+
RdsDatabase(
65+
identifier = it.dbClusterIdentifier(),
66+
arn = it.dbClusterArn(),
67+
engine = it.engine(),
68+
iamDatabaseAuthenticationEnabled = it.iamDatabaseAuthenticationEnabled(),
69+
masterUsername = it.masterUsername(),
70+
endpoint = Endpoint(
71+
host = it.endpoint(),
72+
port = it.port()
73+
)
74+
)
75+
}.toList()
3276
}

jetbrains-ultimate/src-202+/software/aws/toolkits/jetbrains/services/rds/resources/RdsResources.kt

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
package software.aws.toolkits.jetbrains.services.rds.resources
55

66
import software.amazon.awssdk.services.rds.RdsClient
7-
import software.amazon.awssdk.services.rds.model.DBInstance
87
import software.amazon.awssdk.services.rds.model.Filter
98
import software.aws.toolkits.jetbrains.core.ClientBackedCachedResource
109
import software.aws.toolkits.jetbrains.core.Resource
10+
import software.aws.toolkits.jetbrains.services.rds.Endpoint
11+
import software.aws.toolkits.jetbrains.services.rds.RdsDatabase
1112
import software.aws.toolkits.jetbrains.services.rds.RdsEngine
1213

1314
// FIX_WHEN_MIN_IS_202 make this one the default in RdsResources.kt
1415
// Filters are also just a string
1516
private const val ENGINE_FILTER = "engine"
1617

17-
val LIST_SUPPORTED_INSTANCES: Resource.Cached<List<DBInstance>> = ClientBackedCachedResource(RdsClient::class, "rds.list_supported_instances") {
18+
val LIST_SUPPORTED_INSTANCES: Resource.Cached<List<RdsDatabase>> = ClientBackedCachedResource(RdsClient::class, "rds.list_supported_instances") {
1819
describeDBInstancesPaginator {
1920
it.filters(
2021
Filter.builder()
@@ -25,5 +26,46 @@ val LIST_SUPPORTED_INSTANCES: Resource.Cached<List<DBInstance>> = ClientBackedCa
2526
.flatMap { e -> e.engines }
2627
).build()
2728
)
28-
}.dbInstances().toList()
29+
}.dbInstances()
30+
.map {
31+
RdsDatabase(
32+
// if it has a cluster (aurora), use that as the identifier so it will de-dupe
33+
identifier = it.dbClusterIdentifier() ?: it.dbInstanceIdentifier(),
34+
arn = it.dbInstanceArn(),
35+
engine = it.engine(),
36+
iamDatabaseAuthenticationEnabled = it.iamDatabaseAuthenticationEnabled(),
37+
masterUsername = it.masterUsername(),
38+
endpoint = Endpoint(
39+
host = it.endpoint().address(),
40+
port = it.endpoint().port()
41+
)
42+
)
43+
}.toList()
44+
}
45+
46+
val LIST_SUPPORTED_CLUSTERS: Resource.Cached<List<RdsDatabase>> = ClientBackedCachedResource(RdsClient::class, "rds.list_supported_cluster") {
47+
describeDBClustersPaginator {
48+
it.filters(
49+
Filter.builder()
50+
.name(ENGINE_FILTER)
51+
.values(
52+
RdsEngine
53+
.values()
54+
.flatMap { e -> e.engines }
55+
).build()
56+
)
57+
}.dbClusters()
58+
.map {
59+
RdsDatabase(
60+
identifier = it.dbClusterIdentifier(),
61+
arn = it.dbClusterArn(),
62+
engine = it.engine(),
63+
iamDatabaseAuthenticationEnabled = it.iamDatabaseAuthenticationEnabled(),
64+
masterUsername = it.masterUsername(),
65+
endpoint = Endpoint(
66+
host = it.endpoint(),
67+
port = it.port()
68+
)
69+
)
70+
}.toList()
2971
}

jetbrains-ultimate/src/software/aws/toolkits/jetbrains/datagrip/DatabaseSecret.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ object DatabaseSecret {
4444
// If it is a resource node, validate that it is the same resource
4545
when (node) {
4646
is RdsNode -> {
47-
if (node.dbInstance.engine() != dbSecret.engine) return ValidationInfo(
47+
if (node.database.engine != dbSecret.engine) return ValidationInfo(
4848
message(
4949
"datagrip.secretsmanager.validation.different_engine",
5050
secretName,
5151
dbSecret.engine.toString()
5252
)
5353
)
54-
if (node.dbInstance.endpoint().address() != dbSecret.host) return ValidationInfo(
54+
if (node.database.endpoint.host != dbSecret.host) return ValidationInfo(
5555
message("datagrip.secretsmanager.validation.different_address", secretName, dbSecret.host.toString())
5656
)
5757
}

jetbrains-ultimate/src/software/aws/toolkits/jetbrains/datagrip/actions/AddSecretsManagerConnection.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class AddSecretsManagerConnection : SingleExplorerNodeAction<AwsExplorerNode<*>>
6060

6161
private fun recordTelemetry(selected: AwsExplorerNode<*>, result: Result, engine: String? = null) {
6262
val dbEngine = engine ?: if (selected is RdsNode) {
63-
selected.dbInstance.engine()
63+
selected.database.engine
6464
} else {
6565
null
6666
}

jetbrains-ultimate/src/software/aws/toolkits/jetbrains/services/rds/RdsDatasourceConfiguration.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33

44
package software.aws.toolkits.jetbrains.services.rds
55

6-
import software.amazon.awssdk.services.rds.model.DBInstance
7-
86
data class RdsDatasourceConfiguration(
97
val regionId: String,
108
val credentialId: String,
11-
val dbInstance: DBInstance,
9+
val database: RdsDatabase,
1210
val username: String
1311
)

jetbrains-ultimate/src/software/aws/toolkits/jetbrains/services/rds/RdsExplorerNodes.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,33 @@ package software.aws.toolkits.jetbrains.services.rds
55

66
import com.intellij.openapi.project.Project
77
import software.amazon.awssdk.services.rds.RdsClient
8-
import software.amazon.awssdk.services.rds.model.DBInstance
98
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerNode
109
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerResourceNode
1110
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerServiceNode
1211
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerServiceRootNode
1312
import software.aws.toolkits.jetbrains.core.getResourceNow
13+
import software.aws.toolkits.jetbrains.core.utils.buildMap
14+
import software.aws.toolkits.jetbrains.services.rds.resources.LIST_SUPPORTED_CLUSTERS
1415
import software.aws.toolkits.jetbrains.services.rds.resources.LIST_SUPPORTED_INSTANCES
1516

1617
class RdsExplorerParentNode(project: Project, service: AwsExplorerServiceNode) : AwsExplorerServiceRootNode(project, service) {
17-
override fun getChildrenInternal(): List<AwsExplorerNode<*>> = nodeProject.getResourceNow(LIST_SUPPORTED_INSTANCES).map {
18+
override fun getChildrenInternal(): List<AwsExplorerNode<*>> = buildMap<String, RdsDatabase> {
19+
// De-dupe by db identifier
20+
nodeProject.getResourceNow(LIST_SUPPORTED_CLUSTERS).forEach { putIfAbsent(it.identifier, it) }
21+
nodeProject.getResourceNow(LIST_SUPPORTED_INSTANCES).forEach { putIfAbsent(it.identifier, it) }
22+
}.values.map {
1823
RdsNode(nodeProject, it)
1924
}
2025
}
2126

22-
class RdsNode(project: Project, val dbInstance: DBInstance, private val rdsEngine: RdsEngine = dbInstance.rdsEngine()) : AwsExplorerResourceNode<String>(
27+
class RdsNode(project: Project, val database: RdsDatabase, private val rdsEngine: RdsEngine = database.rdsEngine()) : AwsExplorerResourceNode<String>(
2328
project,
2429
RdsClient.SERVICE_NAME,
25-
dbInstance.dbInstanceArn(),
30+
database.arn,
2631
rdsEngine.icon
2732
) {
28-
override fun displayName(): String = dbInstance.dbInstanceIdentifier()
29-
override fun resourceArn(): String = dbInstance.dbInstanceArn()
33+
override fun displayName(): String = database.identifier
34+
override fun resourceArn(): String = database.arn
3035
override fun resourceType(): String = "instance"
3136
override fun statusText(): String? = rdsEngine.additionalInfo
3237
}

jetbrains-ultimate/src/software/aws/toolkits/jetbrains/services/rds/RdsResources.kt

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,29 @@
33

44
package software.aws.toolkits.jetbrains.services.rds
55

6-
import software.amazon.awssdk.services.rds.model.DBInstance
7-
86
// These are the member engine in DBInstance, but it is a string
97
const val mysqlEngineType = "mysql"
108
const val postgresEngineType = "postgres"
119

12-
fun DBInstance.rdsEngine(): RdsEngine = RdsEngine.fromEngine(engine())
13-
1410
const val jdbcMysql = "mysql"
1511
const val jdbcMysqlAurora = "mysql:aurora"
1612
const val jdbcPostgres = "postgresql"
1713

1814
object RdsResources {
19-
private val RDS_REGION_REGEX =
20-
""".*\.(.+).rds\.""".toRegex()
15+
private val RDS_REGION_REGEX = """.*\.(.+).rds\.""".toRegex()
2116

2217
fun extractRegionFromUrl(url: String?): String? = url?.let { RDS_REGION_REGEX.find(url)?.groupValues?.get(1) }
2318
}
19+
20+
data class RdsDatabase(
21+
val identifier: String,
22+
val arn: String,
23+
val engine: String,
24+
val iamDatabaseAuthenticationEnabled: Boolean,
25+
val endpoint: Endpoint,
26+
val masterUsername: String
27+
) {
28+
fun rdsEngine() = RdsEngine.fromEngine(engine)
29+
}
30+
31+
data class Endpoint(val host: String, val port: Int)

jetbrains-ultimate/src/software/aws/toolkits/jetbrains/services/rds/actions/CreateIamDataSourceAction.kt

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import software.aws.toolkits.jetbrains.services.rds.RdsNode
2525
import software.aws.toolkits.jetbrains.services.rds.auth.IamAuth
2626
import software.aws.toolkits.jetbrains.services.rds.auth.RDS_SIGNING_HOST_PROPERTY
2727
import software.aws.toolkits.jetbrains.services.rds.auth.RDS_SIGNING_PORT_PROPERTY
28-
import software.aws.toolkits.jetbrains.services.rds.rdsEngine
2928
import software.aws.toolkits.jetbrains.services.sts.StsResources
3029
import software.aws.toolkits.jetbrains.utils.actions.OpenBrowserAction
3130
import software.aws.toolkits.jetbrains.utils.notifyError
@@ -62,18 +61,18 @@ class CreateIamDataSourceAction : SingleExplorerNodeAction<RdsNode>(message("rds
6261
selected.nodeProject,
6362
result,
6463
DatabaseCredentials.IAM,
65-
selected.dbInstance.engine()
64+
selected.database.engine
6665
)
6766
}.queue()
6867
}
6968

7069
internal fun checkPrerequisites(node: RdsNode): Boolean {
7170
// Assert IAM auth enabled
72-
if (!node.dbInstance.iamDatabaseAuthenticationEnabled()) {
71+
if (!node.database.iamDatabaseAuthenticationEnabled) {
7372
notifyError(
7473
project = node.nodeProject,
7574
title = message("aws.notification.title"),
76-
content = message("rds.validation.no_iam_auth", node.dbInstance.dbInstanceIdentifier()),
75+
content = message("rds.validation.no_iam_auth", node.database.identifier),
7776
action = OpenBrowserAction(message("rds.validation.setup_guide"), null, HelpIds.RDS_SETUP_IAM_AUTH.url)
7877
)
7978
return false
@@ -87,13 +86,13 @@ class CreateIamDataSourceAction : SingleExplorerNodeAction<RdsNode>(message("rds
8786
node.nodeProject.getResourceNow(StsResources.USER).substringAfter(':')
8887
} catch (e: Exception) {
8988
LOG.warn(e) { "Getting username from STS failed, falling back to master username" }
90-
node.dbInstance.masterUsername()
89+
node.database.masterUsername
9190
}
9291
registry.createRdsDatasource(
9392
RdsDatasourceConfiguration(
9493
regionId = node.nodeProject.activeRegion().id,
9594
credentialId = node.nodeProject.activeCredentialProvider().id,
96-
dbInstance = node.dbInstance,
95+
database = node.database,
9796
username = username
9897
)
9998
)
@@ -105,9 +104,9 @@ class CreateIamDataSourceAction : SingleExplorerNodeAction<RdsNode>(message("rds
105104
}
106105

107106
fun DataSourceRegistry.createRdsDatasource(config: RdsDatasourceConfiguration) {
108-
val engine = config.dbInstance.rdsEngine()
109-
val port = config.dbInstance.endpoint().port()
110-
val host = config.dbInstance.endpoint().address()
107+
val engine = config.database.rdsEngine()
108+
val port = config.database.endpoint.port
109+
val host = config.database.endpoint.host
111110
val endpoint = "$host:$port"
112111

113112
builder

0 commit comments

Comments
 (0)