Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
out/
target/
/.idea/
build/
*.iml
.DS_Store
Build/
Build/
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class AwsResourceBundle internal constructor() : S3ClientProvider, LambdaClientP
}

private class AwsResources(val region: String) {
val s3Client: AmazonS3 = AmazonS3Client() //AmazonS3ClientBuilder.standard().withRegion(region).build()
val s3Client: AmazonS3 = AmazonS3Client() //AmazonS3ClientBuilder.standard().withRegion(region).parse()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the commented out part for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to address this. This is because a rename to the method build() -> parse(), but somehow IntelliJ replaces all the occurrences of build to parse. This code would be replaced anyway, so leave it as is for now

val lambdaClient: AWSLambda = AWSLambdaClientBuilder.standard().withRegion(region).build()
val iamClient: AmazonIdentityManagement = AmazonIdentityManagementClientBuilder.standard().withRegion(region).build()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.amazonaws.intellij.core.region

import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.project.Project

@State(name = "region", storages = arrayOf(Storage("aws.xml")))
class AwsDefaultRegionProvider():
PersistentStateComponent<AwsDefaultRegionProvider.RegionState> {

data class RegionState(var currentRegion: String? = AwsRegionManager.defaultRegion.id)
private var regionState: RegionState = RegionState()
var currentRegion: AwsRegion
get() = AwsRegionManager.lookupRegionById(regionState.currentRegion?: AwsRegionManager.defaultRegion.id)
set(value) { regionState.currentRegion = value.id }

override fun loadState(regionState: RegionState) {
this.regionState.currentRegion = regionState.currentRegion
}

override fun getState(): RegionState {
return regionState
}

companion object {
@JvmStatic
fun getInstance(project: Project): AwsDefaultRegionProvider {
return ServiceManager.getService(project, AwsDefaultRegionProvider::class.java)
}
}
}
36 changes: 36 additions & 0 deletions src/main/kotlin/com/amazonaws/intellij/core/region/AwsRegion.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.amazonaws.intellij.core.region

import com.amazonaws.regions.Region
import com.amazonaws.regions.RegionUtils
import com.amazonaws.regions.Regions
import com.intellij.openapi.util.IconLoader
import javax.swing.Icon

data class AwsRegion private constructor(val id: String, val name: String, val icon: Icon) {
private companion object {
val UNKNOWN_REGION_FLAG = "/icons/aws-box.gif"
val REGION_FLAG_MAPPING = mapOf(
"us-east-1" to "/icons/flags/us.png",
"us-east-2" to "/icons/flags/us.png",
"us-west-1" to "/icons/flags/us.png",
"us-west-2" to "/icons/flags/us.png",
"ap-northeast-1" to "/icons/flags/japan.png",
"ap-southeast-1" to "/icons/flags/singapore.png",
"ap-southeast-2" to "/icons/flags/australia.png",
"eu-west-1" to "/icons/flags/ireland.png",
"eu-central-1" to "/icons/flags/eu.png",
"eu-west-2" to "/icons/flags/eu.png"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eu-west-2 is in London technically not part of the EU (or at least it won't be soon) we should change this to the UK flag
eu-central-1 is Frankfurt, in the spirit of consistency we should change this to the german flag

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. We are missing some more flags, and the service icons as well. Will add these all together.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also make this immutable using mapOf

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

)
}

constructor(id: String, name: String):
this(id, name, IconLoader.getIcon (REGION_FLAG_MAPPING.getOrDefault(id, UNKNOWN_REGION_FLAG)))

override fun toString(): String {
return name
}

fun isServiceSupported(serviceId: String): Boolean {
return RegionUtils.getRegion(id).isServiceSupported(serviceId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.amazonaws.intellij.core.region

import com.amazonaws.intellij.utils.notifyException
import com.amazonaws.partitions.model.Partitions
import com.amazonaws.regions.RegionUtils
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.common.collect.ImmutableMap
import com.intellij.openapi.diagnostic.Logger
import java.io.IOException

object AwsRegionManager {
private const val DEFAULT_REGION = "us-west-2"
val regions: Map<String, AwsRegion>
val defaultRegion: AwsRegion

init {
val partitions = PartitionLoader.parse()

val mutableRegionMap = mutableMapOf<String, AwsRegion>()
partitions?.partitions?.forEach {
it.regions?.forEach { key, region -> mutableRegionMap.put(key, AwsRegion(key, region.description))}
}

regions = ImmutableMap.copyOf(mutableRegionMap)
//TODO Is there a better way to notify the customer and report the error to us instead of just crash?
defaultRegion = regions.get(DEFAULT_REGION)!!
}

fun lookupRegionById(regionId: String): AwsRegion {
return regions[regionId]?: defaultRegion
}

fun isServiceSupported(region: String, serviceName: String): Boolean {
return RegionUtils.getRegion(region).isServiceSupported(serviceName)
}
}

private object PartitionLoader {
//TODO This endpoint file should be update-to-date file
private const val JAVA_SDK_PARTITION_RESOURCE_PATH = "com/amazonaws/partitions/endpoints.json"
private val LOG = Logger.getInstance(PartitionLoader::class.java)

private val mapper = ObjectMapper()
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
.disable(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(JsonParser.Feature.ALLOW_COMMENTS)

fun parse(): Partitions? {
PartitionLoader::class.java.classLoader.getResourceAsStream(JAVA_SDK_PARTITION_RESOURCE_PATH).use {
return try {
mapper.readValue<Partitions>(it, Partitions::class.java)
} catch (e: IOException) {
LOG.error("Error: failed to load file from $JAVA_SDK_PARTITION_RESOURCE_PATH !", e)
notifyException("Failed to load region endpoint file", e)
null
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.amazonaws.intellij.lambda.explorer

import com.amazonaws.intellij.ui.LAMBDA_SERVICE_ICON
import com.amazonaws.intellij.ui.SQS_QUEUE_ICON
import com.amazonaws.intellij.ui.explorer.AwsExplorerNode
import com.amazonaws.intellij.ui.explorer.AwsExplorerServiceRootNode
import com.amazonaws.services.lambda.AWSLambda
import com.amazonaws.services.lambda.AWSLambdaClientBuilder
import com.amazonaws.services.lambda.model.FunctionConfiguration
import com.intellij.ide.util.treeView.AbstractTreeNode
import com.intellij.openapi.project.Project

class AwsExplorerLambdaRootNode(project: Project, region: String):
AwsExplorerServiceRootNode<FunctionConfiguration>(project, "AWS Lambda", region, LAMBDA_SERVICE_ICON) {

//TODO we need to move to ClientFactory for initializing service client
private val client: AWSLambda = AWSLambdaClientBuilder.standard()
.withRegion(region)
.build()

override fun loadResources(): Collection<FunctionConfiguration> {
//TODO We need to list all the functions, not just one page
return client.listFunctions().functions
}

override fun mapResourceToNode(resource: FunctionConfiguration) = AwsExplorerFunctionNode(project!!, resource, region)
}

class AwsExplorerFunctionNode(project: Project, private val function: FunctionConfiguration, region: String):
AwsExplorerNode<FunctionConfiguration>(project, function, region, SQS_QUEUE_ICON) { //TODO replace to Function icon

override fun getChildren(): Collection<AbstractTreeNode<Any>> {
return emptyList()
}

override fun toString(): String {
return function.functionName
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.amazonaws.intellij.s3.explorer

import com.amazonaws.intellij.ui.S3_BUCKET_ICON
import com.amazonaws.intellij.ui.S3_SERVICE_ICON
import com.amazonaws.intellij.ui.explorer.AwsExplorerNode
import com.amazonaws.intellij.ui.explorer.AwsExplorerServiceRootNode
import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.AmazonS3ClientBuilder
import com.amazonaws.services.s3.model.Bucket
import com.intellij.ide.util.treeView.AbstractTreeNode
import com.intellij.openapi.project.Project

class AwsExplorerS3RootNode(project: Project, region: String):
AwsExplorerServiceRootNode<Bucket>(project, "Amazon S3", region, S3_SERVICE_ICON) {

//TODO use a ClientFactory instead
private var client: AmazonS3 = AmazonS3ClientBuilder.standard()
.withRegion(region)
.build()

//TODO we need to load all the buckets
override fun loadResources(): Collection<Bucket> {
return client.listBuckets()
}

override fun mapResourceToNode(resource: Bucket) = AwsExplorerBucketNode(project!!, resource, region)
}

class AwsExplorerBucketNode(project: Project, private val bucket: Bucket, region: String):
AwsExplorerNode<Bucket>(project, bucket, region, S3_BUCKET_ICON) {

override fun getChildren(): Collection<AbstractTreeNode<Any>> {
return emptyList()
}

override fun toString(): String {
return bucket.name
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.amazonaws.intellij.ui.explorer

import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.SimpleToolWindowPanel
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.components.panels.Wrapper

class AwsExplorerFactory : ToolWindowFactory, DumbAware {

override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
toolWindow.component.parent.add(ExplorerToolWindow(project))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class AwsExplorerMainView(eventHandler: AwsExplorerMainEventHandler, s3DetailsVi
main.leftComponent.preferredSize = Dimension(500, 100)
main.dividerSize = 2
add(main)
add(resources)
}

fun updateResources(root: TreeNode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.amazonaws.intellij.ui.explorer

import com.amazonaws.intellij.core.region.AwsRegionManager
import com.amazonaws.intellij.ui.AWS_ICON
import com.intellij.ide.projectView.PresentationData
import com.intellij.ide.util.treeView.AbstractTreeNode
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ClearableLazyValue
import com.intellij.ui.SimpleTextAttributes
import javax.swing.Icon

/**
* Created by zhaoxiz on 7/27/17.
*/
abstract class AwsExplorerNode<T>(project: Project, value: T, val region: String, val awsIcon: Icon?):
AbstractTreeNode<T>(project, value) {

override fun update(presentation: PresentationData?) {
presentation?.setIcon(awsIcon)
}

override fun toString() = value.toString()
}

class AwsExplorerRootNode(project: Project, region: String):
AwsExplorerNode<String>(project, "ROOT", region, AWS_ICON) {

override fun getChildren(): Collection<AbstractTreeNode<String>> {
val childrenList = mutableListOf<AbstractTreeNode<String>>()
AwsExplorerService.values()
.filter { AwsRegionManager.isServiceSupported(region, it.serviceId) }
.mapTo(childrenList) { it.buildServiceRootNode(project!!, region) }

return childrenList
}
}

abstract class AwsExplorerServiceRootNode<Resource>(project: Project, value: String, region: String, awsIcon: Icon):
AwsExplorerNode<String>(project, value, region, awsIcon) {
val cache: ClearableLazyValue<Collection<AwsExplorerNode<*>>>

init {
cache = object : ClearableLazyValue<Collection<AwsExplorerNode<*>>>() {
override fun compute(): Collection<AwsExplorerNode<*>> {
return try {
val resources = loadResources()
if (resources.isEmpty()) {
// Return EmptyNode as the single node of the list
listOf(AwsExplorerEmptyNode(project, region))
} else {
resources.map { mapResourceToNode(it) }
}
} catch (e: Exception) {
// Return the ErrorNode as the single Node of the list
listOf(AwsExplorerErrorNode(project, e, region))
}
}
}
}

override fun getChildren(): Collection<AwsExplorerNode<*>> {
return cache.value
}

// This method may throw RuntimeException, must handle it
abstract fun loadResources(): Collection<Resource>

abstract fun mapResourceToNode(resource: Resource): AwsExplorerNode<Resource>
}

class AwsExplorerErrorNode(project: Project, exception: Exception, region: String):
AwsExplorerNode<Exception>(project, exception, region, null) {

override fun getChildren(): Collection<AbstractTreeNode<Any>> {
return emptyList()
}

override fun toString(): String {
return "Error Loading Resources..."
}

override fun update(presentation: PresentationData?) {
super.update(presentation)
presentation?.tooltip = value.message
presentation?.addText(toString(), SimpleTextAttributes.ERROR_ATTRIBUTES)
}
}

class AwsExplorerEmptyNode(project: Project, region: String): AwsExplorerNode<String>(project, "empty", region, null) {

override fun getChildren(): Collection<AbstractTreeNode<Any>> {
return emptyList()
}

override fun update(presentation: PresentationData?) {
super.update(presentation)
presentation?.addText(toString(), SimpleTextAttributes.GRAYED_ATTRIBUTES)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.amazonaws.intellij.ui.explorer

import com.amazonaws.intellij.lambda.explorer.AwsExplorerLambdaRootNode
import com.amazonaws.intellij.s3.explorer.AwsExplorerS3RootNode
import com.amazonaws.services.lambda.AWSLambda
import com.amazonaws.services.s3.AmazonS3
import com.intellij.ide.util.treeView.AbstractTreeNode
import com.intellij.openapi.project.Project

enum class AwsExplorerService(val serviceId: String) {
S3(AmazonS3.ENDPOINT_PREFIX) {
override fun buildServiceRootNode(project: Project, region: String): AwsExplorerS3RootNode {
return AwsExplorerS3RootNode(project, region)
}
},
LAMBDA(AWSLambda.ENDPOINT_PREFIX) {
override fun buildServiceRootNode(project: Project, region: String): AwsExplorerLambdaRootNode {
return AwsExplorerLambdaRootNode(project, region)
}
},
;

abstract fun buildServiceRootNode(project: Project, region: String): AbstractTreeNode<String>
}
Loading