diff --git a/enterprise/cloud.oracle/nbproject/project.xml b/enterprise/cloud.oracle/nbproject/project.xml
index 797fc5dcdb62..ba7bece5da58 100644
--- a/enterprise/cloud.oracle/nbproject/project.xml
+++ b/enterprise/cloud.oracle/nbproject/project.xml
@@ -151,6 +151,14 @@
2.29
+
+ org.openide.actions
+
+
+
+ 6.65
+
+
org.openide.awt
diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/NotificationUtils.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/NotificationUtils.java
new file mode 100644
index 000000000000..52281b294b0e
--- /dev/null
+++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/NotificationUtils.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+
+/**
+ *
+ * @author Dusan Petrovic
+ */
+public class NotificationUtils {
+
+ public static boolean confirmAction(String message) {
+ NotifyDescriptor.Confirmation msg = new NotifyDescriptor.Confirmation(message, NotifyDescriptor.YES_NO_OPTION);
+ Object choice = DialogDisplayer.getDefault().notify(msg);
+ return choice == NotifyDescriptor.YES_OPTION || choice == NotifyDescriptor.OK_OPTION;
+ }
+
+ public static void showErrorMessage(String message) {
+ DialogDisplayer.getDefault()
+ .notifyLater(new NotifyDescriptor.Message(message, NotifyDescriptor.ERROR_MESSAGE));
+ }
+
+ public static void showMessage(String message) {
+ DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message(message));
+ }
+}
diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCINode.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCINode.java
index bfc515d76fe3..52aa6cda255a 100644
--- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCINode.java
+++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCINode.java
@@ -30,6 +30,7 @@
import org.openide.nodes.Node;
import org.openide.util.ContextAwareAction;
import org.openide.util.Lookup;
+import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
import org.openide.util.lookup.Lookups;
@@ -111,15 +112,21 @@ public static final List extends Action> actionsForPath(String path, Lookup lk
}
public void refresh() {
- if (factory != null) {
- factory.refreshKeys();
- }
- update(item);
+ RequestProcessor.getDefault().post(() -> {
+ if (factory != null) {
+ factory.refreshKeys();
+ }
+ update(item);
+ });
}
public void update(OCIItem item) {
}
+
+ public OCIItem getItem() {
+ return this.item;
+ }
@Override
public Node.Handle getHandle() {
diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/developer/ContainerTagNode.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/developer/ContainerTagNode.java
index 5869f53d5f8f..51a835d065a0 100644
--- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/developer/ContainerTagNode.java
+++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/developer/ContainerTagNode.java
@@ -19,21 +19,42 @@
package org.netbeans.modules.cloud.oracle.developer;
import com.oracle.bmc.artifacts.ArtifactsClient;
+import com.oracle.bmc.artifacts.requests.DeleteContainerImageRequest;
import com.oracle.bmc.artifacts.requests.ListContainerImagesRequest;
+import com.oracle.bmc.artifacts.responses.DeleteContainerImageResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.stream.Collectors;
+import javax.swing.Action;
import org.netbeans.modules.cloud.oracle.ChildrenProvider;
import org.netbeans.modules.cloud.oracle.NodeProvider;
+import static org.netbeans.modules.cloud.oracle.NotificationUtils.confirmAction;
+import static org.netbeans.modules.cloud.oracle.NotificationUtils.showErrorMessage;
+import static org.netbeans.modules.cloud.oracle.NotificationUtils.showMessage;
+import org.netbeans.modules.cloud.oracle.OCIManager;
import org.netbeans.modules.cloud.oracle.OCINode;
import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.openide.actions.DeleteAction;
import org.openide.nodes.Children;
+import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
+import org.openide.util.RequestProcessor;
+import org.openide.util.actions.SystemAction;
/**
*
* @author Jan Horvath
*/
@NbBundle.Messages({
- "ContainerTagDesc=Pull URL: {0}\nVersion: {1}\nDigest: {2}"
+ "ContainerTagDesc=Pull URL: {0}\nVersion: {1}\nDigest: {2}",
+ "# {0} - [OCIItem name]",
+ "MSG_ConfirmDeleteAction=Are you sure that you want to delete {0}.",
+ "# {0} - [OCIItem name]",
+ "MSG_DeleteActionFailed=Failed to delete {0}.",
+ "# {0} - [OCIItem name]",
+ "MSG_DeleteActionSuccess=Successfully deleted {0}."
})
public class ContainerTagNode extends OCINode {
private static final String CONTAINER_TAG_ICON = "org/netbeans/modules/cloud/oracle/resources/containertag.svg"; // NOI18N
@@ -49,6 +70,51 @@ public ContainerTagNode(ContainerTagItem tag) {
public static NodeProvider createNode() {
return ContainerTagNode::new;
}
+
+ @Override
+ public Action[] getActions(boolean context) {
+ Action[] actions = super.getActions(context);
+ List actionList = new ArrayList<>(Arrays.asList(actions));
+ actionList.add(SystemAction.get(DeleteAction.class));
+ return actionList.toArray(Action[]::new);
+ }
+
+ @Override
+ public boolean canDestroy() {
+ return true;
+ }
+
+ @Override
+ public void destroy() throws IOException {
+ RequestProcessor.getDefault().post(() -> {
+ if (!confirmAction(Bundle.MSG_ConfirmDeleteAction(this.getName()))) {
+ return;
+ }
+ ArtifactsClient client = OCIManager.getDefault().getActiveSession().newClient(ArtifactsClient.class);
+ DeleteContainerImageRequest request = DeleteContainerImageRequest.builder()
+ .imageId(this.getItem().getKey().getValue())
+ .build();
+ DeleteContainerImageResponse response;
+
+ try {
+ response = client.deleteContainerImage(request);
+ } catch (Exception ex) {
+ Exceptions.printStackTrace(ex);
+ showErrorMessage(Bundle.MSG_DeleteActionFailed(this.getName()));
+ return;
+ }
+ if (response.get__httpStatusCode__() != 204) {
+ showErrorMessage(Bundle.MSG_DeleteActionFailed(this.getName()));
+ return;
+ }
+
+ if (this.getParentNode() instanceof OCINode) {
+ ((OCINode)this.getParentNode()).refresh();
+ }
+ showMessage(Bundle.MSG_DeleteActionSuccess(this.getName()));
+ });
+
+ }
/**
* Retrieves list of Vaults belonging to a given Compartment.
diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/vault/SecretItem.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/vault/SecretItem.java
index a4643bb4e510..9338ab4f4dfb 100644
--- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/vault/SecretItem.java
+++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/vault/SecretItem.java
@@ -18,6 +18,7 @@
*/
package org.netbeans.modules.cloud.oracle.vault;
+import java.util.Date;
import org.netbeans.modules.cloud.oracle.items.OCID;
import org.netbeans.modules.cloud.oracle.items.OCIItem;
@@ -26,18 +27,40 @@
* @author Jan Horvath
*/
public class SecretItem extends OCIItem {
+
+ private String lifecycleState;
+ private Date deletionTime;
- public SecretItem(OCID id, String compartmentId, String name) {
+ public SecretItem(OCID id, String compartmentId, String name, String lifecycleState, Date deletionTime) {
super(id, compartmentId, name);
+ this.lifecycleState = lifecycleState;
+ this.deletionTime = deletionTime;
}
- public SecretItem() {
+ public SecretItem() {
super();
+ this.lifecycleState = null;
+ this.deletionTime = null;
}
-
+
@Override
public int maxInProject() {
return Integer.MAX_VALUE;
}
+ public Date getDeletionTime() {
+ return this.deletionTime;
+ }
+
+ void setDeletionTime(Date deletionTime) {
+ this.deletionTime = deletionTime;
+ }
+
+ public String getLifecycleState() {
+ return this.lifecycleState;
+ }
+
+ void setLifecycleState(String lifecycleState) {
+ this.lifecycleState = lifecycleState;
+ }
}
diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/vault/SecretNode.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/vault/SecretNode.java
index f597e27a805f..002a0ae096bb 100644
--- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/vault/SecretNode.java
+++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/vault/SecretNode.java
@@ -18,36 +18,172 @@
*/
package org.netbeans.modules.cloud.oracle.vault;
-import com.oracle.bmc.keymanagement.KmsVaultClient;
import com.oracle.bmc.vault.VaultsClient;
+import com.oracle.bmc.vault.model.ScheduleSecretDeletionDetails;
+import com.oracle.bmc.vault.model.Secret;
+import com.oracle.bmc.vault.model.SecretSummary.LifecycleState;
+import com.oracle.bmc.vault.requests.GetSecretRequest;
import com.oracle.bmc.vault.requests.ListSecretsRequest;
+import com.oracle.bmc.vault.requests.ScheduleSecretDeletionRequest;
+import com.oracle.bmc.vault.responses.GetSecretResponse;
+import com.oracle.bmc.vault.responses.ScheduleSecretDeletionResponse;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
import java.util.stream.Collectors;
+import javax.swing.Action;
import org.netbeans.modules.cloud.oracle.ChildrenProvider;
import org.netbeans.modules.cloud.oracle.NodeProvider;
-import static org.netbeans.modules.cloud.oracle.OCIManager.getDefault;
+import static org.netbeans.modules.cloud.oracle.NotificationUtils.confirmAction;
+import static org.netbeans.modules.cloud.oracle.NotificationUtils.showErrorMessage;
+import static org.netbeans.modules.cloud.oracle.NotificationUtils.showMessage;
+import org.netbeans.modules.cloud.oracle.OCIManager;
import org.netbeans.modules.cloud.oracle.OCINode;
import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.openide.actions.DeleteAction;
import org.openide.nodes.Children;
+import org.openide.util.Exceptions;
+import org.openide.util.NbBundle;
+import org.openide.util.RequestProcessor;
+import org.openide.util.actions.SystemAction;
/**
*
* @author Jan Horvath
*/
+@NbBundle.Messages({
+ "SecretNodeDesc=Valut Secret: {0}\nLifecycle State: {1}",
+ "SecretNodeDeletingDesc=Valut Secret: {0}\nLifecycle State: {1}\nDeletion time: {2}",
+ "# {0} - [OCIItem name]",
+ "MSG_ConfirmDeleteAction=Are you sure that you want to schedule deletion of {0}",
+ "# {0} - [OCIItem name]",
+ "MSG_DeleteActionFailed=Failed to schedule deletion of {0}.",
+ "# {0} - [OCIItem name]", "# {1} - [Scheduled deletion time]",
+ "MSG_DeleteActionSuccess=Successfully scheduled deletion of {0} at {1}."
+})
public class SecretNode extends OCINode {
private static final String SECRET_ICON = "org/netbeans/modules/cloud/oracle/resources/secret.svg"; // NOI18N
+ private static final SimpleDateFormat DELETION_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public SecretNode(SecretItem vault) {
super(vault, Children.LEAF);
setName(vault.getName());
setDisplayName(vault.getName());
setIconBaseWithExtension(SECRET_ICON);
- setShortDescription(vault.getDescription());
+ setShortDescription(
+ createShortDescription(
+ vault.getLifecycleState(),
+ vault.getDeletionTime()));
+ }
+
+ private String createShortDescription(String state, Date deletionTime) {
+ if (deletionTime != null) {
+ return Bundle.SecretNodeDeletingDesc(this.getItem().getName(), state, formatDateTime(deletionTime));
+ }
+ return Bundle.SecretNodeDesc(this.getItem().getName(), state);
+ }
+
+ private String formatDateTime(Date deletionTime) {
+ return DELETION_TIME_FORMAT.format(deletionTime);
}
public static NodeProvider createNode() {
return SecretNode::new;
}
+
+ @Override
+ public void update(OCIItem item) {
+ SecretItem orig = (SecretItem) item;
+ VaultsClient client = OCIManager.getDefault().getActiveProfile().newClient(VaultsClient.class);
+ GetSecretRequest request = GetSecretRequest.builder()
+ .secretId(orig.getKey().getValue())
+ .build();
+
+ GetSecretResponse response = client.getSecret(request);
+ Secret secret = response.getSecret();
+ orig.setLifecycleState(secret.getLifecycleState().getValue());
+ orig.setDeletionTime(secret.getTimeOfDeletion());
+ setShortDescription(
+ createShortDescription(
+ orig.getLifecycleState(),
+ orig.getDeletionTime()));
+ }
+
+ @Override
+ public Action[] getActions(boolean context) {
+ Action[] actions = super.getActions(context);
+ List actionList = new ArrayList<>(Arrays.asList(actions));
+ actionList.add(SystemAction.get(DeleteAction.class));
+ return actionList.toArray(Action[]::new);
+ }
+
+ @Override
+ public boolean canDestroy() {
+ return ((SecretItem)this.getItem()).getLifecycleState().equals(LifecycleState.Active.getValue());
+ }
+
+ @Override
+ public void destroy() throws IOException {
+ RequestProcessor.getDefault().post(() -> {
+ if (!confirmAction(Bundle.MSG_ConfirmDeleteAction(this.getName()))) {
+ return;
+ }
+
+ VaultsClient client = OCIManager.getDefault().getActiveSession().newClient(VaultsClient.class);
+ Date deletionTime = getDeletionTime();
+ ScheduleSecretDeletionRequest request = buildScheduleDeletionRequest(deletionTime);
+ ScheduleSecretDeletionResponse response;
+
+ try {
+ response = client.scheduleSecretDeletion(request);
+ } catch (Exception ex) {
+ Exceptions.printStackTrace(ex);
+ showErrorMessage(Bundle.MSG_DeleteActionFailed(this.getName()));
+ return;
+ }
+
+ if (response.get__httpStatusCode__() != 200) {
+ showErrorMessage(Bundle.MSG_DeleteActionFailed(this.getName()));
+ return;
+ }
+
+ updateToPendingState(deletionTime);
+ showMessage(Bundle.MSG_DeleteActionSuccess(
+ this.getName(),
+ this.formatDateTime(deletionTime)));
+ });
+ }
+
+ private ScheduleSecretDeletionRequest buildScheduleDeletionRequest(Date deletionTime) {
+ ScheduleSecretDeletionDetails scheduleSecretDeletionDetails = ScheduleSecretDeletionDetails.builder()
+ .timeOfDeletion(deletionTime)
+ .build();
+ return ScheduleSecretDeletionRequest.builder()
+ .secretId(this.getItem().getKey().getValue())
+ .scheduleSecretDeletionDetails(scheduleSecretDeletionDetails)
+ .build();
+ }
+
+ private void updateToPendingState(Date deletionTime) {
+ ((SecretItem) this.getItem()).setLifecycleState(LifecycleState.PendingDeletion.getValue());
+ setShortDescription(
+ createShortDescription(
+ LifecycleState.PendingDeletion.getValue(),
+ deletionTime));
+ }
+
+ private Date getDeletionTime() {
+ LocalDateTime tomorrow = LocalDateTime.now().plusDays(1).plusHours(1);
+ return Date.from(tomorrow.atZone(ZoneId.systemDefault()).toInstant());
+ }
+
/**
* Retrieves list of Secrets belonging to a given Vault.
*
@@ -69,7 +205,9 @@ public static ChildrenProvider.SessionAware getSecrets()
.map(d -> new SecretItem(
OCID.of(d.getId(), "Vault/Secret"), //NOI18N
d.getCompartmentId(),
- d.getSecretName())
+ d.getSecretName(),
+ d.getLifecycleState().getValue(),
+ d.getTimeOfDeletion())
)
.collect(Collectors.toList());
};
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspAssetsDecorationProvider.java b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspAssetsDecorationProvider.java
index 9e2160d79da8..147b573f328a 100644
--- a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspAssetsDecorationProvider.java
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspAssetsDecorationProvider.java
@@ -27,6 +27,7 @@
import org.netbeans.modules.cloud.oracle.developer.ContainerRepositoryItem;
import org.netbeans.modules.cloud.oracle.developer.ContainerTagItem;
import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.vault.SecretItem;
import org.netbeans.modules.java.lsp.server.explorer.NodeLookupContextValues;
import org.netbeans.modules.java.lsp.server.explorer.api.TreeDataListener;
import org.netbeans.modules.java.lsp.server.explorer.api.TreeDataProvider;
@@ -49,6 +50,7 @@ public class LspAssetsDecorationProvider implements TreeDataProvider.Factory {
public static final String CTXVALUE_PREFIX_IMAGE_URL = "imageUrl:"; // NOI18N
public static final String CTXVALUE_PREFIX_IMAGE_COUNT = "imageCount:"; // NOI18N
public static final String CTXVALUE_PREFIX_REPOSITORY_PUBLIC = "repositoryPublic:"; // NOI18N
+ public static final String CTXVALUE_PREFIX_SECRET_LIFECYCLE_STATE = "lifecycleState:"; // NOI18N
@Override
public synchronized TreeDataProvider createProvider(String treeId) {
@@ -101,6 +103,11 @@ public TreeItemData createDecorations(Node n, boolean expanded) {
d.addContextValues(CTXVALUE_CAP_REFERENCE_NAME);
set = true;
}
+
+ if (item instanceof SecretItem) {
+ d.addContextValues(CTXVALUE_PREFIX_SECRET_LIFECYCLE_STATE + ((SecretItem)item).getLifecycleState());
+ set = true;
+ }
return set ? d : null;
}
diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json
index b30768a2bf1f..f68813bdfba6 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -833,6 +833,10 @@
"command": "nbls.cloud.container.docker",
"when": "false"
},
+ {
+ "command": "nbls:Edit:org.openide.actions.DeleteAction",
+ "when": "false"
+ },
{
"command": "nbls:Database:netbeans.db.explorer.action.Connect",
"when": "false"
@@ -1111,6 +1115,11 @@
"command": "nbls.workspace.configureRunSettings",
"when": "view == run-config && viewItem == configureRunSettings",
"group": "inline@1"
+ },
+ {
+ "command": "nbls:Edit:org.openide.actions.DeleteAction",
+ "when": "viewItem =~ /lifecycleState:ACTIVE*/ || (viewItem =~ /publicIp:.*/ && viewItem =~ /imageUrl:.*/)",
+ "group": "inline@10"
}
]
},
diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts
index 0cc35df8c2a6..ceff0619d560 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -1407,6 +1407,11 @@ function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContex
}
return item;
}
+ const lifecycleState: String = getValueAfterPrefix(item.contextValue, "lifecycleState:");
+ if (lifecycleState) {
+ item.description = lifecycleState === "PENDING_DELETION" ? '(pending deletion)' : undefined;
+ }
+
return item;
}