getContextValues() {
+ return Collections.singletonMap("portForward", "http://localhost:" + portLocal); //NOI18N
+ }
+
+
+}
diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwardNode.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwardNode.java
new file mode 100644
index 000000000000..6a3edb135d1e
--- /dev/null
+++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwardNode.java
@@ -0,0 +1,54 @@
+/*
+ * 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 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/assets/k8s/PortForwards.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwards.java
new file mode 100644
index 000000000000..4af048e42723
--- /dev/null
+++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwards.java
@@ -0,0 +1,246 @@
+/*
+ * 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.openide.util.Exceptions;
+import org.openide.util.NbBundle;
+import org.openide.util.RequestProcessor;
+
+/**
+ * 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}",
+ "AlreadyActive=Port forwarding already active for: {0}",
+ "NoPorts=No ports found for pod: {0},",
+ "PodNotFound=Pod not found: {0}",
+ "MaxForwards=Maximum number ({0}) of port forwards is already active"
+})
+public class PortForwards {
+ private static int LIMIT = 10;
+ private static final RequestProcessor RP = new RequestProcessor(PortForwards.class.getName(), LIMIT);
+
+ private static PortForwards instance = null;
+ private final Map> activePortForwards;
+ private final Map stopLatches;
+ private final PropertyChangeSupport propertyChangeSupport;
+
+ private PortForwards() {
+ activePortForwards = new ConcurrentHashMap<>();
+ stopLatches = 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)) {
+ NotificationUtils.showMessage(Bundle.AlreadyActive(podItem.getName()));
+ return;
+ }
+ if (activePortForwards.size() >= LIMIT) {
+ NotificationUtils.showMessage(Bundle.MaxForwards(LIMIT));
+ return;
+ }
+ CountDownLatch latch = new CountDownLatch(1);
+ ProgressHandle handle = ProgressHandle.createHandle(Bundle.Forwarding(podItem.getName()), () -> {
+ closePortForward(podItem);
+ 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) {
+ NotificationUtils.showMessage(Bundle.PodNotFound(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<>();
+
+ if (ports.isEmpty()) {
+ NotificationUtils.showMessage(Bundle.NoPorts(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);
+ forwardItems.add(new PortForwardItem(podItem, fwd.getLocalPort(), port, fwd));
+
+ }
+ stopLatches.put(podItem.getName(), latch);
+ activePortForwards.put(podItem, forwardItems);
+ firePropertyChange(podItem, null, 1);
+ latch.await();
+
+ } catch (InterruptedException | IllegalStateException ex) {
+ NotificationUtils.showErrorMessage(ex.getMessage());
+ } finally {
+ handle.finish();
+ }
+
+ });
+ }, RP);
+ }
+
+ /**
+ * 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(PodItem pod) {
+ return activePortForwards.getOrDefault(pod, 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(PodItem pod) {
+ List removed = activePortForwards.remove(pod);
+ CountDownLatch latch = stopLatches.remove(pod.getName());
+ if (latch != null) {
+ latch.countDown();
+ }
+ for (PortForwardItem portForwardItem : removed) {
+ PortForward fwd = portForwardItem.getForward();
+ if (fwd != null && fwd.isAlive()) {
+ try {
+ fwd.close();
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+ }
+ firePropertyChange(pod, 1, null);
+ }
+
+ /**
+ * 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/k8s/PortForwardsChildFactory.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwardsChildFactory.java
new file mode 100644
index 000000000000..6136b84bf44b
--- /dev/null
+++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwardsChildFactory.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.util.List;
+import org.netbeans.modules.cloud.oracle.RefreshableKeys;
+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);
+ 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/assets/RunInClusterAction.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/RunInClusterAction.java
similarity index 91%
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..45992fe55e54 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,10 +30,12 @@
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.compute.ClusterItem;
+import org.netbeans.modules.cloud.oracle.NotificationUtils;
+import org.netbeans.modules.cloud.oracle.assets.CloudAssets;
+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.developer.ContainerTagItem;
import org.netbeans.modules.cloud.oracle.steps.ProjectStep;
import org.openide.awt.ActionID;
@@ -142,7 +145,7 @@ private void runInCluster() {
.addToLabels(APP, projectName)
.endMetadata()
.withNewSpec()
- .withReplicas(3)
+ .withReplicas(2)
.withNewSelector()
.addToMatchLabels(APP, projectName)
.endSelector()
@@ -188,8 +191,13 @@ private void runInCluster() {
.create();
}
});
+ } catch (ThreadDeath x) {
+ throw x;
+ } catch(Throwable t) {
+ NotificationUtils.showErrorMessage(t.getMessage());
} finally {
h.finish();
}
}
+
}
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 86%
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..6b4e6fdd9d18 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,19 @@
* 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.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.netbeans.modules.cloud.oracle.assets.AbstractStep;
+import org.netbeans.modules.cloud.oracle.assets.Steps;
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..6504ef43831b
--- /dev/null
+++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/ShowPodLogsAction.java
@@ -0,0 +1,81 @@
+/*
+ * 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.client.dsl.PodResource;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import org.netbeans.api.io.IOProvider;
+import org.netbeans.api.io.InputOutput;
+import org.netbeans.api.io.OutputWriter;
+import org.openide.awt.ActionID;
+import org.openide.awt.ActionRegistration;
+import org.openide.util.Exceptions;
+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",
+ "OutputName=Pod {0} Log"
+})
+public class ShowPodLogsAction implements ActionListener {
+
+ private PodItem podItem;
+
+ public ShowPodLogsAction(PodItem podItem) {
+ this.podItem = podItem;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ InputOutput io = IOProvider.getDefault().getIO(Bundle.OutputName(podItem.getName()), true);
+ OutputWriter writer = io.getOut();
+
+ KubernetesUtils.runWithClient(podItem.getCluster(), client -> {
+ PodResource pod = client.pods().inNamespace(podItem.getNamespace()).withName(podItem.getName());
+ InputStream is = pod.watchLog().getOutput();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ writer.println(line);
+ }
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ });
+ }
+
+}
diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/StopPortForwardAction.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/StopPortForwardAction.java
new file mode 100644
index 000000000000..3a30aaf001c4
--- /dev/null
+++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/StopPortForwardAction.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.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.StopPortForwardAction"
+)
+@ActionRegistration(
+ displayName = "#StopPortForward",
+ asynchronous = true
+)
+
+@NbBundle.Messages({
+ "StopPortForward=Stop Port Forward"
+})
+public class StopPortForwardAction implements ActionListener {
+
+ private final PortForwardItem fwdItem;
+
+ public StopPortForwardAction(PortForwardItem fwdItem) {
+ this.fwdItem = fwdItem;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ PortForwards.getDefault().closePortForward(fwdItem.getPod());
+ }
+
+}
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..ac7ad5e4c238 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
@@ -18,6 +18,8 @@
*/
package org.netbeans.modules.cloud.oracle.compute;
+import org.netbeans.modules.cloud.oracle.assets.k8s.ClusterItem;
+import org.netbeans.modules.cloud.oracle.assets.k8s.PodChildFactory;
import com.oracle.bmc.containerengine.requests.ListClustersRequest;
import com.oracle.bmc.core.model.Instance;
import java.util.stream.Collectors;
@@ -26,7 +28,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 +44,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);
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/enterprise/cloud.oracle/test/unit/src/org/netbeans/modules/cloud/oracle/assets/CloudAssetsTest.java b/enterprise/cloud.oracle/test/unit/src/org/netbeans/modules/cloud/oracle/assets/CloudAssetsTest.java
index a4cd280cd610..97fc20bc0df8 100644
--- a/enterprise/cloud.oracle/test/unit/src/org/netbeans/modules/cloud/oracle/assets/CloudAssetsTest.java
+++ b/enterprise/cloud.oracle/test/unit/src/org/netbeans/modules/cloud/oracle/assets/CloudAssetsTest.java
@@ -21,7 +21,7 @@
import java.net.URISyntaxException;
import org.junit.Test;
import static org.junit.Assert.*;
-import org.netbeans.modules.cloud.oracle.compute.ClusterItem;
+import org.netbeans.modules.cloud.oracle.assets.k8s.ClusterItem;
import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
import org.netbeans.modules.cloud.oracle.developer.ContainerRepositoryItem;
import org.netbeans.modules.cloud.oracle.items.OCID;
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..28efef5380e7 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,16 +19,18 @@
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;
import org.netbeans.modules.cloud.oracle.assets.CloudAssets;
import org.netbeans.modules.cloud.oracle.bucket.BucketItem;
-import org.netbeans.modules.cloud.oracle.compute.ClusterItem;
+import org.netbeans.modules.cloud.oracle.assets.k8s.ClusterItem;
import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceItem;
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 +58,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 +76,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;
@@ -146,8 +156,7 @@ public void removeTreeItemDataListener(TreeDataListener l
}
@Override
- public void nodeReleased(Node n
- ) {
+ public void nodeReleased(Node n) {
}
}
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..bd3bce5c1412 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
@@ -22,8 +22,10 @@ org.netbeans.modules.cloud.oracle.items.OCIItem
org.netbeans.modules.cloud.oracle.devops.BuildPipelineItem
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.assets.k8s.ClusterItem
+org.netbeans.modules.cloud.oracle.assets.k8s.ComputeInstanceItem
+org.netbeans.modules.cloud.oracle.assets.k8s.PodItem
+org.netbeans.modules.cloud.oracle.assets.k8s.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..46314666b6f7 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,9 @@
-
+
+
+
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ConnectionSpec.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ConnectionSpec.java
index 9ab389564cc2..b20ac7112099 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ConnectionSpec.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ConnectionSpec.java
@@ -25,9 +25,9 @@
import java.net.Inet4Address;
import java.net.ServerSocket;
import java.net.Socket;
-import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
@@ -48,7 +48,7 @@ final class ConnectionSpec implements Closeable {
private final Boolean listen;
private final int port;
// @GuardedBy (this)
- private final List close = new ArrayList<>();
+ private final List close = new CopyOnWriteArrayList<>();
// @GuardedBy (this)
private final List closed = new ArrayList<>();
diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json
index 89a105f37e1f..50954beea9d6 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,21 @@
"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.ShowPodLogsAction",
+ "title": "Show pod log",
+ "icon": "$(list-flat)"
+ },
+ {
+ "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.StopPortForwardAction",
+ "title": "Stop Port Forward",
+ "icon": "$(stop-circle)"
+ },
{
"command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.RemoveFromProject",
"title": "Remove from Oracle Cloud Assets",
@@ -881,6 +900,10 @@
"command": "nbls.cloud.openConsole",
"when": "false"
},
+ {
+ "command": "nbls.cloud.openInBrowser",
+ "when": "false"
+ },
{
"command": "nbls.cloud.imageUrl.copy",
"when": "false"
@@ -937,6 +960,18 @@
"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.ShowPodLogsAction",
+ "when": "false"
+ },
+ {
+ "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.StopPortForwardAction",
+ "when": "false"
+ },
{
"command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.RemoveFromProject",
"when": "false"
@@ -1138,6 +1173,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 +1238,24 @@
"when": "viewItem =~ /clusterNamespace:.*/ && view == cloud.assets",
"group": "inline@14"
},
+ {
+ "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.PortForwardAction",
+ "when": "viewItem =~ /class:oracle.assets.k8s.PodItem/",
+ "group": "inline@14"
+ },
+ {
+ "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.ShowPodLogsAction",
+ "when": "viewItem =~ /class:oracle.assets.k8s.PodItem/",
+ "group": "inline@14"
+ },
+ {
+ "command": "nbls:Tools:org.netbeans.modules.cloud.oracle.actions.StopPortForwardAction",
+ "when": "viewItem =~ /class:oracle.assets.k8s.PortForwardItem/",
+ "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 +1265,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 +1387,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..1ba578630713 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:');