From 9d2e3c1fa0a6e7e8d56b7eaacf6d0c09b8aaac0b Mon Sep 17 00:00:00 2001 From: Jan Horvath Date: Wed, 11 Dec 2024 10:38:32 +0100 Subject: [PATCH] Adding port forward to OKE Pod --- enterprise/cloud.oracle/nbproject/project.xml | 16 ++ .../cloud/oracle/CloudChildFactory.java | 3 +- .../modules/cloud/oracle/OCINode.java | 13 +- .../modules/cloud/oracle/OCIProfile.java | 6 +- .../modules/cloud/oracle/RefreshableKeys.java | 29 +++ .../oracle/actions/ConfigMapUploader.java | 2 +- .../modules/cloud/oracle/assets/RootNode.java | 2 +- .../assets/{ => k8s}/ConfigMapProvider.java | 3 +- .../CreateSecretRotationCronJobCommand.java | 5 +- .../oracle/assets/k8s/KubernetesLoaders.java | 73 ++++++ .../assets/{ => k8s}/KubernetesUtils.java | 11 +- .../oracle/assets/k8s/PortForwardAction.java | 56 +++++ .../cloud/oracle/assets/k8s/PortForwards.java | 235 ++++++++++++++++++ .../assets/{ => k8s}/RunInClusterAction.java | 29 ++- .../{ => k8s}/SetClusterNamespaceAction.java | 14 +- .../oracle/assets/k8s/ShowPodLogsAction.java | 65 +++++ .../cloud/oracle/compute/ClusterNode.java | 8 +- .../cloud/oracle/compute/PodChildFactory.java | 58 +++++ .../modules/cloud/oracle/compute/PodItem.java | 80 ++++++ .../modules/cloud/oracle/compute/PodNode.java | 40 +++ .../modules/cloud/oracle/compute/Pods.java | 167 +++++++++++++ .../cloud/oracle/compute/PortForwardItem.java | 58 +++++ .../cloud/oracle/compute/PortForwardNode.java | 56 +++++ .../compute/PortForwardsChildFactory.java | 66 +++++ .../oracle/items/ContextValuesProvider.java | 32 +++ .../modules/cloud/oracle/resources/pod.svg | 133 ++++++++++ .../cloud/oracle/resources/port_forward.svg | 133 ++++++++++ .../LspAssetsDecorationProvider.java | 109 ++++---- .../integration/cloud-cookies.contextValues | 2 + .../modules/nbcode/integration/layer.xml | 2 +- java/java.lsp.server/vscode/package.json | 39 ++- java/java.lsp.server/vscode/src/extension.ts | 23 ++ 32 files changed, 1478 insertions(+), 90 deletions(-) create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/RefreshableKeys.java rename enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/{ => k8s}/ConfigMapProvider.java (97%) rename enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/{ => k8s}/CreateSecretRotationCronJobCommand.java (98%) create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/KubernetesLoaders.java rename enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/{ => k8s}/KubernetesUtils.java (94%) create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwardAction.java create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwards.java rename enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/{ => k8s}/RunInClusterAction.java (87%) rename enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/{ => k8s}/SetClusterNamespaceAction.java (87%) create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/ShowPodLogsAction.java create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodChildFactory.java create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodItem.java create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodNode.java create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/Pods.java create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardItem.java create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardNode.java create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardsChildFactory.java create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/items/ContextValuesProvider.java create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/resources/pod.svg create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/resources/port_forward.svg diff --git a/enterprise/cloud.oracle/nbproject/project.xml b/enterprise/cloud.oracle/nbproject/project.xml index c7ae6bd3a6e5..9a85294830ac 100644 --- a/enterprise/cloud.oracle/nbproject/project.xml +++ b/enterprise/cloud.oracle/nbproject/project.xml @@ -209,6 +209,22 @@ 1.48 + + org.netbeans.api.io + + + + 1.29 + + + + org.openide.explorer + + + + 6.50 + + org.netbeans.modules.websvc.restlib diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/CloudChildFactory.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/CloudChildFactory.java index 3325b5d1fbc6..e527d07e06d9 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/CloudChildFactory.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/CloudChildFactory.java @@ -31,7 +31,7 @@ * * @author Jan Horvath */ -public class CloudChildFactory extends ChildFactory { +public class CloudChildFactory extends ChildFactory implements RefreshableKeys { private static final Logger LOG = Logger.getLogger(CloudChildFactory.class.getName()); private final OCIItem parent; @@ -81,6 +81,7 @@ protected Node[] createNodesForKey(OCIItem key) { return new Node[]{nodeProvider.apply(key, session)}; } + @Override public void refreshKeys() { refresh(false); } diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCINode.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCINode.java index 3cc6d626f2a3..01190bbfe866 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCINode.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCINode.java @@ -26,6 +26,7 @@ import javax.swing.Action; import org.netbeans.modules.cloud.oracle.items.OCIItem; import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.ContextAwareAction; @@ -42,7 +43,7 @@ public class OCINode extends AbstractNode { private RefreshListener refreshListener; private final OCIItem item; - private final CloudChildFactory factory; + private final ChildFactory factory; private final OCISessionInitiator session; public OCINode(OCIItem item) { @@ -53,7 +54,11 @@ public OCINode(OCIItem item, OCISessionInitiator session) { this(new CloudChildFactory(session, item), item, session, Lookups.fixed(item, session)); } - private OCINode(CloudChildFactory factory, OCIItem item, OCISessionInitiator session, Lookup lookup) { + public OCINode(OCIItem item, ChildFactory factory) { + this(factory, item, null, Lookups.singleton(item)); + } + + private OCINode(ChildFactory factory, OCIItem item, OCISessionInitiator session, Lookup lookup) { super(Children.create(factory, true), lookup); setName(item.getName()); this.item = item; @@ -113,8 +118,8 @@ public static final List actionsForPath(String path, Lookup lk public void refresh() { RequestProcessor.getDefault().post(() -> { - if (factory != null) { - factory.refreshKeys(); + if (factory != null && factory instanceof RefreshableKeys) { + ((RefreshableKeys) factory).refreshKeys(); } update(item); }); diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCIProfile.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCIProfile.java index 0f393e69bec8..00816a1c1179 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCIProfile.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCIProfile.java @@ -60,6 +60,9 @@ * Represents an OCI profile. A profile has a user, tenancy and region * assigned. */ +@NbBundle.Messages({ + "LBL_HomeRegion=Region: {0}" +}) public final class OCIProfile implements OCISessionInitiator { /** @@ -178,9 +181,6 @@ public Tenancy getTenancyData() { * @return Optional {@code OCIItem} describing the Tenancy. If * Optional.empty() OCI configuration was not found */ - @NbBundle.Messages({ - "LBL_HomeRegion=Region: {0}" - }) public Optional getTenancy() { if (configProvider == null) { return Optional.empty(); diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/RefreshableKeys.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/RefreshableKeys.java new file mode 100644 index 000000000000..fb7850d7e565 --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/RefreshableKeys.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle; + +/** + * + * @author Jan Horvath + */ +public interface RefreshableKeys { + + public void refreshKeys(); + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/actions/ConfigMapUploader.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/actions/ConfigMapUploader.java index 5354503cbd76..1a82cc274afd 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/actions/ConfigMapUploader.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/actions/ConfigMapUploader.java @@ -24,7 +24,7 @@ import org.netbeans.api.project.ProjectUtils; import static org.netbeans.modules.cloud.oracle.NotificationUtils.showMessage; import org.netbeans.modules.cloud.oracle.assets.CloudAssets; -import org.netbeans.modules.cloud.oracle.assets.ConfigMapProvider; +import org.netbeans.modules.cloud.oracle.assets.k8s.ConfigMapProvider; import org.netbeans.modules.cloud.oracle.assets.Steps; import org.netbeans.modules.cloud.oracle.compute.ClusterItem; import org.netbeans.modules.cloud.oracle.steps.CompartmentStep; diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/RootNode.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/RootNode.java index f589d79dc7c5..9b4bd99a7c3e 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/RootNode.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/RootNode.java @@ -39,7 +39,7 @@ public class RootNode { private static Node instance = null; - static synchronized Node instance() { + public static synchronized Node instance() { if (instance == null) { instance = new AbstractNode( Children.create(new AssetsChildren(OCIManager.getDefault().getActiveSession()), true)); diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/ConfigMapProvider.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/ConfigMapProvider.java similarity index 97% rename from enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/ConfigMapProvider.java rename to enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/ConfigMapProvider.java index 52a7190db05c..dacd7427dc66 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/ConfigMapProvider.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/ConfigMapProvider.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.netbeans.modules.cloud.oracle.assets; +package org.netbeans.modules.cloud.oracle.assets.k8s; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; @@ -26,6 +26,7 @@ import io.fabric8.kubernetes.client.KubernetesClient; import java.util.Map; import java.util.TreeMap; +import org.netbeans.modules.cloud.oracle.assets.PropertiesGenerator; import org.netbeans.modules.cloud.oracle.compute.ClusterItem; /** diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CreateSecretRotationCronJobCommand.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/CreateSecretRotationCronJobCommand.java similarity index 98% rename from enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CreateSecretRotationCronJobCommand.java rename to enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/CreateSecretRotationCronJobCommand.java index 665d2de59270..32dee9affba0 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CreateSecretRotationCronJobCommand.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/CreateSecretRotationCronJobCommand.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.netbeans.modules.cloud.oracle.assets; +package org.netbeans.modules.cloud.oracle.assets.k8s; import io.fabric8.kubernetes.api.model.PodTemplateSpec; import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder; @@ -53,6 +53,7 @@ import java.util.concurrent.TimeoutException; import java.util.function.Supplier; import org.netbeans.modules.cloud.oracle.NotificationUtils; +import org.netbeans.modules.cloud.oracle.assets.CloudAssets; import org.netbeans.modules.cloud.oracle.compute.ClusterItem; import org.openide.util.NbBundle; @@ -93,7 +94,7 @@ public CompletableFuture runCommand(String command, List argumen } public CompletableFuture createSecretRotationCronJob() { - CompletableFuture completableFuture = new CompletableFuture(); + CompletableFuture completableFuture = new CompletableFuture<> (); this.cluster = CloudAssets.getDefault().getItem(ClusterItem.class); KubernetesUtils.runWithClient(cluster, client -> { try { diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/KubernetesLoaders.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/KubernetesLoaders.java new file mode 100644 index 000000000000..ac5094d0faf3 --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/KubernetesLoaders.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.assets.k8s; + +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.PodList; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import java.util.ArrayList; +import java.util.List; +import org.netbeans.modules.cloud.oracle.compute.ClusterItem; +import org.netbeans.modules.cloud.oracle.compute.PodItem; +import org.openide.util.NbBundle; + +/** + * + * @author Jan Horvath + */ +@NbBundle.Messages({ + "ForwardingPorts=Forwarding http://localhost:{0} to a pod {1}" + +}) +public class KubernetesLoaders { + + public static List loadPods(ClusterItem cluster, List deploymentNames) { + final List result = new ArrayList<>(); + KubernetesUtils.runWithClient(cluster, client -> { + for (String name : deploymentNames) { + Deployment deployment = client + .apps() + .deployments() + .inNamespace(cluster.getNamespace()) + .withName(name) + .get(); + if (deployment == null) { + continue; + } + + var labelSelector = deployment + .getSpec() + .getSelector() + .getMatchLabels(); + + PodList podList = client.pods() + .inNamespace(cluster.getNamespace()) + .withLabels(labelSelector) + .list(); + for (Pod pod : podList.getItems()) { + result.add(new PodItem(cluster, + pod.getMetadata().getNamespace(), + pod.getMetadata().getName())); + } + } + }); + return result; + } + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/KubernetesUtils.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/KubernetesUtils.java similarity index 94% rename from enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/KubernetesUtils.java rename to enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/KubernetesUtils.java index 37ac304999a1..251b97dbddc3 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/KubernetesUtils.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/KubernetesUtils.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.netbeans.modules.cloud.oracle.assets; +package org.netbeans.modules.cloud.oracle.assets.k8s; import com.oracle.bmc.Region; import com.oracle.bmc.http.Priorities; @@ -28,8 +28,6 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesResource; import io.fabric8.kubernetes.api.model.KubernetesResourceList; -import io.fabric8.kubernetes.api.model.batch.v1.CronJob; -import io.fabric8.kubernetes.api.model.batch.v1.CronJobList; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; @@ -45,7 +43,6 @@ import org.netbeans.modules.cloud.oracle.OCIManager; import org.netbeans.modules.cloud.oracle.OCIProfile; import org.netbeans.modules.cloud.oracle.compute.ClusterItem; -import org.openide.util.Exceptions; import org.snakeyaml.engine.v2.api.Dump; import org.snakeyaml.engine.v2.api.DumpSettings; import org.snakeyaml.engine.v2.api.Load; @@ -57,7 +54,7 @@ */ public class KubernetesUtils { - static void runWithClient(ClusterItem cluster, Consumer consumer) { + public static void runWithClient(ClusterItem cluster, Consumer consumer) { if (cluster.getConfig() == null) { cluster.update(); } @@ -67,8 +64,8 @@ static void runWithClient(ClusterItem cluster, Consumer consum Config config = prepareConfig(cluster.getConfig(), cluster); try (KubernetesClient client = new KubernetesClientBuilder().withConfig(config).build();) { consumer.accept(client); - } catch (Exception e) { - Exceptions.printStackTrace(e); + } catch (Throwable t) { + throw t; } } diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwardAction.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwardAction.java new file mode 100644 index 000000000000..ba5e5dcfdc7f --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwardAction.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.assets.k8s; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import org.netbeans.modules.cloud.oracle.compute.PodItem; +import org.openide.awt.ActionID; +import org.openide.awt.ActionRegistration; +import org.openide.util.NbBundle; + +/** + * + * @author Jan Horvath + */ +@ActionID( + category = "Tools", + id = "org.netbeans.modules.cloud.oracle.actions.PortForwardAction" +) +@ActionRegistration( + displayName = "#PortForward", + asynchronous = true +) + +@NbBundle.Messages({ + "PortForward=Start port forwarding" +}) +public class PortForwardAction implements ActionListener { + private final PodItem pod; + + public PortForwardAction(PodItem pod) { + this.pod = pod; + } + + @Override + public void actionPerformed(ActionEvent e) { + PortForwards.getDefault().startPortForward(pod); + } + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwards.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwards.java new file mode 100644 index 000000000000..54857d17136c --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwards.java @@ -0,0 +1,235 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.assets.k8s; + +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.client.LocalPortForward; +import io.fabric8.kubernetes.client.PortForward; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.IOException; +import java.net.ServerSocket; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.modules.cloud.oracle.NotificationUtils; +import org.netbeans.modules.cloud.oracle.compute.PodItem; +import org.netbeans.modules.cloud.oracle.compute.PortForwardItem; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; + +/** + * Manages port forwarding for Kubernetes Pods. + * + *

This class provides methods to start, list, and stop port forwards + * for specific Pods. It maintains the state of active port forwards + * and ensures thread-safe operations.

+ * + *

Example Usage:

+ *
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * 
+ * + * @author Jan Horvath + */ +@NbBundle.Messages({ + "PortNotFree=Port {0} is occupied by another process and cannot be used.", + "Forwarding=→ {0}" +}) +public class PortForwards { + + private static PortForwards instance = null; + private final Map> activePortForwards; + private final Map latches; + private final PropertyChangeSupport propertyChangeSupport; + + private PortForwards() { + activePortForwards = new ConcurrentHashMap<>(); + latches = new ConcurrentHashMap<>(); + propertyChangeSupport = new PropertyChangeSupport(this); + } + + public static synchronized PortForwards getDefault() { + if (instance == null) { + instance = new PortForwards(); + } + return instance; + } + + /** + * Adds a {@code PropertyChangeListener} to listen for changes in port forwarding for a specific Pod. + * + * @param pod The {@code PodItem} for which the listener is interested. + * @param listener The listener to be added. + */ + public void addPropertyChangeListener(PodItem pod, PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(pod.getName(), listener); + } + + /** + * Removes a {@code PropertyChangeListener} for a specific Pod. + * + * @param pod The {@code PodItem} for which the listener should be removed. + * @param listener The listener to be removed. + */ + public void removePropertyChangeListener(PodItem pod, PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(pod.getName(), listener); + } + + /** + * Notifies listeners of a property change for a specific Pod. + * + * @param pod The {@code PodItem} for which the change occurred. + * @param oldValue The old value of the property. + * @param newValue The new value of the property. + */ + private void firePropertyChange(PodItem pod, Object oldValue, Object newValue) { + propertyChangeSupport.firePropertyChange(pod.getName(), oldValue, newValue); + } + + /** + * Starts port forwarding for a specified Kubernetes Pod. + * + *

+ * This method retrieves the Pod's ports and forwards them to the local + * machine. If any port is unavailable, the operation is halted for that + * port and a notification is displayed.

+ * + * @param podItem The {@code PodItem} representing the Pod for which port + * forwarding should start. + */ + public void startPortForward(PodItem podItem) { + if (activePortForwards.containsKey(podItem.getName())) { + NotificationUtils.showMessage("Port forwarding already active for: " + podItem.getName()); + return; + } + CountDownLatch latch = new CountDownLatch(1); + ProgressHandle handle = ProgressHandle.createHandle(Bundle.Forwarding(podItem.getName()), () -> { + latch.countDown(); + return true; + }); + handle.start(); + CompletableFuture future = CompletableFuture.runAsync(() -> { + KubernetesUtils.runWithClient(podItem.getCluster(), client -> { + Pod pod = client.pods().inNamespace(podItem.getNamespace()).withName(podItem.getName()).get(); + + if (pod == null) { + System.out.println("Pod not found: " + podItem.getName()); + return; + } + List ports = pod.getSpec().getContainers().stream() + .flatMap(container -> container.getPorts().stream()) + .map(port -> port.getContainerPort()) + .collect(Collectors.toList()); + List forwardItems = new ArrayList<>(); + List forwards = new ArrayList<>(); + if (ports.isEmpty()) { + NotificationUtils.showMessage("No ports found for pod: " + podItem.getName()); + return; + } + try { + for (Integer port : ports) { + if (!isPortAvailable(port)) { + NotificationUtils.showErrorMessage(Bundle.PortNotFree(port)); + break; + } + LocalPortForward fwd = client.pods() + .inNamespace(pod.getMetadata().getNamespace()) + .withName(pod.getMetadata().getName()) + .portForward(port, port); + forwards.add(fwd); + forwardItems.add(new PortForwardItem(podItem, fwd.getLocalPort(), port)); + + NotificationUtils.showMessage(Bundle.ForwardingPorts(port.toString(), podItem.getName())); + } + latches.put(podItem.getName(), latch); + List oldValue = activePortForwards.put(podItem.getName(), forwardItems); + firePropertyChange(podItem, oldValue, forwardItems); + latch.await(); + for (PortForward forward : forwards) { + forward.close(); + } + activePortForwards.remove(podItem.getName()); + firePropertyChange(podItem, forwardItems, oldValue); + } catch (InterruptedException | IOException ex) { + Exceptions.printStackTrace(ex); + } catch (IllegalStateException e) { + NotificationUtils.showErrorMessage(e.getMessage()); + } finally { + handle.finish(); + } + + }); + }); + } + + /** + * Retrieves the list of active port forwards for the specified Pod. + * + * @param podName The name of the Pod. + * @return A list of active {@code PortForward} instances for the Pod, or an + * empty list if none are found. + */ + public List getActivePortForwards(String podName) { + return activePortForwards.getOrDefault(podName, Collections.emptyList()); + } + + /** + * Stops and closes all active port forwards for the specified Pod. + * + *

+ * Resources associated with the port forwarding are released, and the port + * forward is removed from the active list.

+ * + * @param podName The name of the Pod for which port forwarding should be + * stopped. + */ + public void closePortForward(String podName) { + CountDownLatch latch = latches.get(podName); + if (latch != null) { + latch.countDown(); + } + activePortForwards.remove(podName); + latches.remove(podName); + } + + /** + * Checks if a TCP port is available. + * + * @param port The port number to check. + * @return true if the port is available, false otherwise. + */ + private static boolean isPortAvailable(int port) { + try (ServerSocket serverSocket = new ServerSocket(port)) { + serverSocket.setReuseAddress(true); + return true; + } catch (IOException e) { + return false; + } + } +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/RunInClusterAction.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/RunInClusterAction.java similarity index 87% rename from enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/RunInClusterAction.java rename to enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/RunInClusterAction.java index c3762e9600fb..05893ed4b0f9 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/RunInClusterAction.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/RunInClusterAction.java @@ -16,8 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.netbeans.modules.cloud.oracle.assets; +package org.netbeans.modules.cloud.oracle.assets.k8s; +import org.netbeans.modules.cloud.oracle.assets.k8s.KubernetesUtils; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.api.model.apps.DeploymentList; @@ -29,14 +30,22 @@ import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectInformation; import org.netbeans.api.project.ProjectUtils; -import static org.netbeans.modules.cloud.oracle.assets.ConfigMapProvider.CONFIG_VOLUME_NAME; -import static org.netbeans.modules.cloud.oracle.assets.ConfigMapProvider.ENVIRONMENT; -import static org.netbeans.modules.cloud.oracle.assets.ConfigMapProvider.VOLUME_MOUNT_PATH; +import org.netbeans.modules.cloud.oracle.NotificationUtils; +import org.netbeans.modules.cloud.oracle.assets.CloudAssets; +import org.netbeans.modules.cloud.oracle.assets.RootNode; +import org.netbeans.modules.cloud.oracle.assets.Steps; +import static org.netbeans.modules.cloud.oracle.assets.k8s.ConfigMapProvider.CONFIG_VOLUME_NAME; +import static org.netbeans.modules.cloud.oracle.assets.k8s.ConfigMapProvider.ENVIRONMENT; +import static org.netbeans.modules.cloud.oracle.assets.k8s.ConfigMapProvider.VOLUME_MOUNT_PATH; import org.netbeans.modules.cloud.oracle.compute.ClusterItem; +import org.netbeans.modules.cloud.oracle.compute.ClusterNode; +import org.netbeans.modules.cloud.oracle.developer.ContainerRepositoryNode; import org.netbeans.modules.cloud.oracle.developer.ContainerTagItem; import org.netbeans.modules.cloud.oracle.steps.ProjectStep; import org.openide.awt.ActionID; import org.openide.awt.ActionRegistration; +import org.openide.nodes.Node; +import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; @@ -142,7 +151,7 @@ private void runInCluster() { .addToLabels(APP, projectName) .endMetadata() .withNewSpec() - .withReplicas(3) + .withReplicas(2) .withNewSelector() .addToMatchLabels(APP, projectName) .endSelector() @@ -188,8 +197,18 @@ private void runInCluster() { .create(); } }); + } catch(Throwable t) { + NotificationUtils.showErrorMessage(t.getMessage()); } finally { h.finish(); } } + + private void refresh() { + for (Node child : RootNode.instance().getChildren().getNodes()) { + if (child instanceof ClusterNode) { + ((ClusterNode) child).refresh(); + } + } + } } diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/SetClusterNamespaceAction.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/SetClusterNamespaceAction.java similarity index 87% rename from enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/SetClusterNamespaceAction.java rename to enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/SetClusterNamespaceAction.java index bf0ba3581c38..5d2708ce88e0 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/SetClusterNamespaceAction.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/SetClusterNamespaceAction.java @@ -16,28 +16,20 @@ * specific language governing permissions and limitations * under the License. */ -package org.netbeans.modules.cloud.oracle.assets; +package org.netbeans.modules.cloud.oracle.assets.k8s; import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.NamespaceList; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.api.model.apps.DeploymentList; -import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import org.netbeans.api.progress.ProgressHandle; -import org.netbeans.api.project.Project; +import org.netbeans.modules.cloud.oracle.assets.AbstractStep; +import org.netbeans.modules.cloud.oracle.assets.Steps; import org.netbeans.modules.cloud.oracle.compute.ClusterItem; -import org.netbeans.modules.cloud.oracle.items.OCIItem; -import org.netbeans.modules.cloud.oracle.items.TenancyItem; -import org.netbeans.modules.cloud.oracle.steps.ProjectStep; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.awt.ActionID; import org.openide.awt.ActionRegistration; diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/ShowPodLogsAction.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/ShowPodLogsAction.java new file mode 100644 index 000000000000..f446c3a3344f --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/ShowPodLogsAction.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.assets.k8s; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import org.netbeans.api.io.InputOutput; +import org.netbeans.modules.cloud.oracle.compute.PodItem; +import org.netbeans.spi.io.InputOutputProvider; +import org.openide.awt.ActionID; +import org.openide.awt.ActionRegistration; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; + +/** + * + * @author Jan Horvath + */ +@ActionID( + category = "Tools", + id = "org.netbeans.modules.cloud.oracle.actions.ShowPodLogsAction" +) +@ActionRegistration( + displayName = "#PodLogs", + asynchronous = true +) + +@NbBundle.Messages({ + "PodLogs=Start port forwarding" +}) +public class ShowPodLogsAction implements ActionListener { + + private PodItem pod; + + public ShowPodLogsAction(PodItem pod) { + this.pod = pod; + } + + @Override + public void actionPerformed(ActionEvent e) { +// KubernetesLoaders.startPortForward(pod); + + InputOutputProvider newSpiDef + = Lookup.getDefault().lookup(InputOutputProvider.class); + Object io = newSpiDef.getIO("test io", true, Lookup.EMPTY); +// newSpiDef.getOut(io); + } + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/ClusterNode.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/ClusterNode.java index 87e1c14c5734..c0bd77194416 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/ClusterNode.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/ClusterNode.java @@ -26,7 +26,6 @@ import org.netbeans.modules.cloud.oracle.OCINode; import org.netbeans.modules.cloud.oracle.compartment.CompartmentItem; import org.netbeans.modules.cloud.oracle.items.OCID; -import org.openide.nodes.Children; import org.openide.util.NbBundle; import com.oracle.bmc.containerengine.ContainerEngineClient; @@ -43,7 +42,7 @@ public class ClusterNode extends OCINode { private static final String CLUSTER_ICON = "org/netbeans/modules/cloud/oracle/resources/cluster.svg"; // NOI18N public ClusterNode(ClusterItem cluster) { - super(cluster, Children.LEAF); + super(cluster, new PodChildFactory(cluster)); setName(cluster.getName()); setDisplayName(cluster.getName()); setIconBaseWithExtension(CLUSTER_ICON); @@ -87,5 +86,10 @@ public static ChildrenProvider.SessionAware getClu .collect(Collectors.toList()); }; } + + @Override + public void refresh() { + super.refresh(); + } } diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodChildFactory.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodChildFactory.java new file mode 100644 index 000000000000..4acffc3b5669 --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodChildFactory.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.compute; + +import java.util.List; +import org.netbeans.modules.cloud.oracle.RefreshableKeys; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; + +/** + * + * @author Jan Horvath + */ +public class PodChildFactory extends ChildFactory implements RefreshableKeys { + + Pods pods; + + public PodChildFactory(ClusterItem cluster) { + pods = Pods.from(cluster); + pods.addPropertyChangeListener(evt -> refreshKeys()); + } + + @Override + protected boolean createKeys(List toPopulate) { + toPopulate.addAll(pods.getItems()); + return true; + } + + @Override + protected Node[] createNodesForKey(PodItem key) { + return new Node[]{ + new PodNode(key) + }; + } + + @Override + public void refreshKeys() { + refresh(false); + } + + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodItem.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodItem.java new file mode 100644 index 000000000000..2733f34989e8 --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodItem.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.compute; + +import java.util.Objects; + +/** + * + * @author Jan Horvath + */ +public class PodItem { + final ClusterItem cluster; + final String namespace; + final String name; + + public PodItem(ClusterItem cluster, String namespace, String name) { + this.cluster = cluster; + this.namespace = namespace; + this.name = name; + } + + public ClusterItem getCluster() { + return cluster; + } + + public String getName() { + return name; + } + + public String getNamespace() { + return namespace; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 29 * hash + Objects.hashCode(this.cluster); + hash = 29 * hash + Objects.hashCode(this.namespace); + hash = 29 * hash + Objects.hashCode(this.name); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PodItem other = (PodItem) obj; + if (!Objects.equals(this.namespace, other.namespace)) { + return false; + } + if (!Objects.equals(this.name, other.name)) { + return false; + } + return Objects.equals(this.cluster, other.cluster); + } + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodNode.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodNode.java new file mode 100644 index 000000000000..31db777807e4 --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PodNode.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.compute; + +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.util.lookup.Lookups; + +/** + * + * @author Jan Horvath + */ +public class PodNode extends AbstractNode { + + private static final String POD_ICON = "org/netbeans/modules/cloud/oracle/resources/pod.svg"; // NOI18N + + public PodNode(PodItem pod) { + super(Children.create(new PortForwardsChildFactory(pod), true), Lookups.singleton(pod)); + setName(pod.getName()); + setIconBaseWithExtension(POD_ICON); + } + + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/Pods.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/Pods.java new file mode 100644 index 000000000000..00885f6dce38 --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/Pods.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.compute; + +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.client.Watch; +import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.kubernetes.client.WatcherException; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectInformation; +import org.netbeans.api.project.ProjectUtils; +import org.netbeans.modules.cloud.oracle.assets.OpenProjectsFinder; +import org.netbeans.modules.cloud.oracle.assets.k8s.KubernetesUtils; +import org.netbeans.modules.cloud.oracle.assets.k8s.PortForwards; +import org.openide.util.Exceptions; +import org.openide.util.RequestProcessor; + +/** + * + * @author Jan Horvath + */ +public class Pods { + private final RequestProcessor RP = new RequestProcessor(Pods.class); + private final ClusterItem cluster; + private Set names = null; + private final List pods = new ArrayList<> (); + private transient PropertyChangeSupport changeSupport = null; + private CountDownLatch latch = null; + + private Pods(ClusterItem cluster) { + this.cluster = cluster; + } + + public static Pods from(ClusterItem cluster) { + Pods instance = new Pods(cluster); + instance.init(); + return instance; + } + + public List getItems() { + return Collections.unmodifiableList(pods); + } + + private void addPod(PodItem pod) { + pods.add(pod); + changeSupport.firePropertyChange("pods", pods.size(), pods.size() - 1); + + } + + private void removePod(PodItem pod) { + pods.remove(pod); + PortForwards.getDefault().closePortForward(pod.getName()); + changeSupport.firePropertyChange("pods", pods.size(), pods.size() + 1); + } + + private void removeAll() { + pods.clear(); + changeSupport.firePropertyChange("pods", pods.size(), pods.size() + 1); + } + + private void init() { + + try { + changeSupport = new PropertyChangeSupport(this); + cluster.addChangeListener((PropertyChangeEvent evt) -> { + if ("namespace".equals(evt.getPropertyName())) { + watchNamespace((String) evt.getNewValue()); + } + }); + CompletableFuture projectsFuture = OpenProjectsFinder.getDefault().findTopLevelProjects(); + projectsFuture.thenApply(projects -> { + List projectNames = new ArrayList<>(); + for (int i = 0; i < projects.length; i++) { + ProjectInformation pi = ProjectUtils.getInformation(projects[i]); + projectNames.add(pi.getDisplayName()); + } + return projectNames; + }).thenAccept(projectNames -> { + names = new HashSet<> (projectNames); + + }).get(); + watchNamespace(cluster.getNamespace()); + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + } + } + + private void watchNamespace(String namespace) { + removeAll(); + if (latch != null) { + latch.countDown(); + } + latch = new CountDownLatch(1); + addWatcher(cluster, namespace, new Watcher() { + @Override + public void eventReceived(Watcher.Action action, Pod t) { + String app = t.getMetadata().getLabels().get("app"); + if (!names.contains(app)) { + return; + } + if (action == Watcher.Action.ADDED) { + addPod(new PodItem(cluster, t.getMetadata().getNamespace(), t.getMetadata().getName())); + } else if (action == Watcher.Action.DELETED) { + removePod(new PodItem(cluster, t.getMetadata().getNamespace(), t.getMetadata().getName())); + } + } + + @Override + public void onClose(WatcherException we) { + System.out.println("close"); + latch.countDown(); + } + }); + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + changeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + changeSupport.removePropertyChangeListener(listener); + } + + public void addWatcher(ClusterItem cluster, String namespace, Watcher watcher) { + RP.post(() -> { + KubernetesUtils.runWithClient(cluster, client -> { + try { + Watch watch = client.pods() + .inNamespace(namespace) + .watch(watcher); + latch.await(); + watch.close(); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } + }); + }); + } + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardItem.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardItem.java new file mode 100644 index 000000000000..ffbccc84d8dc --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardItem.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.compute; + +import java.util.Collections; +import java.util.Map; +import org.netbeans.modules.cloud.oracle.items.ContextValuesProvider; + +/** + * + * @author Jan Horvath + */ +public class PortForwardItem implements ContextValuesProvider { + final PodItem pod; + final int portLocal; + final int portRemote; + + public PortForwardItem(PodItem pod, int portLocal, int portRemote) { + this.pod = pod; + this.portLocal = portLocal; + this.portRemote = portRemote; + } + + public PodItem getPod() { + return pod; + } + + public int getPortLocal() { + return portLocal; + } + + public int getPortRemote() { + return portRemote; + } + + @Override + public Map getContextValues() { + return Collections.singletonMap("portForward", "http://localhost:" + portLocal); //NOI18N + } + + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardNode.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardNode.java new file mode 100644 index 000000000000..f57461a56351 --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardNode.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.compute; + +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; + +/** + * + * @author Jan Horvath + */ +/** + * Node representing all active port forwards for a given PodItem. + */ +@NbBundle.Messages({ + "PortForwardDisplayName=→ Local {0} → Pod {1}" +}) +public class PortForwardNode extends AbstractNode { + private static final String PORT_FORWARD_ICON = "org/netbeans/modules/cloud/oracle/resources/port_forward.svg"; // NOI18N + private final PortForwardItem portForward; + + /** + * Creates a new node for an active port forward. + * + * @param portForward The PortForward instance to represent. + */ + public PortForwardNode(PortForwardItem portForward) { + super(Children.LEAF, Lookups.singleton(portForward)); + this.portForward = portForward; + setDisplayName(Bundle.PortForwardDisplayName( + String.valueOf(portForward.portLocal), + String.valueOf(portForward.portRemote) + + )); +// setIconBaseWithExtension(PORT_FORWARD_ICON); + } + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardsChildFactory.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardsChildFactory.java new file mode 100644 index 000000000000..03c76c332463 --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/PortForwardsChildFactory.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.compute; + +import java.util.List; +import org.netbeans.modules.cloud.oracle.RefreshableKeys; +import org.netbeans.modules.cloud.oracle.assets.k8s.PortForwards; +import org.openide.nodes.Node; + +/** + * Factory to create child nodes for active port forwards of a PodItem. + */ +class PortForwardsChildFactory extends org.openide.nodes.ChildFactory implements RefreshableKeys { + + private final PodItem podItem; + + /** + * Creates a new factory for child nodes of port forwards. + * + * @param podItem The PodItem to fetch port forwards for. + */ + public PortForwardsChildFactory(PodItem podItem) { + this.podItem = podItem; + PortForwards.getDefault().addPropertyChangeListener(podItem, evt -> { + if (podItem.getName().equals(evt.getPropertyName())) { + refreshKeys(); + } + }); + } + + @Override + protected boolean createKeys(List toPopulate) { + List portForwards = PortForwards.getDefault().getActivePortForwards(podItem.getName()); + if (portForwards != null) { + toPopulate.addAll(portForwards); + } + return true; + } + + @Override + protected Node createNodeForKey(PortForwardItem portForward) { + return new PortForwardNode(portForward); + } + + @Override + public void refreshKeys() { + refresh(false); + } + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/items/ContextValuesProvider.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/items/ContextValuesProvider.java new file mode 100644 index 000000000000..a8360006b828 --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/items/ContextValuesProvider.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cloud.oracle.items; + +import java.util.Map; + +/** + * Supplies key/value pairs to determine and assign appropriate context menu items in the UI. + * + * @author Jan Horvath + */ +public interface ContextValuesProvider { + + public Map getContextValues(); + +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/resources/pod.svg b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/resources/pod.svg new file mode 100644 index 000000000000..98403a24370c --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/resources/pod.svg @@ -0,0 +1,133 @@ + + + + + + + + + diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/resources/port_forward.svg b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/resources/port_forward.svg new file mode 100644 index 000000000000..98403a24370c --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/resources/port_forward.svg @@ -0,0 +1,133 @@ + + + + + + + + + diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspAssetsDecorationProvider.java b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspAssetsDecorationProvider.java index 6a3daa7bd5c7..068240aec3a0 100644 --- a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspAssetsDecorationProvider.java +++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspAssetsDecorationProvider.java @@ -19,6 +19,7 @@ package org.netbeans.modules.nbcode.integration; import java.net.URL; +import java.util.Map; import java.util.Optional; import java.util.logging.Logger; import org.netbeans.modules.cloud.oracle.adm.URLProvider; @@ -26,9 +27,11 @@ import org.netbeans.modules.cloud.oracle.bucket.BucketItem; import org.netbeans.modules.cloud.oracle.compute.ClusterItem; import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceItem; +import org.netbeans.modules.cloud.oracle.compute.PortForwardItem; import org.netbeans.modules.cloud.oracle.database.DatabaseItem; import org.netbeans.modules.cloud.oracle.developer.ContainerRepositoryItem; import org.netbeans.modules.cloud.oracle.developer.ContainerTagItem; +import org.netbeans.modules.cloud.oracle.items.ContextValuesProvider; import org.netbeans.modules.cloud.oracle.items.OCIItem; import org.netbeans.modules.cloud.oracle.vault.SecretItem; import org.netbeans.modules.java.lsp.server.explorer.NodeLookupContextValues; @@ -56,6 +59,7 @@ public class LspAssetsDecorationProvider implements TreeDataProvider.Factory { public static final String CTXVALUE_PREFIX_SECRET_LIFECYCLE_STATE = "lifecycleState:"; // NOI18N public static final String CTXVALUE_PREFIX_CLUSTER_NAMESPACE = "clusterNamespace:"; // NOI18N public static final String CTXVALUE_PREFIX_CONSOLE_URL = "consoleUrl:"; // NOI18N + public static final String CTXVALUE_PREFIX_PORT_FORWARD_URL = "portForward:"; // NOI18N @Override public synchronized TreeDataProvider createProvider(String treeId) { @@ -73,63 +77,70 @@ public TreeItemData createDecorations(Node n, boolean expanded) { boolean set = false; OCIItem item = n.getLookup().lookup(OCIItem.class); - if (item == null) { - return null; - } - if (item instanceof URLProvider) { - URL consoleURL = ((URLProvider) item).getURL(); - if (consoleURL != null) { - d.addContextValues(CTXVALUE_PREFIX_CONSOLE_URL + consoleURL.toExternalForm()); - set = true; + if (item != null) { + if (item instanceof URLProvider) { + URL consoleURL = ((URLProvider) item).getURL(); + if (consoleURL != null) { + d.addContextValues(CTXVALUE_PREFIX_CONSOLE_URL + consoleURL.toExternalForm()); + set = true; + } } - } - refName = CloudAssets.getDefault().getReferenceName(item); - if (refName != null) { - d.addContextValues(CTXVALUE_PREFIX_REFERENCE_NAME + refName); - set = true; - } - if (item instanceof ClusterItem) { - String namespace = ((ClusterItem) item).getNamespace(); - if (namespace != null) { - d.addContextValues(CTXVALUE_PREFIX_CLUSTER_NAMESPACE + namespace); + refName = CloudAssets.getDefault().getReferenceName(item); + if (refName != null) { + d.addContextValues(CTXVALUE_PREFIX_REFERENCE_NAME + refName); set = true; } - } - if (item instanceof ComputeInstanceItem) { - String publicIp = ((ComputeInstanceItem) item).getPublicIp(); - if (publicIp != null) { - d.addContextValues(CTXVALUE_PREFIX_PUBLIC_IP + publicIp); + if (item instanceof ClusterItem) { + String namespace = ((ClusterItem) item).getNamespace(); + if (namespace != null) { + d.addContextValues(CTXVALUE_PREFIX_CLUSTER_NAMESPACE + namespace); + set = true; + } + } + if (item instanceof ComputeInstanceItem) { + String publicIp = ((ComputeInstanceItem) item).getPublicIp(); + if (publicIp != null) { + d.addContextValues(CTXVALUE_PREFIX_PUBLIC_IP + publicIp); + set = true; + } + } + if (item instanceof ContainerRepositoryItem) { + ContainerRepositoryItem repo = (ContainerRepositoryItem) item; + d.addContextValues(CTXVALUE_PREFIX_IMAGE_COUNT + repo.getImageCount()); + d.addContextValues(CTXVALUE_PREFIX_REPOSITORY_PUBLIC + repo.getIsPublic()); set = true; } - } - if (item instanceof ContainerRepositoryItem) { - ContainerRepositoryItem repo = (ContainerRepositoryItem) item; - d.addContextValues(CTXVALUE_PREFIX_IMAGE_COUNT + repo.getImageCount()); - d.addContextValues(CTXVALUE_PREFIX_REPOSITORY_PUBLIC + repo.getIsPublic()); - set = true; - } - if (item instanceof ContainerTagItem) { - String imageUrl = ((ContainerTagItem) item).getUrl(); - Optional instance = CloudAssets.getDefault().getAssignedItems().stream().filter(i -> i.getClass().equals(ComputeInstanceItem.class)).findFirst(); - if (instance.isPresent()) { - d.addContextValues(CTXVALUE_PREFIX_PUBLIC_IP + ((ComputeInstanceItem) instance.get()).getPublicIp()); - } else { - ClusterItem cluster = CloudAssets.getDefault().getItem(ClusterItem.class); - if (cluster != null) { - d.addContextValues(CTXVALUE_PREFIX_CLUSTER_NAME + cluster.getName()); + if (item instanceof ContainerTagItem) { + String imageUrl = ((ContainerTagItem) item).getUrl(); + Optional instance = CloudAssets.getDefault().getAssignedItems().stream().filter(i -> i.getClass().equals(ComputeInstanceItem.class)).findFirst(); + if (instance.isPresent()) { + d.addContextValues(CTXVALUE_PREFIX_PUBLIC_IP + ((ComputeInstanceItem) instance.get()).getPublicIp()); + } else { + ClusterItem cluster = CloudAssets.getDefault().getItem(ClusterItem.class); + if (cluster != null) { + d.addContextValues(CTXVALUE_PREFIX_CLUSTER_NAME + cluster.getName()); + } } + d.addContextValues(CTXVALUE_PREFIX_IMAGE_URL + imageUrl); + set = true; + } + if (item instanceof BucketItem + || item instanceof DatabaseItem) { + d.addContextValues(CTXVALUE_CAP_REFERENCE_NAME); + set = true; } - d.addContextValues(CTXVALUE_PREFIX_IMAGE_URL + imageUrl); - set = true; - } - if (item instanceof BucketItem - || item instanceof DatabaseItem) { - d.addContextValues(CTXVALUE_CAP_REFERENCE_NAME); - set = true; - } - if (item instanceof SecretItem) { - d.addContextValues(CTXVALUE_PREFIX_SECRET_LIFECYCLE_STATE + ((SecretItem) item).getLifecycleState()); + if (item instanceof SecretItem) { + d.addContextValues(CTXVALUE_PREFIX_SECRET_LIFECYCLE_STATE + ((SecretItem) item).getLifecycleState()); + set = true; + } + } + + ContextValuesProvider context = n.getLookup().lookup(ContextValuesProvider.class); + if (context != null) { + for (Map.Entry entry : context.getContextValues().entrySet()) { + d.addContextValues(entry.getKey() + ":" + entry.getValue()); + } set = true; } return set ? d : null; diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/cloud-cookies.contextValues b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/cloud-cookies.contextValues index ec8d31fbfa0c..1e4498166fc4 100644 --- a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/cloud-cookies.contextValues +++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/cloud-cookies.contextValues @@ -24,6 +24,8 @@ org.netbeans.modules.cloud.oracle.devops.DevopsProjectItem org.netbeans.modules.cloud.oracle.assets.SuggestedItem org.netbeans.modules.cloud.oracle.compute.ClusterItem org.netbeans.modules.cloud.oracle.compute.ComputeInstanceItem +org.netbeans.modules.cloud.oracle.compute.PodItem +org.netbeans.modules.cloud.oracle.compute.PortForwardItem org.netbeans.modules.cloud.oracle.bucket.BucketItem org.netbeans.modules.cloud.oracle.vault.VaultItem org.netbeans.modules.cloud.oracle.developer.ContainerRepositoryItem diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/layer.xml b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/layer.xml index b8efd98ce3dd..9194210b5a95 100644 --- a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/layer.xml +++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/layer.xml @@ -64,7 +64,7 @@ - + diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json index 89a105f37e1f..e4504bd6ac30 100644 --- a/java/java.lsp.server/vscode/package.json +++ b/java/java.lsp.server/vscode/package.json @@ -529,6 +529,10 @@ "command": "nbls.cloud.openConsole", "title": "Open in OCI Console" }, + { + "command": "nbls.cloud.openInBrowser", + "title": "Open in Browser" + }, { "command": "nbls.cloud.imageUrl.copy", "title": "Copy pull command" @@ -641,6 +645,11 @@ "title": "Set a Cluster Namespace", "icon": "$(edit)" }, + { + "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.PortForwardAction", + "title": "Start port forwarding", + "icon": "$(arrow-small-right)" + }, { "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.RemoveFromProject", "title": "Remove from Oracle Cloud Assets", @@ -881,6 +890,10 @@ "command": "nbls.cloud.openConsole", "when": "false" }, + { + "command": "nbls.cloud.openInBrowser", + "when": "false" + }, { "command": "nbls.cloud.imageUrl.copy", "when": "false" @@ -937,6 +950,10 @@ "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.SetClusterNamespace", "when": "false" }, + { + "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.PortForwardAction", + "when": "false" + }, { "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.RemoveFromProject", "when": "false" @@ -1138,6 +1155,11 @@ "when": "viewItem =~ /consoleUrl:.*/", "group": "oci@2000" }, + { + "command": "nbls.cloud.openInBrowser", + "when": "viewItem =~ /portForward:.*/", + "group": "oci@2000" + }, { "command": "nbls.cloud.imageUrl.copy", "when": "viewItem =~ /imageUrl:.*/", @@ -1198,9 +1220,14 @@ "when": "viewItem =~ /clusterNamespace:.*/ && view == cloud.assets", "group": "inline@14" }, + { + "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.PortForwardAction", + "when": "viewItem =~ /class:oracle.compute.PodItem/", + "group": "inline@14" + }, { "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.RemoveFromProject", - "when": "viewItem =~ /^(?!.*(Suggested|ContainerTagItem)).*/ && view == cloud.assets", + "when": "viewItem =~ /^(?!.*(Suggested|ContainerTagItem|PodItem|PortForwardItem)).*/ && view == cloud.assets", "group": "inline@17" }, { @@ -1210,7 +1237,7 @@ }, { "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.CloudRefresh", - "when": "viewItem =~ /^(?!.*(ContainerTagItem|Suggested)).*class:oracle.*$/", + "when": "viewItem =~ /^(?!.*(ContainerTagItem|Suggested|PortForwardItem)).*class:oracle.*$/", "group": "inline@13" }, { @@ -1332,6 +1359,14 @@ "uriExpression": "nbres:/org/netbeans/modules/cloud/oracle/resources/cluster.svg", "codeicon": "compass" }, + { + "uriExpression": "nbres:/org/netbeans/modules/cloud/oracle/resources/pod.svg", + "codeicon": "vm" + }, + { + "uriExpression": "nbres:/org/netbeans/modules/cloud/oracle/resources/port_forward.svg", + "codeicon": "arrow-small-right" + }, { "uriExpression": "nbres:/org/netbeans/modules/cloud/oracle/resources/vault.svg", "codeicon": "lock" diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts index 779e3e0cdd6a..2d7134b1ac59 100644 --- a/java/java.lsp.server/vscode/src/extension.ts +++ b/java/java.lsp.server/vscode/src/extension.ts @@ -1013,6 +1013,14 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { } )); + context.subscriptions.push(commands.registerCommand(COMMAND_PREFIX + '.cloud.openInBrowser', + async (node) => { + const portForward = getValueAfterPrefix(node.contextValue, 'portForward:'); + const url = vscode.Uri.parse(portForward); + vscode.env.openExternal(url); + } + )); + context.subscriptions.push(commands.registerCommand(COMMAND_PREFIX + '.cloud.imageUrl.copy', async (node) => { const imageUrl = getValueAfterPrefix(node.contextValue, 'imageUrl:'); @@ -1078,6 +1086,21 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { }); } +let queryDocument: vscode.TextDocument | undefined; + +async function getRestDocument(): Promise { + if (queryDocument) { + const allDocuments = vscode.workspace.textDocuments; + if (!allDocuments.includes(queryDocument)) { + queryDocument = undefined; + } + } + if (!queryDocument) { + queryDocument = await vscode.workspace.openTextDocument({ language: 'http' }); + } + return queryDocument; +} + /** * Pending maintenance (install) task, activations should be chained after it. */