This is an automated email from the ASF dual-hosted git repository.

jhorvath pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new 7b241cf932 Update OKE ConfigMap with Vault details
     new 894d91bf68 Merge pull request #6746 from jhorvath/vault-to-configmap
7b241cf932 is described below

commit 7b241cf932be7e0f32cb8c397667a83a51291a68
Author: Jan Horvath <[email protected]>
AuthorDate: Fri Nov 24 18:11:37 2023 +0100

    Update OKE ConfigMap with Vault details
---
 enterprise/cloud.oracle/nbproject/project.xml      |   8 +
 .../oracle/actions/AddDbConnectionToVault.java     | 373 +++++++++++++++++----
 .../cloud/oracle/devops/DevopsProjectService.java  |  90 +++++
 .../oracle/actions/AddDbConnectionToVaultTest.java |  83 ++++-
 .../nbcode/integration/LspDevopsConfigFinder.java  |  52 +++
 .../modules/java/lsp/server/LspServerState.java    |   9 +
 .../modules/java/lsp/server/protocol/Server.java   |   7 +
 .../lsp/server/protocol/WorkspaceServiceImpl.java  |  57 ++++
 java/java.lsp.server/vscode/BUILD.md               |   5 +
 9 files changed, 620 insertions(+), 64 deletions(-)

diff --git a/enterprise/cloud.oracle/nbproject/project.xml 
b/enterprise/cloud.oracle/nbproject/project.xml
index 2aee363fd3..c955b26745 100644
--- a/enterprise/cloud.oracle/nbproject/project.xml
+++ b/enterprise/cloud.oracle/nbproject/project.xml
@@ -82,6 +82,14 @@
                         <specification-version>2.18</specification-version>
                     </run-dependency>
                 </dependency>
+                <dependency>
+                    <code-name-base>com.google.gson</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>2.8.9</specification-version>
+                    </run-dependency>
+                </dependency>
                 <dependency>
                     <code-name-base>org.netbeans.modules.db</code-name-base>
                     <build-prerequisite/>
diff --git 
a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/actions/AddDbConnectionToVault.java
 
b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/actions/AddDbConnectionToVault.java
index ee2a170dd8..a5fac3c988 100644
--- 
a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/actions/AddDbConnectionToVault.java
+++ 
b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/actions/AddDbConnectionToVault.java
@@ -18,6 +18,19 @@
  */
 package org.netbeans.modules.cloud.oracle.actions;
 
+import com.oracle.bmc.devops.DevopsClient;
+import com.oracle.bmc.devops.model.DeployArtifactSource;
+import com.oracle.bmc.devops.model.DeployArtifactSummary;
+import com.oracle.bmc.devops.model.InlineDeployArtifactSource;
+import com.oracle.bmc.devops.model.ProjectSummary;
+import com.oracle.bmc.devops.model.UpdateDeployArtifactDetails;
+import com.oracle.bmc.devops.requests.GetDeployArtifactRequest;
+import com.oracle.bmc.devops.requests.ListDeployArtifactsRequest;
+import com.oracle.bmc.devops.requests.ListProjectsRequest;
+import com.oracle.bmc.devops.requests.UpdateDeployArtifactRequest;
+import com.oracle.bmc.devops.responses.GetDeployArtifactResponse;
+import com.oracle.bmc.devops.responses.ListDeployArtifactsResponse;
+import com.oracle.bmc.devops.responses.ListProjectsResponse;
 import com.oracle.bmc.identity.Identity;
 import com.oracle.bmc.identity.IdentityClient;
 import com.oracle.bmc.identity.model.Compartment;
@@ -35,17 +48,18 @@ import com.oracle.bmc.vault.model.UpdateSecretDetails;
 import com.oracle.bmc.vault.requests.CreateSecretRequest;
 import com.oracle.bmc.vault.requests.ListSecretsRequest;
 import com.oracle.bmc.vault.requests.UpdateSecretRequest;
-import com.oracle.bmc.vault.responses.CreateSecretResponse;
 import com.oracle.bmc.vault.responses.ListSecretsResponse;
 import com.oracle.bmc.vault.responses.UpdateSecretResponse;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -56,6 +70,7 @@ import java.util.Set;
 import java.util.TreeMap;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.regex.Matcher;
@@ -67,6 +82,8 @@ import static 
org.netbeans.modules.cloud.oracle.OCIManager.getDefault;
 import org.netbeans.modules.cloud.oracle.OCIProfile;
 import org.netbeans.modules.cloud.oracle.OCISessionInitiator;
 import org.netbeans.modules.cloud.oracle.compartment.CompartmentItem;
+import org.netbeans.modules.cloud.oracle.devops.DevopsProjectItem;
+import org.netbeans.modules.cloud.oracle.devops.DevopsProjectService;
 import org.netbeans.modules.cloud.oracle.items.OCID;
 import org.netbeans.modules.cloud.oracle.items.OCIItem;
 import org.netbeans.modules.cloud.oracle.items.TenancyItem;
@@ -114,7 +131,15 @@ import org.openide.util.Pair;
     "SecretExists=Secrets with name {0} already exists",
     "NoProfile=There is not any OCI profile in the config",
     "NoCompartment=There are no compartments in the Tenancy",
-    "Password=Enter password for Database user {0}"
+    "Password=Enter password for Database user {0}",
+    "NoConfigMap=No ConfigMap found in the Devops project {0}",
+    "SelectDevopsProject=Select Devops Project",
+    "NoDevopsProjects=There are no Devops Projects in selected Compartment",
+    "ConfigmapUpdateFailed=Failed to update ConfigMap",
+    "CreatingSecret=Creating secret {0}",
+    "UpdatingSecret=Updating secret {0}",
+    "UpdatingVault=Updating {0} Vault",
+    "ReadingSecrets=Reading existing Secrets"
 })
 public class AddDbConnectionToVault implements ActionListener {
 
@@ -182,10 +207,11 @@ public class AddDbConnectionToVault implements 
ActionListener {
             return this;
         }
 
-        public void setValue(String selected) {
+        public void setValue(String value) {
             for (OCIProfile profile : profiles) {
-                if (profile.getId().equals(selected)) {
+                if (profile.getId().equals(value)) {
                     profile.getTenancy().ifPresent(t -> this.selected.set(t));
+                    OCIManager.getDefault().setActiveProfile(profile);
                     break;
                 }
             }
@@ -286,7 +312,7 @@ public class AddDbConnectionToVault implements 
ActionListener {
 
         @Override
         public Step getNext() {
-            return new KeyStep().prepare(selected);
+            return new KeyStep().prepare(getValue());
         }
 
         @Override
@@ -297,7 +323,7 @@ public class AddDbConnectionToVault implements 
ActionListener {
         @Override
         public VaultItem getValue() {
             if (onlyOneChoice()) {
-                vaults.values().iterator().next();
+                selected = vaults.values().iterator().next();
             }
             return selected;
         }
@@ -452,7 +478,7 @@ public class AddDbConnectionToVault implements 
ActionListener {
         }
 
     }
-    
+
     class PasswordStep implements Step<Result, Result> {
 
         private Result item;
@@ -468,7 +494,7 @@ public class AddDbConnectionToVault implements 
ActionListener {
 
         @Override
         public NotifyDescriptor createInput() {
-            return new NotifyDescriptor.InputLine("DEFAULT", 
Bundle.Password(context.getUser())); //NOI18N
+            return new NotifyDescriptor.PasswordLine("DEFAULT", 
Bundle.Password(context.getUser())); //NOI18N
         }
 
         @Override
@@ -478,7 +504,7 @@ public class AddDbConnectionToVault implements 
ActionListener {
 
         @Override
         public Step getNext() {
-            return null;
+            return new DevopsStep().prepare(item);
         }
 
         @Override
@@ -491,12 +517,80 @@ public class AddDbConnectionToVault implements 
ActionListener {
             return item;
         }
     }
-    
+
+    class DevopsStep implements Step<Result, Result> {
+
+        private Result item;
+        private Map<String, DevopsProjectItem> devopsProjects;
+
+        @Override
+        public Step<Result, Result> prepare(Result item) {
+            this.item = item;
+            ProgressHandle h = 
ProgressHandle.createHandle(Bundle.MSG_CollectingItems());
+            h.start();
+            h.progress(Bundle.MSG_CollectingItems_Text());
+            try {
+                List<String> devops = 
DevopsProjectService.getDevopsProjectOcid();
+                
+                Map<String, DevopsProjectItem> allProjectsInCompartment = 
getDevopsProjects(item.vault.getCompartmentId());
+                Map<String, DevopsProjectItem> filtered = 
allProjectsInCompartment.entrySet()
+                        .stream()
+                        .filter(e -> 
devops.contains(e.getValue().getKey().getValue()))
+                        .collect(Collectors
+                                .toMap(Entry::getKey, Entry::getValue));
+                if (filtered.size() > 0) {
+                    devopsProjects = filtered;
+                } else {
+                    devopsProjects = allProjectsInCompartment;
+                }
+                if (devopsProjects.size() == 1) {
+                    item.project = devopsProjects.values().iterator().next();
+                }
+                
+            } finally {
+                h.finish();
+            }
+            return this;
+        }
+
+        @Override
+        public NotifyDescriptor createInput() {
+            if (devopsProjects.size() > 1) {
+                return createQuickPick(devopsProjects, 
Bundle.SelectDevopsProject());
+            }
+            if (devopsProjects.isEmpty()) {
+                return new NotifyDescriptor.QuickPick("", 
Bundle.NoDevopsProjects(), Collections.emptyList(), false);
+            }
+            throw new IllegalStateException("No data to create input"); // 
NOI18N
+        }
+
+        @Override
+        public boolean onlyOneChoice() {
+            return devopsProjects.size() == 1;
+        }
+
+        @Override
+        public Step getNext() {
+            return null;
+        }
+
+        @Override
+        public void setValue(String projectName) {
+            item.project = devopsProjects.get(projectName);
+        }
+
+        @Override
+        public Result getValue() {
+            return item;
+        }
+    }
+
     static class Result {
         VaultItem vault;
         KeyItem key;
         String datasourceName;
         String password;
+        DevopsProjectItem project;
         private boolean update;
     }
 
@@ -510,8 +604,9 @@ public class AddDbConnectionToVault implements 
ActionListener {
 
         NotifyDescriptor.ComposedInput.Callback createInput() {
             return new NotifyDescriptor.ComposedInput.Callback() {
+                private int lastNumber = 0;
 
-                private void showInput(Step step, NotifyDescriptor desc) {
+                private void readValue(Step step, NotifyDescriptor desc) {
                     String selected = null;
                     if (!step.onlyOneChoice()) {
                         if (desc instanceof NotifyDescriptor.QuickPick) {
@@ -528,31 +623,34 @@ public class AddDbConnectionToVault implements 
ActionListener {
                     }
                 }
 
-                NotifyDescriptor prepareInput(NotifyDescriptor.ComposedInput 
input, int number) {
+                @Override
+                public NotifyDescriptor 
createInput(NotifyDescriptor.ComposedInput input, int number) {
                     if (number == 1) {
-                        steps.get(0).prepare(null);
-                        return steps.get(0).createInput();
-                    }
-                    if (steps.size() > number) {
+                        while (steps.size() > 1) {
+                            steps.removeLast();
+                        }
+                        steps.getLast().prepare(null);
+                    } else if (lastNumber > number) {
                         steps.removeLast();
+                        while(steps.getLast().onlyOneChoice() && steps.size() 
> 1) {
+                            steps.removeLast();
+                        }
+                        lastNumber = number;
                         return steps.getLast().createInput();
+                    } else {
+                        readValue(steps.getLast(), input.getInputs()[number - 
2]);
+                        steps.add(steps.getLast().getNext());
                     }
-                    showInput(steps.getLast(), input.getInputs()[number - 2]);
-                    Step currentStep = steps.getLast().getNext();
-                    if (currentStep == null) {
-                        return null;
+                    lastNumber = number;
+                    
+                    while(steps.getLast() != null && 
steps.getLast().onlyOneChoice()) {
+                        steps.add(steps.getLast().getNext());
                     }
-
-                    steps.add(currentStep);
-                    if (currentStep.onlyOneChoice()) {
-                        return prepareInput(input, number);
+                    if (steps.getLast() == null) {
+                        steps.removeLast();
+                        return null;
                     }
-                    return currentStep.createInput();
-                }
-
-                @Override
-                public NotifyDescriptor 
createInput(NotifyDescriptor.ComposedInput input, int number) {
-                    return prepareInput(input, number);
+                    return steps.getLast().createInput();
                 }
             };
         }
@@ -576,29 +674,33 @@ public class AddDbConnectionToVault implements 
ActionListener {
     }
 
     private void addDbConnectionToVault(Result item) {
-        VaultsClient client = 
VaultsClient.builder().build(getDefault().getActiveProfile().getConfigProvider());
+        ProgressHandle h = 
ProgressHandle.createHandle(Bundle.UpdatingVault(item.vault.getName()));
+        h.start();
+        h.progress(Bundle.ReadingSecrets());
+           
+        try {
+            VaultsClient client = 
VaultsClient.builder().build(getDefault().getActiveProfile().getConfigProvider());
 
-        ListSecretsRequest listSecretsRequest = ListSecretsRequest.builder()
-                .compartmentId(item.vault.getCompartmentId())
-                .vaultId(item.vault.getKey().getValue())
-                .limit(88)
-                .build();
+            ListSecretsRequest listSecretsRequest = 
ListSecretsRequest.builder()
+                    .compartmentId(item.vault.getCompartmentId())
+                    .vaultId(item.vault.getKey().getValue())
+                    .limit(88)
+                    .build();
 
-        ListSecretsResponse secrets = client.listSecrets(listSecretsRequest);
+            ListSecretsResponse secrets = 
client.listSecrets(listSecretsRequest);
 
         Map<String, String> existingSecrets = secrets.getItems().stream()
                 .collect(Collectors.toMap(s -> s.getSecretName(), s -> 
s.getId()));
 
-        Map<String, String> values = new HashMap<String, String>() {
-            {
-                put("Username", context.getUser()); //NOI18N
-                put("Password", item.password); //NOI18N
-                put("OCID", (String) 
context.getConnectionProperties().get("OCID")); //NOI18N
-                put("wallet_Password", UUID.randomUUID().toString()); //NOI18N
-            }
-        };
+            Map<String, String> values = new HashMap<String, String>() {
+                {
+                    put("Username", context.getUser()); //NOI18N
+                    put("Password", item.password); //NOI18N
+                    put("OCID", (String) 
context.getConnectionProperties().get("OCID")); //NOI18N
+                    put("wallet_Password", UUID.randomUUID().toString()); 
//NOI18N
+                }
+            };
 
-        try {
             for (Entry<String, String> entry : values.entrySet()) {
                 String secretName = "DATASOURCES_" + item.datasourceName + "_" 
+ entry.getKey().toUpperCase(); //NOI18N
                 String base64Content = 
Base64.getEncoder().encodeToString(entry.getValue().getBytes(StandardCharsets.UTF_8));
@@ -607,6 +709,7 @@ public class AddDbConnectionToVault implements 
ActionListener {
                         .content(base64Content)
                         .stage(SecretContentDetails.Stage.Current).build();
                 if (existingSecrets.containsKey(secretName)) {
+                    h.progress(Bundle.UpdatingSecret(secretName));
                     UpdateSecretDetails updateSecretDetails = 
UpdateSecretDetails.builder()
                             .secretContent(contentDetails)
                             .build();
@@ -614,8 +717,14 @@ public class AddDbConnectionToVault implements 
ActionListener {
                             .secretId(existingSecrets.get(secretName))
                             .updateSecretDetails(updateSecretDetails)
                             .build();
-                    UpdateSecretResponse response = 
client.updateSecret(request);
+                    try { 
+                        UpdateSecretResponse response = 
client.updateSecret(request);
+                    } catch (BmcException ex) {
+                        // Update fails if the new value is same as the 
current one. It is safe to ignore
+                        LOG.log(Level.WARNING, "Update of secret failed", ex);
+                    }
                 } else {
+                    h.progress(Bundle.CreatingSecret(secretName));
                     CreateSecretDetails createDetails = 
CreateSecretDetails.builder()
                             .secretName(secretName)
                             .secretContent(contentDetails)
@@ -629,17 +738,152 @@ public class AddDbConnectionToVault implements 
ActionListener {
                             .builder()
                             .createSecretDetails(createDetails)
                             .build();
-                    CreateSecretResponse response = 
client.createSecret(request);
+                    client.createSecret(request);
                 }
             }
 
-        } catch (BmcException e) {
-            NotifyDescriptor.Message msg = new 
NotifyDescriptor.Message(e.getMessage());
+            // Add Vault to the ConfigMap artifact
+            DevopsClient devopsClient = 
DevopsClient.builder().build(OCIManager.getDefault().getActiveProfile().getConfigProvider());
+            ListDeployArtifactsRequest request = 
ListDeployArtifactsRequest.builder()
+                    .projectId(item.project.getKey().getValue()).build();
+            ListDeployArtifactsResponse response = 
devopsClient.listDeployArtifacts(request);
+            List<DeployArtifactSummary> artifacts = 
response.getDeployArtifactCollection().getItems();
+            boolean found = false;
+            for (DeployArtifactSummary artifact : artifacts) {
+                if ((item.project.getName() + 
"_oke_configmap").equals(artifact.getDisplayName())) { //NOI18N
+                    h.progress("updating  " + item.project.getName() + 
"_oke_configmap"); //NOI18N
+                    found = true;
+                    GetDeployArtifactRequest artRequest = 
GetDeployArtifactRequest.builder().deployArtifactId(artifact.getId()).build();
+                    GetDeployArtifactResponse artResponse = 
devopsClient.getDeployArtifact(artRequest);
+                    DeployArtifactSource source = 
artResponse.getDeployArtifact().getDeployArtifactSource();
+                    if (source instanceof InlineDeployArtifactSource) {
+                        byte[] content = ((InlineDeployArtifactSource) 
source).getBase64EncodedContent();
+                        String srcString = updateProperties(new 
String(content, StandardCharsets.UTF_8),
+                                item.vault.getCompartmentId(), 
item.vault.getKey().getValue(), item.datasourceName);
+                        byte[] base64Content = 
Base64.getEncoder().encode(srcString.getBytes(StandardCharsets.UTF_8));
+                        DeployArtifactSource updatedSource = 
InlineDeployArtifactSource.builder()
+                                .base64EncodedContent(base64Content).build();
+                        UpdateDeployArtifactDetails updateArtifactDetails = 
UpdateDeployArtifactDetails.builder()
+                                .deployArtifactSource(updatedSource)
+                                .build();
+                        UpdateDeployArtifactRequest updateArtifactRequest = 
UpdateDeployArtifactRequest.builder()
+                                
.updateDeployArtifactDetails(updateArtifactDetails)
+                                .deployArtifactId(artifact.getId())
+                                .build();
+                        
devopsClient.updateDeployArtifact(updateArtifactRequest);
+                    }
+                }
+            }
+            if (!found) {
+                NotifyDescriptor.Message msg = new 
NotifyDescriptor.Message(Bundle.NoConfigMap(item.project.getName()), 
NotifyDescriptor.WARNING_MESSAGE);
+                DialogDisplayer.getDefault().notify(msg);
+            }
+            NotifyDescriptor.Message msg = new 
NotifyDescriptor.Message(Bundle.SecretsCreated());
             DialogDisplayer.getDefault().notify(msg);
-            throw new RuntimeException(e);
+        } catch(ThreadDeath e) {
+            throw e;
+        } catch (Throwable e) {
+            h.finish();
+            NotifyDescriptor.Message msg = new 
NotifyDescriptor.Message(e.getMessage(), NotifyDescriptor.WARNING_MESSAGE);
+            DialogDisplayer.getDefault().notify(msg);
+        } finally {
+            h.finish();
+        }
+    }
+
+    protected static String updateProperties(String configmap, String 
compartmentOcid, String vaultOcid, String datasourceName) {
+        StringWriter output = new StringWriter();
+        String[] lines = configmap.split("\n");
+        int previousIndent = 0;
+        Map<Integer, String> path = new LinkedHashMap<>();
+        String propertiesName = null;
+        Map<String, String> properties = new LinkedHashMap<>();
+        for (int i = 0; i < lines.length; i++) {
+            String line = lines[i];
+            if (line.trim().startsWith("#") || line.isEmpty()) {
+                output.append(line);
+                output.append("\n");
+                continue;
+            }
+            int indent = 0;
+            while (line.charAt(indent) == ' ') {
+                indent++;
+            }
+            if (previousIndent > indent || (propertiesName != null && 
!line.contains("="))) {
+                final int f = indent;
+                path.entrySet().removeIf(entry -> entry.getKey() >= f);
+                if (propertiesName != null) {
+                    int propIndent = previousIndent;
+                    if (properties.size() == 0) {
+                        propIndent = indent + 2;
+                    }
+                    output.append(
+                            formatProperties(propertiesName, properties, 
propIndent, compartmentOcid, vaultOcid, datasourceName));
+
+                    properties.clear();
+                }
+                propertiesName = null;
+                if (line.trim().equals("---")) { //NOI18N
+                    output.append(line);
+                    output.append("\n");
+                    continue;
+                }
+            }
+            if (propertiesName == null) {
+                if (line.indexOf(':') < 0) {
+                    throw new IllegalStateException("Invalid ConfigMap 
format"); //NOI18N
+                }
+                String k = line.substring(0, line.indexOf(':')).trim();
+                String v = line.substring(line.indexOf(':') + 1).trim();
+                if (k == null) {
+                    throw new IllegalStateException();
+                }
+
+                path.put(indent, k);
+                output.append(line);
+                output.append("\n");
+                if (v.trim().equals("|")) {
+                    propertiesName = k;
+                    continue;
+                }
+            }
+            if (propertiesName != null && line.contains("=")) {
+                properties.put(line.substring(0, line.indexOf('=')).trim(),
+                        line.substring(line.indexOf('=') + 1).trim());
+            }
+
+            previousIndent = indent;
         }
-        NotifyDescriptor.Message msg = new 
NotifyDescriptor.Message(Bundle.SecretsCreated());
-        DialogDisplayer.getDefault().notify(msg);
+        output.append(
+                formatProperties(propertiesName, properties, previousIndent, 
compartmentOcid, vaultOcid, datasourceName));
+
+        return output.toString();
+    }
+
+    private static String formatProperties(String proprtiesName, Map<String, 
String> prop, int indent, String compartmentId, String vaultId, String 
datasourceName) {
+        StringBuilder output = new StringBuilder();
+        if (proprtiesName.startsWith("bootstrap")) { // NOI18N
+            prop.entrySet().removeIf(entry -> ((String) 
entry.getKey()).startsWith("oci.vault.vaults")); // NOI18N
+            prop.put("oci.config.instance-principal.enabled", "true"); // 
NOI18N
+            prop.put("micronaut.config-client.enabled", "true"); // NOI18N
+            prop.put("oci.vault.config.enabled", "true"); // NOI18N
+            prop.put("oci.vault.vaults[0].ocid", vaultId); // NOI18N
+            prop.put("oci.vault.vaults[0].compartment-ocid", compartmentId); 
// NOI18N
+        } else if (proprtiesName.startsWith("application")) { // NOI18N
+            prop.put("datasources.default.dialect", "ORACLE"); // NOI18N
+            prop.put("datasources.default.ocid", "${DATASOURCES_" + 
datasourceName + "_OCID}"); // NOI18N
+            prop.put("datasources.default.walletPassword", "${DATASOURCES_" + 
datasourceName + "_WALLET_PASSWORD}"); // NOI18N
+            prop.put("datasources.default.username", "${DATASOURCES_" + 
datasourceName + "_USERNAME}"); // NOI18N
+            prop.put("datasources.default.password", "${DATASOURCES_" + 
datasourceName + "_PASSWORD}"); // NOI18N
+        }
+        for (Entry<String, String> entry : prop.entrySet()) {
+            output.append(new String(new char[indent]).replace('\0', ' '));
+            output.append(entry.getKey());
+            output.append("=");
+            output.append(entry.getValue());
+            output.append("\n");
+        }
+        return output.toString();
     }
 
     private static <T extends OCIItem> NotifyDescriptor.QuickPick 
createQuickPick(Map<String, T> ociItems, String title) {
@@ -722,6 +966,23 @@ public class AddDbConnectionToVault implements 
ActionListener {
         abstract FlatCompartmentItem getItem(OCID compId);
     }
 
+    protected static Map<String, DevopsProjectItem> getDevopsProjects(String 
compartmentId) {
+        try (DevopsClient client = new 
DevopsClient(OCIManager.getDefault().getConfigProvider());) {
+            ListProjectsRequest request = 
ListProjectsRequest.builder().compartmentId(compartmentId).build();
+            ListProjectsResponse response = client.listProjects(request);
+
+            List<ProjectSummary> projects = 
response.getProjectCollection().getItems();
+            for (ProjectSummary project : projects) {
+                project.getNotificationConfig().getTopicId();
+
+            }
+            return projects.stream()
+                    .map(p -> new DevopsProjectItem(OCID.of(p.getId(), 
"DevopsProject"), // NOI18N
+                    p.getName()))
+                    .collect(Collectors.toMap(DevopsProjectItem::getName, 
Function.identity()));
+        }
+    }
+
     protected static Map<String, VaultItem> getVaults(OCIItem parent) {
         Map<String, VaultItem> items = new HashMap<>();
         try {
@@ -738,15 +999,15 @@ public class AddDbConnectionToVault implements 
ActionListener {
         Map<String, KeyItem> items = new HashMap<>();
         try {
             if (parent instanceof VaultItem) {
-                KeyNode.getKeys().apply((VaultItem) parent).forEach((db) -> 
items.put(db.getName(), db));
+                KeyNode.getKeys().apply((VaultItem) parent).forEach(key -> 
items.put(key.getName(), key));
             }
         } catch (BmcException e) {
-            LOG.log(Level.SEVERE, "Unable to load vault list", e); //NOI18N
+            LOG.log(Level.SEVERE, "Unable to load key list", e); //NOI18N
         }
         return items;
     }
 
-    static Pattern p = Pattern.compile("[A-Z]*_([A-Z]*)_[A-Z]*"); //NOI18N
+    static Pattern p = Pattern.compile("[A-Z]*_([a-zA-Z0-9]*)_[A-Z]*"); 
//NOI18N
 
     protected static String extractDatasourceName(String value) {
         Matcher m = p.matcher(value);
diff --git 
a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/devops/DevopsProjectService.java
 
b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/devops/DevopsProjectService.java
new file mode 100644
index 0000000000..77c0cf85e5
--- /dev/null
+++ 
b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/devops/DevopsProjectService.java
@@ -0,0 +1,90 @@
+/*
+ * 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.devops;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.io.FileNotFoundException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.openide.filesystems.FileObject;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+
+/**
+ * Finds OCI DevOps configuration if available.
+ * 
+ * @author jhorvath
+ */
+public class DevopsProjectService {
+    private static DevopsConfigFinder finder = null;
+    
+    
+    public static List<String> getDevopsProjectOcid() {
+        List<FileObject> configs = getDefaultFinder().findDevopsConfig();
+        Gson gson = new Gson();
+        List<String> devopsOcid = new ArrayList<> ();
+        try {
+            for (FileObject config : configs) {
+                JsonObject json = gson.fromJson(new 
InputStreamReader(configs.get(0).getInputStream()), JsonObject.class);
+                JsonArray services = json.getAsJsonArray("cloudServices"); 
//NOI18N
+                for (JsonElement service : services) {
+                    JsonElement type = service.getAsJsonObject().get("type");
+                    if (type != null && "oci".equals(type.getAsString())) { 
//NOI18N
+                        JsonObject data = 
service.getAsJsonObject().getAsJsonObject("data"); //NOI18N
+                        JsonObject context = data.getAsJsonObject("context"); 
//NOI18N
+                        
devopsOcid.add(context.get("devopsProject").getAsString()); //NOI18N
+                    }
+                }
+            }
+        } catch (FileNotFoundException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+        return devopsOcid;
+    }
+    
+    
+    private static DevopsConfigFinder getDefaultFinder() {
+        if (finder == null) {
+            finder = Lookup.getDefault().lookup(DevopsConfigFinder.class);
+        }
+        if (finder == null) {
+            finder = new DefaultDevopsConfigFinder();
+        }
+        return finder;
+    }
+    
+    public interface DevopsConfigFinder {
+        List<FileObject> findDevopsConfig();
+    }
+
+    static class DefaultDevopsConfigFinder implements DevopsConfigFinder {
+
+        @Override
+        public List<FileObject> findDevopsConfig() {
+            return Collections.emptyList();
+        }
+        
+    }
+    
+}
diff --git 
a/enterprise/cloud.oracle/test/unit/src/org/netbeans/modules/cloud/oracle/actions/AddDbConnectionToVaultTest.java
 
b/enterprise/cloud.oracle/test/unit/src/org/netbeans/modules/cloud/oracle/actions/AddDbConnectionToVaultTest.java
index cc9ccd1db4..9f79e00d2d 100644
--- 
a/enterprise/cloud.oracle/test/unit/src/org/netbeans/modules/cloud/oracle/actions/AddDbConnectionToVaultTest.java
+++ 
b/enterprise/cloud.oracle/test/unit/src/org/netbeans/modules/cloud/oracle/actions/AddDbConnectionToVaultTest.java
@@ -18,21 +18,16 @@
  */
 package org.netbeans.modules.cloud.oracle.actions;
 
-import java.awt.event.ActionEvent;
-import java.util.Map;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import static org.junit.Assert.*;
-import org.netbeans.modules.cloud.oracle.items.OCIItem;
-import org.netbeans.modules.cloud.oracle.vault.KeyItem;
-import org.netbeans.modules.cloud.oracle.vault.VaultItem;
 
 /**
  *
- * @author honza
+ * @author jhorvath
  */
 public class AddDbConnectionToVaultTest {
     
@@ -56,7 +51,7 @@ public class AddDbConnectionToVaultTest {
     }
 
     /**
-     * Test of actionPerformed method, of class AddDbConnectionToVault.
+     * Test of extractDatasourceName method, of class AddDbConnectionToVault.
      */
     @Test
     public void datasourceName() {
@@ -64,7 +59,79 @@ public class AddDbConnectionToVaultTest {
         String ds = AddDbConnectionToVault.extractDatasourceName(input);
         assertEquals("DEFAULT", ds);
         
+        input = "DATASOURCES_DEF3_USERNAME";
+        ds = AddDbConnectionToVault.extractDatasourceName(input);
+        assertEquals("DEF3", ds);
+    }
+    
+    @Test
+    public void testConfigMap() {
+        String cm = "apiVersion: v1\n" +
+            "kind: ConfigMap\n" +
+            "metadata:\n" +
+            "  name: demo-adb-vault\n" +
+            "data:\n" +
+            "  bootstrap-oraclecloud.properties: |\n" +
+            "    oci.config.instance-principal.enabled=false\n" +
+            "    micronaut.config-client.enabled=fase\n" +
+            "    oci.vault.config.enabled=false\n" +
+            "    oci.vault.vaults[0].ocid=xxxx\n" +
+            "    oci.vault.vaults[0].compartment-ocid=xxx\n" +
+            "  application-oraclecloud.properties: |\n" +
+            "    a=b";
+        String expected = "apiVersion: v1\n" +
+            "kind: ConfigMap\n" +
+            "metadata:\n" +
+            "  name: demo-adb-vault\n" +
+            "data:\n" +
+            "  bootstrap-oraclecloud.properties: |\n" +
+            "    oci.config.instance-principal.enabled=true\n" +
+            "    micronaut.config-client.enabled=true\n" +
+            "    oci.vault.config.enabled=true\n" +
+            "    oci.vault.vaults[0].ocid=cde\n" +
+            "    oci.vault.vaults[0].compartment-ocid=abc\n" +
+            "  application-oraclecloud.properties: |\n" +
+            "    a=b\n" +
+            "    datasources.default.dialect=ORACLE\n" +
+            "    datasources.default.ocid=${DATASOURCES_DEFAULT_OCID}\n" +
+            "    
datasources.default.walletPassword=${DATASOURCES_DEFAULT_WALLET_PASSWORD}\n" +
+            "    
datasources.default.username=${DATASOURCES_DEFAULT_USERNAME}\n" +
+            "    
datasources.default.password=${DATASOURCES_DEFAULT_PASSWORD}\n";
+        String result = AddDbConnectionToVault.updateProperties(cm, "abc", 
"cde", "DEFAULT");
+        assertEquals(expected, result);
     }
-
     
+    @Test
+    public void testConfigMap1() {
+        String cm = "apiVersion: v1\n" +
+            "kind: ConfigMap\n" +
+            "metadata:\n" +
+            "  name: demo-adb-vault\n" +
+            "data:\n" +
+            "  bootstrap-oraclecloud.properties: |\n" +
+            "    # placeholder\n" +
+            "  application-oraclecloud.properties: |\n" +
+            "    a=b";
+        String expected = "apiVersion: v1\n" +
+            "kind: ConfigMap\n" +
+            "metadata:\n" +
+            "  name: demo-adb-vault\n" +
+            "data:\n" +
+            "  bootstrap-oraclecloud.properties: |\n" +
+            "    # placeholder\n" +
+            "    oci.config.instance-principal.enabled=true\n" +
+            "    micronaut.config-client.enabled=true\n" +
+            "    oci.vault.config.enabled=true\n" +
+            "    oci.vault.vaults[0].ocid=cde\n" +
+            "    oci.vault.vaults[0].compartment-ocid=abc\n" +
+            "  application-oraclecloud.properties: |\n" +
+            "    a=b\n" +
+            "    datasources.default.dialect=ORACLE\n" +
+            "    datasources.default.ocid=${DATASOURCES_ABC_OCID}\n" +
+            "    
datasources.default.walletPassword=${DATASOURCES_ABC_WALLET_PASSWORD}\n" +
+            "    datasources.default.username=${DATASOURCES_ABC_USERNAME}\n" +
+            "    datasources.default.password=${DATASOURCES_ABC_PASSWORD}\n";
+        String result = AddDbConnectionToVault.updateProperties(cm, "abc", 
"cde", "ABC");
+        assertEquals(expected, result);
+    }
 }
diff --git 
a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspDevopsConfigFinder.java
 
b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspDevopsConfigFinder.java
new file mode 100644
index 0000000000..fd2033e55c
--- /dev/null
+++ 
b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspDevopsConfigFinder.java
@@ -0,0 +1,52 @@
+/*
+ * 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.nbcode.integration;
+
+import java.util.ArrayList;
+import java.util.List;
+import 
org.netbeans.modules.cloud.oracle.devops.DevopsProjectService.DevopsConfigFinder;
+import org.netbeans.modules.java.lsp.server.LspServerState;
+import org.openide.filesystems.FileObject;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author jhorvath
+ */
+@ServiceProvider(service = DevopsConfigFinder.class, position = 1000)
+public class LspDevopsConfigFinder implements DevopsConfigFinder {
+
+    @Override
+    public List<FileObject> findDevopsConfig() {
+        List<FileObject> result = new ArrayList<> ();
+        LspServerState serverState = 
Lookup.getDefault().lookup(LspServerState.class);
+        if (serverState != null) {
+            List<FileObject> folders = serverState.getClientWorkspaceFolders();
+            for (FileObject folder : folders) {
+                FileObject f = folder.getFileObject(".vscode/devops.json"); 
//NOI18N
+                if (f != null && f.isValid()) {
+                    result.add(f);
+                }
+            }
+        }
+        return result;
+    }
+    
+}
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerState.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerState.java
index 2fcd7a3546..593767b674 100644
--- 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerState.java
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerState.java
@@ -100,4 +100,13 @@ public interface LspServerState {
      * @return snapshot of folders.
      */
     public List<FileObject> getAcceptedWorkspaceFolders();
+    
+    /**
+     * Returns the set of workspace folders reported by the client. If a 
folder from the list is recognized
+     * as a project, it will be also present in {@link #openedProjects()} 
including all its subprojects.
+     * The list of client workspace folders contains just toplevel items in 
client's workspace, as defined in
+     * LSP protocol.
+     * @return list of workspace folders
+     */
+    public List<FileObject> getClientWorkspaceFolders();
 }
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
index b427a84993..1674055e8f 100644
--- 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
@@ -922,6 +922,8 @@ public final class Server {
 
             initializeOptions();
 
+            
workspaceService.setClientWorkspaceFolders(init.getWorkspaceFolders());
+
             // but complete the InitializationRequest independently of the 
project initialization.
             return CompletableFuture.completedFuture(
                     finishInitialization(
@@ -1038,6 +1040,11 @@ public final class Server {
             // no op: there's already a lot of noise in the log, and the 
console log
             // can be controlled by a commandline parameter to the NBLS.
         }
+
+        @Override
+        public List<FileObject> getClientWorkspaceFolders() {
+            return workspaceService.getClientWorkspaceFolders();
+        }
     }
 
     public static final String NBLS_BUILD_WORKSPACE =  "nbls.build.workspace";
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
index e337829640..0394de153e 100644
--- 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
@@ -169,10 +169,42 @@ public final class WorkspaceServiceImpl implements 
WorkspaceService, LanguageCli
     private final LspServerState server;
     private NbCodeLanguageClient client;
 
+    /**
+     * List of workspace folders as reported by the client. Initialized in 
`initialize` request,
+     * and then updated by didChangeWorkspaceFolder notifications.
+     */
+    private volatile List<FileObject> clientWorkspaceFolders = 
Collections.emptyList();
+
     WorkspaceServiceImpl(LspServerState server) {
         this.server = server;
     }
 
+    /**
+     * Returns the set of workspace folders reported by the client. If a 
folder from the list is recognized
+     * as a project, it will be also present in {@link #openedProjects()} 
including all its subprojects.
+     * The list of client workspace folders contains just toplevel items in 
client's workspace, as defined in
+     * LSP protocol.
+     * @return list of workspace folders
+     */
+    public List<FileObject> getClientWorkspaceFolders() {
+        return new ArrayList<>(clientWorkspaceFolders);
+    }
+
+    public void setClientWorkspaceFolders(List<WorkspaceFolder> 
clientWorkspaceFolders) {
+        if (clientWorkspaceFolders == null) {
+            return;
+        }
+        List<FileObject> newWorkspaceFolders = new 
ArrayList<>(this.clientWorkspaceFolders);
+        try {
+            for (WorkspaceFolder clientWorkspaceFolder : 
clientWorkspaceFolders) {
+                
newWorkspaceFolders.add(Utils.fromUri(clientWorkspaceFolder.getUri()));
+            }
+            this.clientWorkspaceFolders = newWorkspaceFolders;
+        } catch (MalformedURLException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+    
     @Override
     public CompletableFuture<Object> executeCommand(ExecuteCommandParams 
params) {
         String command = Utils.decodeCommand(params.getCommand(), 
client.getNbCodeCapabilities());
@@ -1368,6 +1400,8 @@ public final class WorkspaceServiceImpl implements 
WorkspaceService, LanguageCli
     })
     @Override
     public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams 
params) {
+        // the client > server notification stream is sequential
+        List<FileObject> newWorkspaceFolders = new 
ArrayList<>(this.clientWorkspaceFolders);
         List<FileObject> refreshProjectFolders = new ArrayList<>();
         for (WorkspaceFolder wkspFolder : params.getEvent().getAdded()) {
             String uri = wkspFolder.getUri();
@@ -1375,12 +1409,35 @@ public final class WorkspaceServiceImpl implements 
WorkspaceService, LanguageCli
                 FileObject f = Utils.fromUri(uri);
                 if (f != null) {
                     refreshProjectFolders.add(f);
+                    // avoid duplicates
+                    if (!newWorkspaceFolders.contains(f)) {
+                        LOG.log(Level.FINE, "Adding client workspace folder 
{0}", f);
+                        newWorkspaceFolders.add(f);
+                    }
                 }
             } catch (MalformedURLException ex) {
                 // expected, perhaps some client-specific URL scheme ?
                 LOG.fine("Workspace folder URI could not be converted into 
fileobject: {0}");
             }
         }
+        
+        if (params.getEvent().getRemoved() != null) {
+            for (WorkspaceFolder wsf : params.getEvent().getRemoved()) {
+                String uri = wsf.getUri();
+                try {
+                    FileObject f = Utils.fromUri(uri);
+                    if (f != null) {
+                        LOG.log(Level.FINE, "Removing client workspace folder 
{0}", f);
+                        newWorkspaceFolders.remove(f);
+                    }
+                } catch (MalformedURLException ex) {
+                    // was never added 
+                }
+            }
+        }
+        // the client > server notification stream is sequential; no need to 
sync
+        this.clientWorkspaceFolders = newWorkspaceFolders;
+        
         if (!refreshProjectFolders.isEmpty()) {
             server.asyncOpenSelectedProjects(refreshProjectFolders, 
true).thenAccept((projects) -> {
                 // report initialization of a project / projects
diff --git a/java/java.lsp.server/vscode/BUILD.md 
b/java/java.lsp.server/vscode/BUILD.md
index dcdca1d898..95611cc484 100644
--- a/java/java.lsp.server/vscode/BUILD.md
+++ b/java/java.lsp.server/vscode/BUILD.md
@@ -131,6 +131,11 @@ and specify suitable debug arguments to start _standalone 
NBLS_ instance:
 vscode$ npm run nbcode -- --jdkhome /jdk 
-J-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
 ```
 
+To add extra modules while debugging the NetBeans part
+```bash
+vscode$ npm run nbcode -- 
-J-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000 
-J-Dnetbeans.extra.dirs=/path/to/extension
+```
+
 Connect to the process with Java debugger, setup all breakpoints. Then launch
 the VS Code extension (which connects to the already running _standalone NBLS_ 
Java process):
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

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


Reply via email to