From 957334c19228380d5510006242f31f26c32ce391 Mon Sep 17 00:00:00 2001 From: Dusan Petrovic Date: Thu, 22 Aug 2024 13:33:36 +0200 Subject: [PATCH] Copy application config file before docker run --- .../oracle/assets/ConfigFileGenerator.java | 206 ++++++++++++++++++ .../oracle/assets/ConfigFileProvider.java | 95 ++++++++ .../oracle/assets/OCIPropertiesProvider.java | 139 +----------- .../oracle/steps/DatabaseConnectionStep.java | 5 +- java/java.lsp.server/vscode/src/extension.ts | 31 ++- 5 files changed, 338 insertions(+), 138 deletions(-) create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/ConfigFileGenerator.java create mode 100644 enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/ConfigFileProvider.java diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/ConfigFileGenerator.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/ConfigFileGenerator.java new file mode 100644 index 000000000000..d9d757fb9412 --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/ConfigFileGenerator.java @@ -0,0 +1,206 @@ +/* + * 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; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.attribute.AclEntry; +import java.nio.file.attribute.AclEntryPermission; +import static java.nio.file.attribute.AclEntryPermission.APPEND_DATA; +import static java.nio.file.attribute.AclEntryPermission.READ_ACL; +import static java.nio.file.attribute.AclEntryPermission.READ_ATTRIBUTES; +import static java.nio.file.attribute.AclEntryPermission.READ_DATA; +import static java.nio.file.attribute.AclEntryPermission.READ_NAMED_ATTRS; +import static java.nio.file.attribute.AclEntryPermission.SYNCHRONIZE; +import static java.nio.file.attribute.AclEntryPermission.WRITE_ACL; +import static java.nio.file.attribute.AclEntryPermission.WRITE_ATTRIBUTES; +import static java.nio.file.attribute.AclEntryPermission.WRITE_DATA; +import static java.nio.file.attribute.AclEntryPermission.WRITE_NAMED_ATTRS; +import java.nio.file.attribute.AclFileAttributeView; +import java.nio.file.attribute.DosFileAttributeView; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFilePermission; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.openide.modules.Places; + +/** + * + * @author Dusan Petrovic + */ +public class ConfigFileGenerator { + + private static final Logger LOG = Logger.getLogger(ConfigFileGenerator.class.getName()); + + private static final boolean POSIX = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); // NOI18N + private static final EnumSet readWritePosix = EnumSet.of(OWNER_READ, OWNER_WRITE); + private static final EnumSet readPosix = EnumSet.of(OWNER_READ); + + private static final EnumSet readOnlyAcl = EnumSet.of( + READ_ACL, + READ_ATTRIBUTES, + READ_DATA, + READ_NAMED_ATTRS, + SYNCHRONIZE + ); + + private static final EnumSet readWriteAcl = EnumSet.of( + READ_DATA, + WRITE_DATA, + APPEND_DATA, + READ_NAMED_ATTRS, + WRITE_NAMED_ATTRS, + READ_ATTRIBUTES, + WRITE_ATTRIBUTES, + READ_ACL, + WRITE_ACL + ); + + private final boolean readOnly; + private final String filePrefix; + private final String fileSufix; + private final String configPath; + + public ConfigFileGenerator(String filePrefix, String fileSufix, String configPath, boolean readOnly) { + this.readOnly = readOnly; + this.configPath = configPath; + this.filePrefix = filePrefix; + this.fileSufix = fileSufix; + } + + public Path writePropertiesFile(Properties props) throws IOException { + Path temp = null; + try { + temp = generateConfigFile(); + writePropertiesToFile(props, temp); + } catch (IOException ex) { + deleteTempFile(temp); + throw ex; + } + + return temp; + } + + private Path generateConfigFile() throws IOException { + Path dir = generateDirPath(); + + if (!Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS)) { + Files.createDirectory(dir); + } + if (POSIX) { + return createFilePosix(dir); + } + + Path temp = Files.createTempFile(dir, this.filePrefix, this.fileSufix); + setFileOwnerAcl(temp, readWriteAcl); + return temp; + } + + private void writePropertiesToFile(Properties props, Path filePath) throws IOException { + try (Writer writer = new FileWriter(filePath.toFile(), Charset.defaultCharset());) { + props.store(writer, ""); + if (POSIX) { + setFilePermissionPosix(filePath); + } else { + if (readOnly) { + DosFileAttributeView attribs = Files.getFileAttributeView(filePath, DosFileAttributeView.class); + attribs.setReadOnly(true); + setFileOwnerAcl(filePath, readOnlyAcl); + } else { + setFileOwnerAcl(filePath, readWriteAcl); + } + } + filePath.toFile().deleteOnExit(); + } + } + + private void setFileOwnerAcl(Path filePath, Set permissions) throws IOException { + AclFileAttributeView acl = Files.getFileAttributeView(filePath, AclFileAttributeView.class); + AclEntry ownerEntry = findFileOwner(acl); + + if (ownerEntry == null) { + throw new IOException("Owner missing, file:" + filePath.toString()); // NOI18N + } + + AclEntry ownerAcl = AclEntry.newBuilder(ownerEntry).setPermissions(permissions).build(); + acl.setAcl(Collections.singletonList(ownerAcl)); + } + + private AclEntry findFileOwner(AclFileAttributeView acl) throws IOException { + for(AclEntry e : acl.getAcl()) { + if (e.principal().equals(acl.getOwner())) { + return e; + } + } + return null; + } + + private void setFilePermissionPosix(Path temp) throws IOException { + PosixFileAttributeView attributes = Files.getFileAttributeView(temp, PosixFileAttributeView.class); + if (this.readOnly) { + attributes.setPermissions(readPosix); + } else { + attributes.setPermissions(readWritePosix); + } + } + + + private Path createFilePosix(Path dir) throws IOException { + FileAttribute readWriteAttribs = PosixFilePermissions.asFileAttribute(readWritePosix); + return Files.createTempFile(dir,this.filePrefix, this.fileSufix, readWriteAttribs); + } + + private Path generateDirPath() { + File file = Places.getCacheSubdirectory(this.configPath); + file.deleteOnExit(); + return file.toPath(); + } + + private void deleteTempFile(Path temp) { + if (temp != null && Files.isRegularFile(temp, LinkOption.NOFOLLOW_LINKS)) { + try { + if (POSIX) { + PosixFileAttributeView attribs = Files.getFileAttributeView(temp, PosixFileAttributeView.class); + attribs.setPermissions(readWritePosix); + } else { + DosFileAttributeView attribs = Files.getFileAttributeView(temp, DosFileAttributeView.class); + attribs.setReadOnly(false); + } + Files.delete(temp); + } catch (IOException ex) { + LOG.log(Level.WARNING, "deleteTempFile", ex); + } + } + } +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/ConfigFileProvider.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/ConfigFileProvider.java new file mode 100644 index 000000000000..f04e4edf6fba --- /dev/null +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/ConfigFileProvider.java @@ -0,0 +1,95 @@ +/* + * 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; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import org.netbeans.spi.lsp.CommandProvider; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author Dusan Petrovic + */ +@ServiceProvider(service = CommandProvider.class) +public class ConfigFileProvider implements CommandProvider { + private static final String GET_CONFIG_FILE_PATH = "nbls.config.file.path"; //NOI18N + + private final ConfigFileGenerator applicationPropertiesFileGenerator; + private final ConfigFileGenerator bootstrapPropertiesFileGenerator; + + public ConfigFileProvider() { + this.applicationPropertiesFileGenerator = new ConfigFileGenerator("application-", ".properties", GET_CONFIG_FILE_PATH, false); // NOI18N + this.bootstrapPropertiesFileGenerator = new ConfigFileGenerator("bootstrap-", ".properties", GET_CONFIG_FILE_PATH, false); // NOI18N + } + + @Override + public Set getCommands() { + return Collections.singleton(GET_CONFIG_FILE_PATH); + } + + @Override + public CompletableFuture runCommand(String command, List arguments) { + CompletableFuture ret = new CompletableFuture(); + + Properties applicationProps = new Properties(); + Properties bootstrapProps = new Properties(); + PropertiesGenerator propGen = new PropertiesGenerator(false); + + applicationProps.putAll(propGen.getApplication()); + bootstrapProps.putAll(propGen.getBootstrap()); + + String applicationPropertiesPath = null; + String bootstrapPropertiesPath = null; + try { + if (!bootstrapProps.isEmpty()) { + Path bootstrapProperties = bootstrapPropertiesFileGenerator.writePropertiesFile(bootstrapProps); + bootstrapPropertiesPath = bootstrapProperties.toAbsolutePath().toString(); + } + + if (!applicationProps.isEmpty()) { + Path applicationProperties = applicationPropertiesFileGenerator.writePropertiesFile(applicationProps); + applicationPropertiesPath = applicationProperties.toAbsolutePath().toString(); + } + ret.complete(new ConfigFilesResponse(applicationPropertiesPath, bootstrapPropertiesPath)); + } catch (IOException ex) { + ret.completeExceptionally(ex); + return ret; + } + + return ret; + } + + private class ConfigFilesResponse { + + final String applicationProperties; + final String bootstrapProperties; + + public ConfigFilesResponse(String applicationProperties, String bootstrapProperties) { + this.applicationProperties = applicationProperties; + this.bootstrapProperties = bootstrapProperties; + } + } +} diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/OCIPropertiesProvider.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/OCIPropertiesProvider.java index 89f469b050c9..bdc85ab08ec1 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/OCIPropertiesProvider.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/OCIPropertiesProvider.java @@ -19,35 +19,15 @@ package org.netbeans.modules.cloud.oracle.assets; -import java.io.FileWriter; import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.file.DirectoryStream; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.LinkOption; import java.nio.file.Path; -import java.nio.file.attribute.AclEntry; -import java.nio.file.attribute.AclEntryPermission; -import static java.nio.file.attribute.AclEntryPermission.*; -import java.nio.file.attribute.AclFileAttributeView; -import java.nio.file.attribute.DosFileAttributeView; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.PosixFileAttributeView; -import java.nio.file.attribute.PosixFilePermission; -import static java.nio.file.attribute.PosixFilePermission.*; -import java.nio.file.attribute.PosixFilePermissions; import java.util.Collections; -import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.logging.Level; -import java.util.logging.Logger; import org.netbeans.api.db.explorer.ConnectionManager; import org.netbeans.api.db.explorer.DatabaseConnection; import org.netbeans.spi.lsp.CommandProvider; @@ -59,22 +39,12 @@ */ @ServiceProvider(service = CommandProvider.class) public class OCIPropertiesProvider implements CommandProvider { - private static final Logger LOG = Logger.getLogger(OCIPropertiesProvider.class.getName()); private static final String GET_DB_CONNECTION = "nbls.db.connection"; //NOI18N - private static final boolean POSIX = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); // NOI18N - private static final EnumSet readWritePosix = EnumSet.of(OWNER_READ, OWNER_WRITE); - private static final EnumSet readOnlyAcl = EnumSet.of(READ_ACL, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_DATA, READ_NAMED_ATTRS, DELETE, SYNCHRONIZE); - - // temporary directory location - private static final Path tmpdir = Path.of(System.getProperty("java.io.tmpdir")); // NOI18N + private final ConfigFileGenerator configFileGenerator; public OCIPropertiesProvider() { - try { - deleteOldFiles(generateDirPath()); - } catch (IOException ex) { - LOG.log(Level.SEVERE, "deleteOldFiles", ex); - } + this.configFileGenerator = new ConfigFileGenerator("db-", ".properties", GET_DB_CONNECTION, true); // NOI18N } @Override @@ -99,80 +69,15 @@ public CompletableFuture runCommand(String command, List argumen } } if (!dbProps.isEmpty()) { - Path temp = null; - Path dir = generateDirPath(); - try { - if (!Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS)) { - Files.createDirectory(dir); - } - if (POSIX) { - FileAttribute readWriteAttribs = PosixFilePermissions.asFileAttribute(readWritePosix); - temp = Files.createTempFile(dir, "db-", ".properties", readWriteAttribs); // NOI18N - } else { - temp = Files.createTempFile(dir, "db-", ".properties"); // NOI18N - AclFileAttributeView acl = Files.getFileAttributeView(temp, AclFileAttributeView.class); - AclEntry ownerEntry = null; - for(AclEntry e : acl.getAcl()) { - if (e.principal().equals(acl.getOwner())) { - ownerEntry = e; - break; - } - } - if (ownerEntry != null) { - acl.setAcl(Collections.singletonList(ownerEntry)); - } else { - deleteTempFile(temp); - ret.completeExceptionally(new IOException("Owner missing, file:"+temp.toString())); // NOI18N - return ret; - } - } + Path temp = configFileGenerator.writePropertiesFile(dbProps); + result.put("MICRONAUT_CONFIG_FILES", temp.toAbsolutePath().toString()); // NOI18N + ret.complete(result); } catch (IOException ex) { - deleteTempFile(temp); - ret.completeExceptionally(ex); - return ret; - } - - try (Writer writer = new FileWriter(temp.toFile(), Charset.defaultCharset());) { - dbProps.store(writer, ""); - if (POSIX) { - PosixFileAttributeView attribs = Files.getFileAttributeView(temp, PosixFileAttributeView.class); - attribs.setPermissions(EnumSet.of(OWNER_READ)); - } else { - DosFileAttributeView attribs = Files.getFileAttributeView(temp, DosFileAttributeView.class); - attribs.setReadOnly(true); - AclFileAttributeView acl = Files.getFileAttributeView(temp, AclFileAttributeView.class); - AclEntry ownerEntry = null; - if (acl.getAcl().size() != 1) { - deleteTempFile(temp); - ret.completeExceptionally(new IOException("Too many Acls, file:"+temp.toString())); // NOI18N - return ret; - } - for(AclEntry e : acl.getAcl()) { - if (e.principal().equals(acl.getOwner())) { - ownerEntry = e; - break; - } - } - if (ownerEntry != null) { - AclEntry readOnly = AclEntry.newBuilder(ownerEntry).setPermissions(readOnlyAcl).build(); - acl.setAcl(Collections.singletonList(readOnly)); - } else { - deleteTempFile(temp); - ret.completeExceptionally(new IOException("Owner missing, file:"+temp.toString())); // NOI18N - return ret; - } - } - temp.toFile().deleteOnExit(); - result.put("MICRONAUT_CONFIG_FILES", temp.toAbsolutePath().toString()); // NOI18N - } catch (IOException ex) { - deleteTempFile(temp); ret.completeExceptionally(ex); return ret; } } - - ret.complete(result); return ret; } @@ -180,38 +85,4 @@ public CompletableFuture runCommand(String command, List argumen public Set getCommands() { return Collections.singleton(GET_DB_CONNECTION); } - - private static Path generateDirPath() { - String s = GET_DB_CONNECTION + "_" + System.getProperty("user.name"); // NOI18N - Path name = tmpdir.getFileSystem().getPath(s); - return tmpdir.resolve(name); - } - - private static void deleteOldFiles(Path dir) throws IOException { - if (Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS)) { - try (DirectoryStream stream = Files.newDirectoryStream(dir)) { - for (Path f : stream) { - deleteTempFile(f); - } - } - } - } - - private static void deleteTempFile(Path temp) { - if (temp != null && Files.isRegularFile(temp, LinkOption.NOFOLLOW_LINKS)) { - try { - if (POSIX) { - PosixFileAttributeView attribs = Files.getFileAttributeView(temp, PosixFileAttributeView.class); - attribs.setPermissions(readWritePosix); - } else { - DosFileAttributeView attribs = Files.getFileAttributeView(temp, DosFileAttributeView.class); - attribs.setReadOnly(false); - } - Files.delete(temp); - } catch (IOException ex) { - LOG.log(Level.WARNING, "deleteTempFile", ex); - } - } - } - } diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/steps/DatabaseConnectionStep.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/steps/DatabaseConnectionStep.java index c7c022610adf..acc3733bb8e0 100644 --- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/steps/DatabaseConnectionStep.java +++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/steps/DatabaseConnectionStep.java @@ -39,7 +39,8 @@ */ @NbBundle.Messages({ "SelectDBConnection=Select Database Connection", - "AddNewConnection=" + "AddNewConnection=", + "OADB=Oracle Autonomous Database" }) public final class DatabaseConnectionStep extends AbstractStep { @@ -68,7 +69,7 @@ public NotifyDescriptor createInput() { List items = new LinkedList<>(); items.add(new QuickPick.Item(Bundle.AddNewConnection(), Bundle.AddNewConnection())); for (Entry adbConnection : adbConnections.entrySet()) { - items.add(new QuickPick.Item(adbConnection.getKey(), "Connection " + adbConnection.getValue())); + items.add(new QuickPick.Item(adbConnection.getKey(), Bundle.OADB())); } return new NotifyDescriptor.QuickPick(Bundle.SelectDBConnection(), Bundle.SelectDBConnection(), items, false); } diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts index e094374a1f1e..f907a2dad917 100644 --- a/java/java.lsp.server/vscode/src/extension.ts +++ b/java/java.lsp.server/vscode/src/extension.ts @@ -972,8 +972,35 @@ function openSSHSession(username: string, host: string, name?: string) { terminal.show(); } -function runDockerSSH(username: string, host: string, dockerImage: string) { - const sshCommand = `ssh ${username}@${host} "docker pull ${dockerImage} && docker run -p 8080:8080 -it ${dockerImage}"`; +interface ConfigFiles { + applicationProperties : string | null; + bootstrapProperties: string | null; +} + +async function runDockerSSH(username: string, host: string, dockerImage: string) { + const configFiles: ConfigFiles = await vscode.commands.executeCommand('nbls.config.file.path') as ConfigFiles; + const { applicationProperties, bootstrapProperties } = configFiles; + + const applicationPropertiesRemotePath = `/home/${username}/application.properties`; + const bootstrapPropertiesRemotePath = `/home/${username}/bootstrap.properties`; + const applicationPropertiesContainerPath = "/home/app/application.properties"; + const bootstrapPropertiesContainerPath = "/home/app/bootstrap.properties"; + + let sshCommand = ""; + let mountVolume = ""; + let micronautConfigFilesEnv = ""; + if (bootstrapProperties) { + sshCommand = `scp "${bootstrapProperties}" ${username}@${host}:${bootstrapPropertiesRemotePath} && `; + mountVolume = `-v ${bootstrapPropertiesRemotePath}:${bootstrapPropertiesContainerPath}:Z `; + micronautConfigFilesEnv = `${bootstrapPropertiesContainerPath}`; + } + + if (applicationProperties) { + sshCommand += `scp "${applicationProperties}" ${username}@${host}:${applicationPropertiesRemotePath} && `; + mountVolume += ` -v ${applicationPropertiesRemotePath}:${applicationPropertiesContainerPath}:Z`; + micronautConfigFilesEnv += `${bootstrapProperties ? "," : ""}${applicationPropertiesContainerPath}`; + } + sshCommand += `ssh ${username}@${host} "docker pull ${dockerImage} && docker run -p 8080:8080 ${mountVolume} -e MICRONAUT_CONFIG_FILES=${micronautConfigFilesEnv} -it ${dockerImage}"`; const terminal = vscode.window.createTerminal('Remote Docker'); terminal.sendText(sshCommand);