All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.enterprise.admin.cli.cluster.InstallNodeSshCommand Maven / Gradle / Ivy

There is a newer version: 8.0.0-JDK17-M9
Show newest version
/*
 * Copyright (c) 2022, 2025 Contributors to the Eclipse Foundation
 * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.enterprise.admin.cli.cluster;

import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.SftpException;
import com.sun.enterprise.util.SystemPropertyConstants;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import org.glassfish.api.Param;
import org.glassfish.api.admin.CommandException;
import org.glassfish.cluster.ssh.launcher.SSHException;
import org.glassfish.cluster.ssh.launcher.SSHLauncher;
import org.glassfish.cluster.ssh.launcher.SSHSession;
import org.glassfish.cluster.ssh.sftp.SFTPClient;
import org.glassfish.cluster.ssh.sftp.SFTPPath;
import org.glassfish.cluster.ssh.util.SSHUtil;
import org.glassfish.hk2.api.PerLookup;
import org.jvnet.hk2.annotations.Service;

import static java.util.logging.Level.SEVERE;

/**
 * @author Byron Nevins
 */
@Service(name = "install-node-ssh")
@PerLookup
public class InstallNodeSshCommand extends InstallNodeBaseCommand {
    private static final String GF_DIR_NAME = "glassfish";
    private static final SFTPPath PATH_REL_DOMAINS = SFTPPath.ofRelativePath(GF_DIR_NAME, "domains");
    private static final SFTPPath PATH_REL_NODES = SFTPPath.ofRelativePath(GF_DIR_NAME, "nodes");


    @Param(name = "sshuser", optional = true, defaultValue = "${user.name}")
    private String user;
    @Param(optional = true, defaultValue = "22", name = "sshport")
    int port;
    @Param(optional = true)
    String sshkeyfile;

    //storing password to prevent prompting twice
    private final Map sshPasswords = new HashMap<>();

    @Override
    String getRawRemoteUser() {
        return user;
    }

    @Override
    int getRawRemotePort() {
        return port;
    }

    @Override
    String getSshKeyFile() {
        return sshkeyfile;
    }

    @Override
    protected void validate() throws CommandException {
        super.validate();
        if (sshkeyfile == null) {
            //if user hasn't specified a key file check if key exists in
            //default location
            File existingKey = SSHUtil.getExistingKeyFile();
            if (existingKey == null) {
                promptPass = true;
            } else {
                sshkeyfile = existingKey.getAbsolutePath();
            }
        } else {
            validateKey(sshkeyfile);
        }

        //we need the key passphrase if key is encrypted
        if (sshkeyfile != null && SSHUtil.isEncryptedKey(new File(sshkeyfile))) {
            sshkeypassphrase = getSSHPassphrase(true);
        }
    }

    @Override
    void copyToHosts(File zipFile, List binDirFiles) throws CommandException {
        // exception handling is too complicated to mess with in the real method.
        // the idea is to catch everything here and re-throw as one kind
        // the caller is just going to do it anyway so we may as well do it here.
        // And it makes the signature simpler for other subclasses...
        try {
            copyToHostsInternal(zipFile, binDirFiles);
        } catch (CommandException ex) {
            throw ex;
        } catch (SSHException e) {
            // Note: CommandException is not printed to logs.
            logger.log(SEVERE,
                "Failed to copy zip file " + zipFile + " and to make binary files " + binDirFiles + " executable.", e);
            throw new CommandException(e.getMessage(), e);
        }
    }


    private void copyToHostsInternal(File zipFile, List binDirFiles)
        throws SSHException, CommandException {
        boolean prompt = promptPass;
        for (String host : hosts) {
            File keyFile = getSshKeyFile() == null ? null : new File(getSshKeyFile());
            SSHLauncher sshLauncher = new SSHLauncher(getRemoteUser(), host, getRemotePort(), sshpassword, keyFile, sshkeypassphrase);

            if (getSshKeyFile() != null && !sshLauncher.checkConnection()) {
                // key auth failed, so use password auth
                prompt = true;
            }

            if (prompt) {
                final String sshpass;
                if (sshPasswords.containsKey(host)) {
                    sshpass = String.valueOf(sshPasswords.get(host));
                } else {
                    sshpass = getSSHPassword(host);
                }

                //re-initialize
                sshLauncher = new SSHLauncher(getRemoteUser(), host, getRemotePort(), sshpass, keyFile, sshkeypassphrase);
                prompt = false;
            }

            final SFTPPath sshInstallDir = SFTPPath.of(new File(getInstallDir()));
            try (SSHSession session = sshLauncher.openSession(); SFTPClient sftp = session.createSFTPClient()) {
                final SFTPPath backupDir;
                if (sftp.exists(sshInstallDir)) {
                    backupDir = createBackup(sftp, sshInstallDir);
                    sftp.rmDir(sshInstallDir, true);
                } else {
                    backupDir = null;
                }
                sftp.mkdirs(sshInstallDir);
                if (sshLauncher.getCapabilities().isChmodSupported()) {
                    sftp.chmod(sshInstallDir, 0755);
                }
                final SFTPPath remoteZipFile = sshInstallDir.resolve(zipFile.getName());
                logger.info(() -> "Copying " + zipFile + " (" + zipFile.length() + " bytes)" + " to " + host + ":"
                    + remoteZipFile);
                sftp.put(zipFile, remoteZipFile);
                logger.finer(() -> "Copied " + zipFile + " to " + host + ":" + remoteZipFile);

                logger.info(() -> "Unpacking " + remoteZipFile + " on " + host + " to " + sshInstallDir);
                session.unzip(remoteZipFile, sshInstallDir);
                logger.finer(() -> "Unpacked " + getArchiveName() + " into " + host + ":" + sshInstallDir);

                logger.info(() -> "Removing " + host + ":" + remoteZipFile);
                sftp.rm(remoteZipFile);
                logger.finer(() -> "Removed " + host + ":" + remoteZipFile);

                // zip doesn't retain file permissions, hence executables need
                // to be fixed with proper permissions
                if (sshLauncher.getCapabilities().isChmodSupported()) {
                    logger.info(() -> "Fixing file permissions of all bin files under " + host + ":" + sshInstallDir);
                    if (binDirFiles.isEmpty()) {
                        // binDirFiles can be empty if the archive isn't a fresh one
                        searchAndFixBinDirectoryFiles(sshInstallDir, sftp);
                    } else {
                        for (SFTPPath binDirFile : binDirFiles) {
                            sftp.chmod(sshInstallDir.resolve(binDirFile), 0755);
                        }
                    }
                    logger.finer(() -> "Fixed file permissions of all bin files under " + host + ":" + sshInstallDir);
                }

                if (backupDir != null) {
                    List dirs = sftp.ls(backupDir, e -> e.getAttrs().isDir());
                    for (String dirName : dirs) {
                        SFTPPath target = sshInstallDir.resolve(SFTPPath.ofRelativePath(GF_DIR_NAME, dirName));
                        sftp.mv(backupDir.resolve(dirName), target);
                    }
                    sftp.rmDir(backupDir, false);
                    logger.log(Level.INFO, "Successfuly restored domains and nodes from previous installation.");
                }
            }
        }
    }

    private SFTPPath createBackup(SFTPClient sftp, SFTPPath sshInstallDir) throws SSHException {
        SFTPPath origDomainsPath = sshInstallDir.resolve(PATH_REL_DOMAINS);
        SFTPPath origNodesPath = sshInstallDir.resolve(PATH_REL_NODES);
        boolean origDomainsExist = sftp.existsDirectory(origDomainsPath);
        boolean origNodesExist = sftp.existsDirectory(origNodesPath);
        if (!origDomainsExist && !origNodesExist) {
            return null;
        }
        SFTPPath backupDir = sshInstallDir.getParent().resolve("tmp-upgrade-backup");
        logger.log(Level.INFO, "Creating backup, domains: {0}, nodes: {1} into {2}",
            new Object[] {origDomainsExist, origNodesExist, backupDir});
        if (sftp.existsDirectory(backupDir)) {
            throw new SSHException("The backup directory already exists, probably some failed previous upgrade?");
        }
        sftp.mkdirs(backupDir);
        if (origDomainsExist) {
            sftp.mv(origDomainsPath, backupDir.resolve("domains"));
        }
        if (origNodesExist) {
            sftp.mv(origNodesPath, backupDir.resolve("nodes"));
        }
        return backupDir;
    }


    /**
     * Recursively list install dir and identify "bin" directory. Change permissions
     * of files under "bin" directory.
     * @param dir GlassFish install root
     * @param sftpClient ftp client handle
     * @throws SftpException
     */
    private void searchAndFixBinDirectoryFiles(SFTPPath dir, SFTPClient sftpClient) throws SSHException {
        for (LsEntry entry : sftpClient.lsDetails(dir, e -> true)) {
            SFTPPath subPath = dir.resolve(entry.getFilename());
            if (entry.getAttrs().isDir()) {
                if (entry.getFilename().equals("bin")) {
                    fixFilePermissions(subPath, sftpClient);
                } else {
                    searchAndFixBinDirectoryFiles(subPath, sftpClient);
                }
            } else if ("nadmin".equals(entry.getFilename())) {
                sftpClient.chmod(subPath, 0755);
            }
        }
    }

    /**
     * Set permissions of all files under specified directory. Note that this
     * doesn't check the file type before changing the permissions.
     * @param binDir directory where file permissions need to be fixed
     * @param sftpClient ftp client handle
     * @throws SftpException
     */
    private void fixFilePermissions(SFTPPath binDir, SFTPClient sftpClient) throws SSHException {
        for (String directoryEntry : sftpClient.ls(binDir, entry -> !entry.getAttrs().isDir())) {
            sftpClient.chmod(binDir.resolve(directoryEntry), 0755);
        }
    }


    @Override
    final void precopy() throws CommandException {
        if (getForce()) {
            return;
        }

        boolean prompt = promptPass;
        for (String host : hosts) {
            File keyFile = getSshKeyFile() == null ? null : new File(getSshKeyFile());
            SSHLauncher sshLauncher = new SSHLauncher(getRemoteUser(), host, getRemotePort(), sshpassword, keyFile, sshkeypassphrase);

            if (keyFile != null && !sshLauncher.checkConnection()) {
                //key auth failed, so use password auth
                prompt = true;
            }

            if (prompt) {
                String sshpass = getSSHPassword(host);
                sshPasswords.put(host, sshpass.toCharArray());
                sshLauncher = new SSHLauncher(getRemoteUser(), host, getRemotePort(), sshpass, keyFile, sshkeypassphrase);
                prompt = false;
            }

            SFTPPath sshInstallDir = SFTPPath.of(getInstallDir());
            try (SSHSession session = sshLauncher.openSession(); SFTPClient sftpClient = session.createSFTPClient()) {
                if (sftpClient.exists(sshInstallDir)) {
                    checkIfAlreadyInstalled(session, host, sshInstallDir);
                }
            } catch (IOException ex) {
                throw new CommandException(ex);
            }
        }
    }


    /**
     * Determines if GlassFish is installed on remote host at specified location.
     * Uses SSH launcher to execute 'asadmin version'
     *
     * @param host remote host
     */
    private void checkIfAlreadyInstalled(SSHSession session, String host, Path sshInstallDir)
        throws CommandException, SSHException {
        //check if an installation already exists on remote host
        String asadmin = Constants.v4 ? "/lib/nadmin' version --local --terse" : "/bin/asadmin' version --local --terse";
        String cmd = "'" + sshInstallDir + "/" + SystemPropertyConstants.getComponentName() + asadmin;
        int status = session.exec(cmd);
        if (status == 0) {
            throw new CommandException(Strings.get("install.dir.exists", sshInstallDir));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy