-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implemented 'Open Dashboard' action (#617)
Signed-off-by: Andre Dietisheim <adietish@redhat.com>
- Loading branch information
Showing
16 changed files
with
862 additions
and
13 deletions.
There are no files selected for viewing
57 changes: 57 additions & 0 deletions
57
src/main/kotlin/com/redhat/devtools/intellij/kubernetes/actions/OpenDashboardAction.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2023 Red Hat, Inc. | ||
* Distributed under license by Red Hat, Inc. All rights reserved. | ||
* This program is made available under the terms of the | ||
* Eclipse Public License v2.0 which accompanies this distribution, | ||
* and is available at http://www.eclipse.org/legal/epl-v20.html | ||
* | ||
* Contributors: | ||
* Red Hat, Inc. - initial API and implementation | ||
******************************************************************************/ | ||
package com.redhat.devtools.intellij.kubernetes.actions | ||
|
||
import com.intellij.ide.BrowserUtil | ||
import com.intellij.openapi.actionSystem.AnActionEvent | ||
import com.intellij.openapi.diagnostic.logger | ||
import com.redhat.devtools.intellij.common.actions.StructureTreeAction | ||
import com.redhat.devtools.intellij.kubernetes.model.Notification | ||
import com.redhat.devtools.intellij.kubernetes.model.context.IActiveContext | ||
import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService | ||
import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService.PROP_RESOURCE_KIND | ||
import javax.swing.tree.TreePath | ||
|
||
class OpenDashboardAction: StructureTreeAction() { | ||
|
||
override fun actionPerformed(event: AnActionEvent?, path: Array<out TreePath>?, selected: Array<out Any>?) { | ||
val model = getResourceModel() ?: return | ||
val currentContext = model.getCurrentContext() ?: return | ||
run("Opening Dashboard...", true) | ||
{ | ||
val telemetry = TelemetryService.instance.action("open dashboard") | ||
.property(PROP_RESOURCE_KIND, currentContext.name) | ||
try { | ||
val url = currentContext.getDashboardUrl() | ||
BrowserUtil.open(url.toString()) | ||
telemetry.success().send() | ||
} catch (e: Exception) { | ||
logger<OpenDashboardAction>().warn("Could not open Dashboard", e) | ||
Notification().error("Dashboard Error", e.message ?: "") | ||
telemetry.error(e).send() | ||
} | ||
} | ||
} | ||
|
||
override fun actionPerformed(event: AnActionEvent?, path: TreePath?, selected: Any?) { | ||
// not called | ||
} | ||
|
||
override fun isVisible(selected: Array<out Any>?): Boolean { | ||
return selected?.any { isVisible(it) } | ||
?: false | ||
} | ||
|
||
override fun isVisible(selected: Any?): Boolean { | ||
val context = selected?.getElement<IActiveContext<*,*>>() | ||
return context != null | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/dashboard/AbstractDashboard.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2023 Red Hat, Inc. | ||
* Distributed under license by Red Hat, Inc. All rights reserved. | ||
* This program is made available under the terms of the | ||
* Eclipse Public License v2.0 which accompanies this distribution, | ||
* and is available at http://www.eclipse.org/legal/epl-v20.html | ||
* | ||
* Contributors: | ||
* Red Hat, Inc. - initial API and implementation | ||
******************************************************************************/ | ||
package com.redhat.devtools.intellij.kubernetes.model.dashboard | ||
|
||
import com.redhat.devtools.intellij.kubernetes.model.util.PluginException | ||
import io.fabric8.kubernetes.client.KubernetesClient | ||
import io.fabric8.kubernetes.client.http.HttpStatusMessage | ||
|
||
|
||
/** | ||
* An abstract factory that can determine the url of the dashboard for a cluster. | ||
*/ | ||
abstract class AbstractDashboard<C : KubernetesClient>( | ||
protected val client: C, | ||
private val contextName: String, | ||
private val clusterUrl: String?, | ||
/** for testing purposes */ | ||
protected val httpRequest: HttpRequest | ||
): IDashboard { | ||
|
||
private var url: String? = null | ||
|
||
override fun get(): String { | ||
val url = this.url ?: connect() | ||
this.url = url | ||
return url | ||
} | ||
|
||
private fun connect(): String { | ||
val status = try { | ||
doConnect() | ||
} catch (e: Exception) { | ||
throw PluginException("Could not find Dashboard for cluster $contextName at $clusterUrl: ${e.message}") | ||
} ?: throw PluginException("Could not find Dashboard for cluster $contextName at $clusterUrl") | ||
|
||
if (status.isSuccessful | ||
|| status.isForbidden | ||
) { | ||
return status.url | ||
} else { | ||
throw PluginException( | ||
"Could not reach dashboard for cluster $contextName ${ | ||
if (clusterUrl.isNullOrEmpty()) { | ||
"" | ||
} else { | ||
"at $clusterUrl" | ||
} | ||
}" + "${ | ||
if (status.status == null) { | ||
"" | ||
} else { | ||
". Responded with ${ | ||
HttpStatusMessage.getMessageForStatus(status.status) | ||
}" | ||
} | ||
}." | ||
) | ||
} | ||
} | ||
|
||
protected abstract fun doConnect(): HttpRequest.HttpStatusCode? | ||
|
||
override fun close() { | ||
// noop default impl | ||
} | ||
} | ||
|
||
interface IDashboard { | ||
fun get(): String | ||
fun close() | ||
} |
129 changes: 129 additions & 0 deletions
129
src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/dashboard/HttpRequest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2023 Red Hat, Inc. | ||
* Distributed under license by Red Hat, Inc. All rights reserved. | ||
* This program is made available under the terms of the | ||
* Eclipse Public License v2.0 which accompanies this distribution, | ||
* and is available at http://www.eclipse.org/legal/epl-v20.html | ||
* | ||
* Contributors: | ||
* Red Hat, Inc. - initial API and implementation | ||
******************************************************************************/ | ||
package com.redhat.devtools.intellij.kubernetes.model.dashboard | ||
|
||
import io.fabric8.kubernetes.client.http.HttpResponse | ||
import java.net.HttpURLConnection | ||
import java.security.SecureRandom | ||
import java.security.cert.CertificateException | ||
import java.security.cert.CertificateParsingException | ||
import java.security.cert.X509Certificate | ||
import javax.net.ssl.SSLContext | ||
import javax.net.ssl.SSLHandshakeException | ||
import javax.net.ssl.TrustManager | ||
import javax.net.ssl.X509TrustManager | ||
import okhttp3.OkHttpClient | ||
import okhttp3.Request | ||
import okhttp3.Response | ||
|
||
class HttpRequest { | ||
|
||
companion object { | ||
private val trustAllTrustManager = object : X509TrustManager { | ||
|
||
@Throws(CertificateException::class) | ||
override fun checkServerTrusted(chain: Array<X509Certificate>?, authType: String?) { | ||
// ignore aka trust | ||
} | ||
|
||
override fun getAcceptedIssuers(): Array<X509Certificate> { | ||
return emptyArray() | ||
} | ||
|
||
@Throws(CertificateException::class) | ||
override fun checkClientTrusted(chain: Array<X509Certificate>?, authType: String?) { | ||
// ignore aka trust | ||
} | ||
} | ||
} | ||
|
||
fun request(url: String): HttpStatusCode { | ||
return requestHttpStatusCode(url) | ||
} | ||
|
||
fun request(host: String, port: Int): HttpStatusCode { | ||
var status = requestHttpStatusCode("http://$host:$port") | ||
if (status.isSuccessful) { | ||
return status | ||
} else { | ||
status = requestHttpStatusCode("https://$host:$port") | ||
if (status.isSuccessful) { | ||
return status | ||
} | ||
} | ||
return status | ||
} | ||
|
||
/** | ||
* Requests the https status code for the given url. | ||
* Return [HttpStatusCode] and throws if connecting fails. | ||
* | ||
* @param url the url to request the http status code for | ||
* | ||
* The implementation is ignores (private) SSL certificates and doesn't verify the hostname. | ||
* All that matters is whether we can connect successfully or not. | ||
* OkHttp is used because it allows to set a [javax.net.ssl.HostnameVerifier] on a per connection base. | ||
*/ | ||
private fun requestHttpStatusCode(url: String): HttpStatusCode { | ||
val sslContext = createSSLContext() | ||
var response: Response? = null | ||
try { | ||
response = OkHttpClient.Builder() | ||
.sslSocketFactory(sslContext.socketFactory, trustAllTrustManager) | ||
.hostnameVerifier { _, _ -> true } | ||
.build() | ||
.newCall( | ||
Request.Builder() | ||
.url(url) | ||
.build() | ||
) | ||
.execute() | ||
return HttpStatusCode(url, response.code) | ||
} catch (e: SSLHandshakeException) { | ||
if (e.cause is CertificateParsingException) { | ||
/** | ||
* Fake 200 OK response in case ssl handshake certificate could not be parsed. | ||
* This happens with azure dashboard where a certificate is used that the jdk cannot handle: | ||
* ``` | ||
* javax.net.ssl.SSLHandshakeException: Failed to parse server certificates | ||
* java.security.cert.CertificateParsingException: Empty issuer DN not allowed in X509Certificates | ||
* ``` | ||
* @see [Stackoverflow question 65692099](https://stackoverflow.com/questions/65692099/java-empty-issuer-dn-not-allowed-in-x509certificate-libimobiledevice-implementa) | ||
* @see [kubernetes cert-manager issue #3634](https://github.com/cert-manager/cert-manager/issues/3634) | ||
*/ | ||
return HttpStatusCode(url, HttpURLConnection.HTTP_OK) | ||
} else { | ||
throw e | ||
} | ||
} finally { | ||
response?.close() | ||
} | ||
} | ||
|
||
private fun createSSLContext(): SSLContext { | ||
val sslContext: SSLContext = SSLContext.getDefault() | ||
sslContext.init(null, arrayOf<TrustManager>(trustAllTrustManager), SecureRandom()) | ||
return sslContext | ||
} | ||
|
||
class HttpStatusCode(val url: String, val status: Int?) { | ||
val isSuccessful: Boolean | ||
get() { | ||
return status != null | ||
&& HttpResponse.isSuccessful(status) | ||
} | ||
val isForbidden: Boolean | ||
get() { | ||
return status != null | ||
&& HttpURLConnection.HTTP_FORBIDDEN == status | ||
} | ||
} | ||
} |
Oops, something went wrong.