You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@netbeans.apache.org by "jhorvath (via GitHub)" <gi...@apache.org> on 2024/12/03 14:05:13 UTC

[PR] Adding port forward to OKE Pod [netbeans]

jhorvath opened a new pull request, #8014:
URL: https://github.com/apache/netbeans/pull/8014

   Adding port forward command to the Oracle Cloud Assets


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "MartinBalin (via GitHub)" <gi...@apache.org>.
MartinBalin commented on PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#issuecomment-2577282206

   I don't understand the do-not-merge label here and why there is some discussion about it in old already merged and released PR???


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "matthiasblaesing (via GitHub)" <gi...@apache.org>.
matthiasblaesing commented on PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#issuecomment-2596606632

   I would have expected for https://github.com/apache/netbeans/pull/8162 to happen first, but yeah.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "jhorvath (via GitHub)" <gi...@apache.org>.
jhorvath commented on code in PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#discussion_r1969429006


##########
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<PodItem> loadPods(ClusterItem cluster, List<String> deploymentNames) {
+        final List<PodItem> 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;

Review Comment:
   Only discovering what is currently deployed. There is no need to fail here.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "mbien (via GitHub)" <gi...@apache.org>.
mbien commented on PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#issuecomment-2577380255

   > I don't understand the do-not-merge label here and why there is some discussion about it in old already merged and released PR???
   
   @MartinBalin not sure what is confusing here. The PR in question was not discussed prior to integration and added >30 jars. The sig file had to be turned off since it would be 14MB of method signatures alone. This PR here appears to build on top of the other PR. Things have to be cleared up in the right order.
   
   there was 0 visible evaluation done, the PR text is one sentence. The first comment with some elaboration is two days old https://github.com/apache/netbeans/pull/7826#issuecomment-2573226486
   
   And yes, this absolutely should not have been discussed _after_ integration - I agree with you there.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "neilcsmith-net (via GitHub)" <gi...@apache.org>.
neilcsmith-net commented on PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#issuecomment-2577428163

   > And yes, this absolutely should not have been discussed after integration - I agree with you there.
   
   Ideally, yes, but we are far more towards commit-then-review than review-then-commit.  So it happens.  Sometimes things need rolling back.  Ideally the people discussing _that_ would have applied the veto on _this_!  :wink:  Instead I've given permission to remove mine.  I just want to see the concerns resolved before things build on top of them, and we end up with an even more complicated situation to manage.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "mbien (via GitHub)" <gi...@apache.org>.
mbien commented on PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#issuecomment-2577443988

   > So it happens
   
   I am sorry, but one sentence to add 30 jars is not enough. This repo is treated like the wild west by some. I don't know any other project, closed or open, which would allow things like this. Lets try to open a PR against openjdk with one sentence which adds 30 dependencies.
   
   We just had another unsquashed PR merged today - I am out of ideas tbh.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "neilcsmith-net (via GitHub)" <gi...@apache.org>.
neilcsmith-net commented on PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#issuecomment-2535378301

   -1 to further changes here pending resolution of concerns in https://github.com/apache/netbeans/pull/7826#issuecomment-2532560711 as it might conflict.
   
   cc/ @matthiasblaesing 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "sdedic (via GitHub)" <gi...@apache.org>.
sdedic commented on code in PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#discussion_r1988899098


##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/Pods.java:
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.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.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<String> names = null;
+    private final List<PodItem> 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<PodItem> 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);
+        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<Project[]> projectsFuture = OpenProjectsFinder.getDefault().findTopLevelProjects();
+            projectsFuture.thenApply(projects -> {
+                List<String> 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);

Review Comment:
   watchNamespace() should synchronize access to `latch`;  the new latch should be copied to local variable before synchronized block terminates, Watcher::onClose  should use that local copy.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/Pods.java:
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.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.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<String> names = null;
+    private final List<PodItem> 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<PodItem> 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);
+        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<Project[]> projectsFuture = OpenProjectsFinder.getDefault().findTopLevelProjects();
+            projectsFuture.thenApply(projects -> {
+                List<String> 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);

Review Comment:
   Why is this in additional `thenAccept` ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/ShowPodLogsAction.java:
##########
@@ -0,0 +1,75 @@
+/*
+ * 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.IOException;
+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.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();
+        
+        OutputStream os = new OutputStream() {
+            @Override
+            public void write(int b) throws IOException {
+                writer.write(b);

Review Comment:
   Seems that a byte is written as a full character to the Writer -- won't work for multibyte-encoded characters in the stream.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/PortForwards.java:
##########
@@ -0,0 +1,240 @@
+/*
+ * 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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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}" 
+})
+public class PortForwards {
+    private static final RequestProcessor RP = new RequestProcessor(PortForwards.class.getName(), 6);

Review Comment:
   Note: this means you can have 6 port forwards at maximum, other forwardings will not spin their thread until the former ones stop. 



##########
java/java.lsp.server/vscode/src/extension.ts:
##########
@@ -1078,6 +1086,21 @@ export function activate(context: ExtensionContext): VSNetBeansAPI {
     });
 }
 
+let queryDocument: vscode.TextDocument | undefined;
+
+async function getRestDocument(): Promise<vscode.TextDocument> {

Review Comment:
   I couldn't find usage of this ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/Pods.java:
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.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.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<String> names = null;
+    private final List<PodItem> 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<PodItem> 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);
+        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<Project[]> projectsFuture = OpenProjectsFinder.getDefault().findTopLevelProjects();
+            projectsFuture.thenApply(projects -> {
+                List<String> 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<Pod>() {
+            @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");

Review Comment:
   Leftover



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/Pods.java:
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.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.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<String> names = null;
+    private final List<PodItem> 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<PodItem> getItems() {
+        return Collections.unmodifiableList(pods);

Review Comment:
   add/removePod should synchronize with this, and the client should get a copy (since this.pods can be modified further asynchronously).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "MartinBalin (via GitHub)" <gi...@apache.org>.
MartinBalin commented on PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#issuecomment-2596616106

   Matthias, I leave it up to you and Jan. Looks like I don't have full picture.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "jhorvath (via GitHub)" <gi...@apache.org>.
jhorvath merged PR #8014:
URL: https://github.com/apache/netbeans/pull/8014


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "matthiasblaesing (via GitHub)" <gi...@apache.org>.
matthiasblaesing commented on PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#issuecomment-2578224927

   @MartinBalin the discussion was in PR #7826, because the problem (wrong license, massive dependency set) was introduced there. 
   
   The do-no-merge label was set here to stop further changes to the module and a fix for the license first, well, that did not work....
   
   There are currently two show stoppers:
   
   - The license file for the external dependencies in the `enterprise/libs.fabric8` module is most probably wrong and needs to be fixed.
   - The signature file is massive (14MB) and its usefulness questionable (discussion can be found: https://github.com/apache/netbeans/pull/8034#issuecomment-2536413976, possible solution also: https://github.com/apache/netbeans/pull/8034#issuecomment-2536706475)
   
   Not required now is fixing the size of the libraries. Either Oracles API is really such a mess or there needs to be better ways to bind a required subset of functions/structures for what we need. Extrapolate 40MB (or 26MB if I ignore kubernetes as "common") of dependencies to multiple providers and I think it is clear why I think, that needs some thoughts.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "MartinBalin (via GitHub)" <gi...@apache.org>.
MartinBalin commented on PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#issuecomment-2593557570

   I've removed `do not merge` label as discussion in https://github.com/apache/netbeans/pull/7826 concluded. Right?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "sdedic (via GitHub)" <gi...@apache.org>.
sdedic commented on code in PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#discussion_r1879456919


##########
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<PodItem> loadPods(ClusterItem cluster, List<String> deploymentNames) {
+        final List<PodItem> 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;

Review Comment:
   no log / failed (ignored) result to the caller ?



##########
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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<String, List<PortForwardItem>> activePortForwards;
+    private final Map<String, CountDownLatch> 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.
+     *
+     * <p>
+     * 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.</p>
+     *
+     * @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<Void> 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<Integer> ports = pod.getSpec().getContainers().stream()
+                        .flatMap(container -> container.getPorts().stream())
+                        .map(port -> port.getContainerPort())
+                        .collect(Collectors.toList());
+                List<PortForwardItem> forwardItems = new ArrayList<>();
+                List<PortForward> forwards = new ArrayList<>();
+                if (ports.isEmpty()) {
+                    NotificationUtils.showMessage("No ports found for pod: " + podItem.getName());

Review Comment:
   I18N



##########
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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<String, List<PortForwardItem>> activePortForwards;
+    private final Map<String, CountDownLatch> 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.
+     *
+     * <p>
+     * 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.</p>
+     *
+     * @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());

Review Comment:
   I18N & message format, please.



##########
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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<String, List<PortForwardItem>> activePortForwards;
+    private final Map<String, CountDownLatch> 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.
+     *
+     * <p>
+     * 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.</p>
+     *
+     * @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<Void> future = CompletableFuture.runAsync(() -> {

Review Comment:
   Please use RequestProcessor as the runAsync exec service parameter, to retain default Lookup in the async processing.



##########
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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<String, List<PortForwardItem>> activePortForwards;
+    private final Map<String, CountDownLatch> 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.
+     *
+     * <p>
+     * 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.</p>
+     *
+     * @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<Void> 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<Integer> ports = pod.getSpec().getContainers().stream()
+                        .flatMap(container -> container.getPorts().stream())
+                        .map(port -> port.getContainerPort())
+                        .collect(Collectors.toList());
+                List<PortForwardItem> forwardItems = new ArrayList<>();
+                List<PortForward> 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<PortForwardItem> 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<PortForwardItem> getActivePortForwards(String podName) {
+        return activePortForwards.getOrDefault(podName, Collections.emptyList());
+    }
+
+    /**
+     * Stops and closes all active port forwards for the specified Pod.
+     *
+     * <p>
+     * Resources associated with the port forwarding are released, and the port
+     * forward is removed from the active list.</p>
+     *
+     * @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);

Review Comment:
   I suggest not to remove activePortForwards/latches here, but just request the shutdown using countDown, and let the closing code that call portForward.close() to remove the forward after it is closed (or fails to close).



##########
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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<String, List<PortForwardItem>> activePortForwards;
+    private final Map<String, CountDownLatch> 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.
+     *
+     * <p>
+     * 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.</p>
+     *
+     * @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<Void> 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<Integer> ports = pod.getSpec().getContainers().stream()
+                        .flatMap(container -> container.getPorts().stream())
+                        .map(port -> port.getContainerPort())
+                        .collect(Collectors.toList());
+                List<PortForwardItem> forwardItems = new ArrayList<>();
+                List<PortForward> 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<PortForwardItem> 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);

Review Comment:
   Shouldn't this error notification be presented to the user somehow ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/KubernetesUtils.java:
##########
@@ -57,7 +54,7 @@
  */
 public class KubernetesUtils {
 
-    static void runWithClient(ClusterItem cluster, Consumer<KubernetesClient> consumer) {
+    public static void runWithClient(ClusterItem cluster, Consumer<KubernetesClient> consumer) {

Review Comment:
   I'd suggest to add some label and use Logger to log start/end of (labelled) operation and some client config info. Could be useful for future debugging.



##########
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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<String, List<PortForwardItem>> activePortForwards;
+    private final Map<String, CountDownLatch> 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.
+     *
+     * <p>
+     * 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.</p>
+     *
+     * @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<Void> 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<Integer> ports = pod.getSpec().getContainers().stream()
+                        .flatMap(container -> container.getPorts().stream())
+                        .map(port -> port.getContainerPort())
+                        .collect(Collectors.toList());
+                List<PortForwardItem> forwardItems = new ArrayList<>();
+                List<PortForward> 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;

Review Comment:
   This means that as soon as one port is not available, no more ports are forwarded - but forwardings established to this point remain active. Wouldn't it be more stable/expected  to just skip the failures and continue with next requested portforward ?



##########
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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<String, List<PortForwardItem>> activePortForwards;
+    private final Map<String, CountDownLatch> latches;

Review Comment:
   Consider naming this as 'stopLatches' etc



##########
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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<String, List<PortForwardItem>> activePortForwards;
+    private final Map<String, CountDownLatch> 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.
+     *
+     * <p>
+     * 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.</p>
+     *
+     * @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<Void> 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());

Review Comment:
   Log or NotificationUtils.showMessage ?



##########
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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<String, List<PortForwardItem>> activePortForwards;
+    private final Map<String, CountDownLatch> 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.
+     *
+     * <p>
+     * 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.</p>
+     *
+     * @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<Void> 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<Integer> ports = pod.getSpec().getContainers().stream()
+                        .flatMap(container -> container.getPorts().stream())
+                        .map(port -> port.getContainerPort())
+                        .collect(Collectors.toList());
+                List<PortForwardItem> forwardItems = new ArrayList<>();
+                List<PortForward> 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<PortForwardItem> oldValue = activePortForwards.put(podItem.getName(), forwardItems);

Review Comment:
   The old value does not make much sense: even if old value is later restored, its `latch` is lost for good.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/ClusterNode.java:
##########
@@ -87,5 +86,10 @@ public static ChildrenProvider.SessionAware<CompartmentItem, ClusterItem> getClu
                     .collect(Collectors.toList());
         };
     }
+
+    @Override

Review Comment:
   Leftover no-op ?



##########
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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<String, List<PortForwardItem>> activePortForwards;
+    private final Map<String, CountDownLatch> 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.
+     *
+     * <p>
+     * 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.</p>
+     *
+     * @param podItem The {@code PodItem} representing the Pod for which port
+     * forwarding should start.
+     */
+    public void startPortForward(PodItem podItem) {
+        if (activePortForwards.containsKey(podItem.getName())) {

Review Comment:
   This does not prevent from starting portforward for the same Pod twice: the method may run, and will spawn a forward thread, again until `activePortForwards.put` after communicating with the k8s client.
   
   If it was meant as sync/prevention of starting portforward twice, it should be fixed.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/RunInClusterAction.java:
##########
@@ -188,8 +197,18 @@ private void runInCluster() {
                             .create();
                 }
             });
+        } catch(Throwable t) {

Review Comment:
   Pass on ThreadDeath.



##########
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Example Usage:</p>
+ * <pre>
+ * PortForwards manager = PortForwards.getDefault();
+ * manager.startPortForward(podItem);
+ * List<PortForwardItem> forwards = manager.getActivePortForwards("pod-name");
+ * manager.closePortForward("pod-name");
+ * </pre>
+ *
+ * @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<String, List<PortForwardItem>> activePortForwards;
+    private final Map<String, CountDownLatch> 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.
+     *
+     * <p>
+     * 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.</p>
+     *
+     * @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<Void> 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<Integer> ports = pod.getSpec().getContainers().stream()
+                        .flatMap(container -> container.getPorts().stream())
+                        .map(port -> port.getContainerPort())
+                        .collect(Collectors.toList());
+                List<PortForwardItem> forwardItems = new ArrayList<>();
+                List<PortForward> 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<PortForwardItem> oldValue = activePortForwards.put(podItem.getName(), forwardItems);
+                    firePropertyChange(podItem, oldValue, forwardItems);
+                    latch.await();

Review Comment:
   Consider using other approach than CDL here. Each Pod will block its thread until it is requested to close. Ideally the request to close should execute the closing procedure, eventually asynchronously.
   
   Please add `@OnStop` handler that closes port forwards upon NB shutdown. Eventually load *active* port forwards from the k8s after startup, if possible.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/k8s/KubernetesUtils.java:
##########
@@ -67,8 +64,8 @@ static void runWithClient(ClusterItem cluster, Consumer<KubernetesClient> 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) {

Review Comment:
   No op ?



##########
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);

Review Comment:
   This seems as an unfinished implementation ?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Re: [PR] Adding port forward to OKE Pod [netbeans]

Posted by "matthiasblaesing (via GitHub)" <gi...@apache.org>.
matthiasblaesing commented on PR #8014:
URL: https://github.com/apache/netbeans/pull/8014#issuecomment-2599754828

   As PR #8162 is in I'll stop intervening.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists