Skip to content

Commit 8de5eb8

Browse files
authored
Special case S3 Bucket in Resources node (#2896)
* Adding ability to create a view which is region aware - using it for S3 buckets. * Special case S3 for Cloud API * Adding test and moving string into constant * Adding changelog
1 parent 23ef07a commit 8de5eb8

File tree

15 files changed

+128
-78
lines changed

15 files changed

+128
-78
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "bugfix",
3+
"description" : "Fix issue in Resources where some S3 Buckets fail to open"
4+
}

jetbrains-core/it/software/aws/toolkits/jetbrains/services/lambda/upload/CreateFunctionIntegrationTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class CreateFunctionIntegrationTest {
129129
val s3Bucket = temporaryBucket.createBucket()
130130
resourceCache.addEntry(
131131
projectRule.project,
132-
S3Resources.LIST_REGIONALIZED_BUCKETS,
132+
"s3.list_buckets",
133133
listOf(S3Resources.RegionalizedBucket(Bucket.builder().name(s3Bucket).build(), projectRule.project.activeRegion()))
134134
)
135135

jetbrains-core/src/software/aws/toolkits/jetbrains/core/AwsResourceCache.kt

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -210,17 +210,22 @@ sealed class Resource<T> {
210210
* in order to return the desired type [Output]. The [transform] result is not cached, [transform]s are re-applied on each fetch - thus should
211211
* should be relatively cheap.
212212
*/
213-
class View<Input, Output>(val underlying: Resource<Input>, private val transform: Input.() -> Output) : Resource<Output>() {
213+
class View<Input, Output>(val underlying: Resource<Input>, private val transform: (Input, AwsRegion) -> Output) : Resource<Output>() {
214214
@Suppress("UNCHECKED_CAST")
215-
fun doMap(input: Any) = transform(input as Input)
215+
fun doMap(input: Any, region: AwsRegion) = transform(input as Input, region)
216+
}
217+
218+
companion object {
219+
fun <Input, Output> view(underlying: Resource<Input>, transform: Input.() -> Output): Resource<Output> =
220+
View(underlying) { input, _ -> transform(input) }
216221
}
217222
}
218223

219-
fun <Input, Output> Resource<out Iterable<Input>>.map(transform: (Input) -> Output): Resource<List<Output>> = Resource.View(this) { map(transform) }
224+
fun <Input, Output> Resource<out Iterable<Input>>.map(transform: (Input) -> Output): Resource<List<Output>> = Resource.view(this) { map(transform) }
220225

221-
fun <T> Resource<out Iterable<T>>.filter(predicate: (T) -> Boolean): Resource<List<T>> = Resource.View(this) { filter(predicate) }
226+
fun <T> Resource<out Iterable<T>>.filter(predicate: (T) -> Boolean): Resource<List<T>> = Resource.view(this) { filter(predicate) }
222227

223-
fun <T> Resource<out Iterable<T>>.find(predicate: (T) -> Boolean): Resource<T?> = Resource.View(this) { find(predicate) }
228+
fun <T> Resource<out Iterable<T>>.find(predicate: (T) -> Boolean): Resource<T?> = Resource.view(this) { find(predicate) }
224229

225230
class ClientBackedCachedResource<ReturnType, ClientType : SdkClient>(
226231
private val sdkClientClass: KClass<ClientType>,
@@ -294,7 +299,13 @@ class DefaultAwsResourceCache(
294299
useStale: Boolean,
295300
forceFetch: Boolean
296301
): CompletionStage<T> = when (resource) {
297-
is Resource.View<*, T> -> getResource(resource.underlying, region, credentialProvider, useStale, forceFetch).thenApply { resource.doMap(it as Any) }
302+
is Resource.View<*, T> -> getResource(
303+
resource.underlying,
304+
region,
305+
credentialProvider,
306+
useStale,
307+
forceFetch
308+
).thenApply { resource.doMap(it as Any, region) }
298309
is Resource.Cached<T> -> Context(resource, region, credentialProvider, useStale, forceFetch).also { getCachedResource(it) }.future
299310
}
300311

@@ -360,7 +371,12 @@ class DefaultAwsResourceCache(
360371
else -> null
361372
}
362373
}
363-
is Resource.View<*, T> -> getResourceIfPresent(resource.underlying, region, credentialProvider, useStale)?.let { resource.doMap(it) }
374+
is Resource.View<*, T> -> getResourceIfPresent(resource.underlying, region, credentialProvider, useStale)?.let {
375+
resource.doMap(
376+
it,
377+
region
378+
)
379+
}
364380
}
365381

366382
override fun clear(resource: Resource<*>, connectionSettings: ConnectionSettings) {

jetbrains-core/src/software/aws/toolkits/jetbrains/services/dynamic/CloudControlApiResources.kt

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,26 @@ import software.amazon.awssdk.services.cloudformation.model.RegistryType
1212
import software.amazon.awssdk.services.cloudformation.model.Visibility
1313
import software.aws.toolkits.jetbrains.core.ClientBackedCachedResource
1414
import software.aws.toolkits.jetbrains.core.Resource
15+
import software.aws.toolkits.jetbrains.core.map
16+
import software.aws.toolkits.jetbrains.services.s3.resources.S3Resources
1517

1618
object CloudControlApiResources {
17-
18-
fun listResources(typeName: String): Resource.Cached<List<DynamicResource>> =
19-
ClientBackedCachedResource(CloudControlClient::class, "cloudcontrolapi.dynamic.resources.$typeName") {
20-
this.listResourcesPaginator {
21-
it.typeName(typeName)
22-
}.flatMap {
23-
it.resourceDescriptions().map { resource ->
24-
DynamicResource(resourceTypeFromResourceTypeName(it.typeName()), resource.identifier())
25-
}
19+
fun listResources(typeName: String): Resource<List<DynamicResource>> {
20+
return when (typeName) {
21+
S3_BUCKET -> S3Resources.LIST_BUCKETS.map { it.name() }
22+
else -> ClientBackedCachedResource(CloudControlClient::class, "cloudcontrolapi.dynamic.resources.$typeName") {
23+
this.listResourcesPaginator { req -> req.typeName(typeName) }
24+
.flatMap { page -> page.resourceDescriptions().map { it.identifier() } }
2625
}
27-
}
26+
}.map { DynamicResource(resourceTypeFromResourceTypeName(typeName), it) }
27+
}
2828

2929
fun resourceTypeFromResourceTypeName(typeName: String): ResourceType {
3030
val (_, svc, type) = typeName.split("::")
3131
return ResourceType(typeName, svc, type)
3232
}
3333

34-
fun listResources(resourceType: ResourceType): Resource.Cached<List<DynamicResource>> = listResources(resourceType.fullName)
34+
fun listResources(resourceType: ResourceType): Resource<List<DynamicResource>> = listResources(resourceType.fullName)
3535

3636
fun getResourceDisplayName(identifier: String): String =
3737
if (identifier.startsWith("arn:")) {
@@ -49,14 +49,13 @@ object CloudControlApiResources {
4949
LightVirtualFile("${resourceType}Schema.json", schema)
5050
}
5151

52-
fun listTypes(): Resource.Cached<List<String>> = ClientBackedCachedResource(
53-
CloudFormationClient::class, "cloudformation.listTypes"
54-
) {
52+
fun listTypes(): Resource.Cached<List<String>> = ClientBackedCachedResource(CloudFormationClient::class, "cloudformation.listTypes") {
5553
this.listTypesPaginator {
5654
it.visibility(Visibility.PUBLIC)
5755
it.type(RegistryType.RESOURCE)
5856
}.flatMap { it.typeSummaries().map { it.typeName() } }
5957
}
58+
private const val S3_BUCKET = "AWS::S3::Bucket"
6059
}
6160

6261
data class ResourceDetails(val operations: List<PermittedOperation>, val arnRegex: String?, val documentation: String?)

jetbrains-core/src/software/aws/toolkits/jetbrains/services/ecs/resources/EcsResources.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,5 @@ object EcsResources {
4343
fun listTaskIds(clusterArn: String, serviceArn: String) = listTasks(clusterArn, serviceArn).map { it.substringAfterLast("/") }
4444

4545
fun listContainers(taskDefinitionArn: String): Resource<List<ContainerDefinition>> =
46-
Resource.View(describeTaskDefinition(taskDefinitionArn)) { containerDefinitions() }
46+
Resource.view(describeTaskDefinition(taskDefinitionArn)) { containerDefinitions() }
4747
}

jetbrains-core/src/software/aws/toolkits/jetbrains/services/iam/Iam.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ object IamResources {
3131
}
3232

3333
@JvmField
34-
val LIST_ALL: Resource<List<IamRole>> = Resource.View(LIST_RAW_ROLES) {
34+
val LIST_ALL: Resource<List<IamRole>> = Resource.view(LIST_RAW_ROLES) {
3535
map { IamRole(it.arn()) }.toList()
3636
}
3737

3838
@JvmField
39-
val LIST_LAMBDA_ROLES: Resource<List<IamRole>> = Resource.View(LIST_RAW_ROLES) {
39+
val LIST_LAMBDA_ROLES: Resource<List<IamRole>> = Resource.view(LIST_RAW_ROLES) {
4040
filter { it.assumeRolePolicyDocument().contains(LAMBDA_PRINCIPAL) }
4141
.map { IamRole(it.arn()) }
4242
.toList()

jetbrains-core/src/software/aws/toolkits/jetbrains/services/lambda/deploy/DeployServerlessApplicationDialog.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import software.amazon.awssdk.services.lambda.model.PackageType
2828
import software.amazon.awssdk.services.s3.S3Client
2929
import software.aws.toolkits.jetbrains.core.awsClient
3030
import software.aws.toolkits.jetbrains.core.help.HelpIds
31+
import software.aws.toolkits.jetbrains.core.map
3132
import software.aws.toolkits.jetbrains.services.cloudformation.CloudFormationTemplate
3233
import software.aws.toolkits.jetbrains.services.cloudformation.Parameter
3334
import software.aws.toolkits.jetbrains.services.cloudformation.SamFunction
@@ -90,7 +91,7 @@ class DeployServerlessApplicationDialog(
9091
.build()
9192

9293
private val s3BucketSelector = ResourceSelector.builder()
93-
.resource(S3Resources.listBucketNamesByActiveRegion(project))
94+
.resource(S3Resources.LIST_BUCKETS.map { it.name() })
9495
.awsConnection(project)
9596
.apply {
9697
if (!loadResourcesOnCreate) {

jetbrains-core/src/software/aws/toolkits/jetbrains/services/lambda/upload/CodeStoragePanel.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import com.intellij.ui.IdeBorderFactory
88
import com.intellij.ui.SimpleListCellRenderer
99
import software.amazon.awssdk.services.lambda.model.PackageType
1010
import software.aws.toolkits.jetbrains.core.awsClient
11+
import software.aws.toolkits.jetbrains.core.map
1112
import software.aws.toolkits.jetbrains.services.ecr.CreateEcrRepoDialog
1213
import software.aws.toolkits.jetbrains.services.ecr.resources.EcrResources
1314
import software.aws.toolkits.jetbrains.services.ecr.resources.Repository
1415
import software.aws.toolkits.jetbrains.services.s3.CreateS3BucketDialog
15-
import software.aws.toolkits.jetbrains.services.s3.resources.S3Resources.listBucketNamesByActiveRegion
16+
import software.aws.toolkits.jetbrains.services.s3.resources.S3Resources.LIST_BUCKETS
1617
import software.aws.toolkits.jetbrains.ui.ResourceSelector
1718
import software.aws.toolkits.jetbrains.utils.ui.validationInfo
1819
import software.aws.toolkits.resources.message
@@ -74,7 +75,7 @@ class CodeStoragePanel(private val project: Project) : JPanel(BorderLayout()) {
7475
}
7576

7677
private fun createUIComponents() {
77-
sourceBucket = ResourceSelector.builder().resource(listBucketNamesByActiveRegion(project)).awsConnection(project).build()
78+
sourceBucket = ResourceSelector.builder().resource(LIST_BUCKETS.map { it.name() }).awsConnection(project).build()
7879
ecrRepo = ResourceSelector.builder()
7980
.resource(EcrResources.LIST_REPOS)
8081
.customRenderer(SimpleListCellRenderer.create("") { it.repositoryName })

jetbrains-core/src/software/aws/toolkits/jetbrains/services/s3/S3ExplorerNode.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ import software.aws.toolkits.jetbrains.core.credentials.activeRegion
1111
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerNode
1212
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerResourceNode
1313
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerServiceNode
14-
import software.aws.toolkits.jetbrains.core.explorer.nodes.AwsExplorerServiceRootNode
15-
import software.aws.toolkits.jetbrains.core.getResourceNow
14+
import software.aws.toolkits.jetbrains.core.explorer.nodes.CacheBackedAwsExplorerServiceRootNode
1615
import software.aws.toolkits.jetbrains.services.s3.resources.S3Resources
1716
import software.aws.toolkits.resources.message
1817

19-
class S3ServiceNode(project: Project, service: AwsExplorerServiceNode) : AwsExplorerServiceRootNode(project, service) {
18+
class S3ServiceNode(project: Project, service: AwsExplorerServiceNode) :
19+
CacheBackedAwsExplorerServiceRootNode<Bucket>(project, service, S3Resources.LIST_BUCKETS) {
2020
override fun displayName(): String = message("explorer.node.s3")
21-
override fun getChildrenInternal(): List<AwsExplorerNode<*>> =
22-
nodeProject.getResourceNow(S3Resources.listBucketsByActiveRegion(nodeProject)).map { S3BucketNode(nodeProject, it) }
21+
override fun toNode(child: Bucket): AwsExplorerNode<*> = S3BucketNode(nodeProject, child)
2322
}
2423

2524
class S3BucketNode(project: Project, val bucket: Bucket) :

jetbrains-core/src/software/aws/toolkits/jetbrains/services/s3/resources/S3Resources.kt

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
package software.aws.toolkits.jetbrains.services.s3.resources
55

6-
import com.intellij.openapi.project.Project
76
import kotlinx.coroutines.async
87
import kotlinx.coroutines.awaitAll
98
import kotlinx.coroutines.runBlocking
109
import kotlinx.coroutines.withContext
10+
import org.jetbrains.annotations.TestOnly
1111
import org.slf4j.event.Level
1212
import software.amazon.awssdk.services.s3.S3Client
1313
import software.amazon.awssdk.services.s3.model.Bucket
@@ -18,9 +18,6 @@ import software.aws.toolkits.core.utils.tryOrNull
1818
import software.aws.toolkits.jetbrains.core.ClientBackedCachedResource
1919
import software.aws.toolkits.jetbrains.core.Resource
2020
import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext
21-
import software.aws.toolkits.jetbrains.core.credentials.AwsConnectionManager
22-
import software.aws.toolkits.jetbrains.core.filter
23-
import software.aws.toolkits.jetbrains.core.map
2421
import software.aws.toolkits.jetbrains.core.region.AwsRegionProvider
2522
import java.time.Instant
2623
import java.time.LocalDateTime
@@ -31,7 +28,8 @@ object S3Resources {
3128
private val LOG = getLogger<S3Resources>()
3229
private val regions by lazy { AwsRegionProvider.getInstance().allRegions() }
3330

34-
val LIST_REGIONALIZED_BUCKETS = ClientBackedCachedResource(S3Client::class, "s3.list_buckets") {
31+
@TestOnly
32+
internal val LIST_REGIONALIZED_BUCKETS = ClientBackedCachedResource(S3Client::class, "s3.list_buckets") {
3533
val buckets = listBuckets().buckets()
3634
// TODO when the resource cache is coroutine based, remove the runBlocking and withContext
3735
runBlocking {
@@ -50,16 +48,10 @@ object S3Resources {
5048
}
5149
}
5250

53-
val LIST_BUCKETS: Resource<List<Bucket>> = LIST_REGIONALIZED_BUCKETS.map { it.bucket }
54-
55-
fun listBucketsByActiveRegion(project: Project): Resource<List<Bucket>> {
56-
val activeRegion = AwsConnectionManager.getInstance(project).activeRegion
57-
return LIST_REGIONALIZED_BUCKETS.filter { it.region == activeRegion }.map { it.bucket }
51+
val LIST_BUCKETS: Resource<List<Bucket>> = Resource.View(LIST_REGIONALIZED_BUCKETS) { bucketList, region ->
52+
bucketList.filter { it.region == region }.map { it.bucket }
5853
}
5954

60-
@JvmStatic
61-
fun listBucketNamesByActiveRegion(project: Project): Resource<List<String>> = listBucketsByActiveRegion(project).map { it.name() }
62-
6355
@JvmStatic
6456
fun formatDate(date: Instant): String {
6557
val datetime = LocalDateTime.ofInstant(date, ZoneId.systemDefault())

0 commit comments

Comments
 (0)