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

org.flywaydb.commandline.configuration.LegacyConfigurationManager Maven / Gradle / Ivy

There is a newer version: 11.1.0
Show newest version
/*-
 * ========================LICENSE_START=================================
 * flyway-commandline
 * ========================================================================
 * Copyright (C) 2010 - 2024 Red Gate Software Ltd
 * ========================================================================
 * Licensed 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.
 * =========================LICENSE_END==================================
 */
package org.flywaydb.commandline.configuration;

import lombok.CustomLog;
import org.flywaydb.commandline.Main;
import org.flywaydb.core.api.configuration.Configuration;
import org.flywaydb.core.api.configuration.FluentConfiguration;
import org.flywaydb.core.internal.configuration.ConfigUtils;
import org.flywaydb.core.internal.database.DatabaseType;
import org.flywaydb.core.internal.database.DatabaseTypeRegister;
import org.flywaydb.core.internal.util.ClassUtils;
import org.flywaydb.core.internal.util.StringUtils;
import org.flywaydb.core.extensibility.LicenseGuard;
import org.flywaydb.core.extensibility.Tier;

import java.io.Console;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.flywaydb.core.internal.configuration.ConfigUtils.DEFAULT_CLI_JARS_LOCATION;
import static org.flywaydb.core.internal.configuration.ConfigUtils.DEFAULT_CLI_SQL_LOCATION;
import static org.flywaydb.core.internal.configuration.ConfigUtils.makeRelativeJarDirsBasedOnWorkingDirectory;
import static org.flywaydb.core.internal.configuration.ConfigUtils.makeRelativeLocationsBasedOnWorkingDirectory;

@CustomLog
public class LegacyConfigurationManager implements ConfigurationManager {

    public Configuration getConfiguration(CommandLineArguments commandLineArguments) {

        Map config = new HashMap<>();
        String installDirectory = commandLineArguments.isWorkingDirectorySet() ? commandLineArguments.getWorkingDirectory() : ClassUtils.getInstallDir(Main.class);
        String workingDirectory = commandLineArguments.getWorkingDirectoryOrNull();

        File jarDir = new File(installDirectory, DEFAULT_CLI_JARS_LOCATION);
        ConfigUtils.warnIfUsingDeprecatedMigrationsFolder(jarDir, ".jar");
        if (jarDir.exists()) {
            config.put(ConfigUtils.JAR_DIRS, jarDir.getAbsolutePath());
        }

        Map envVars = ConfigUtils.environmentVariablesToPropertyMap();

        loadConfigurationFromConfigFiles(config, commandLineArguments, envVars);

        config.putAll(envVars);
        config = overrideConfiguration(config, commandLineArguments.getConfiguration(false));

        File sqlFolder = new File(installDirectory, DEFAULT_CLI_SQL_LOCATION);
        if (ConfigUtils.shouldUseDefaultCliSqlLocation(sqlFolder, StringUtils.hasText(config.get(ConfigUtils.LOCATIONS)))) {
            config.put(ConfigUtils.LOCATIONS, "filesystem:" + sqlFolder.getAbsolutePath());
        }

        if (workingDirectory != null) {
            makeRelativeLocationsBasedOnWorkingDirectory(workingDirectory, config);
            makeRelativeJarDirsBasedOnWorkingDirectory(workingDirectory, config);
        }

        ClassLoader classLoader = buildClassLoaderBasedOnJarDirs(Thread.currentThread().getContextClassLoader(), config);

        ConfigUtils.dumpConfigurationMap(config, "Using configuration:");
        filterProperties(config);

        final FluentConfiguration configuration = new FluentConfiguration(classLoader).configuration(config).workingDirectory(workingDirectory);

        if (!commandLineArguments.shouldSuppressPrompt()) {
            promptForCredentialsIfMissing(config, configuration);
        }

        return configuration;
    }

    protected void loadConfigurationFromConfigFiles(Map config, CommandLineArguments commandLineArguments, Map envVars) {
        String encoding = determineConfigurationFileEncoding(commandLineArguments, envVars);
        File installationDir = new File(ClassUtils.getInstallDir(Main.class));
        String workingDirectory = commandLineArguments.getWorkingDirectoryOrNull();

        config.putAll(ConfigUtils.loadDefaultConfigurationFiles(installationDir, workingDirectory, encoding));

        for (File configFile : determineLegacyConfigFilesFromArgs(commandLineArguments)) {
            config.putAll(ConfigUtils.loadConfigurationFile(configFile, encoding, true));
        }
    }

    /**
     * @return The encoding. (default: UTF-8)
     */
    private static String determineConfigurationFileEncoding(CommandLineArguments commandLineArguments, Map envVars) {
        if (envVars.containsKey(ConfigUtils.CONFIG_FILE_ENCODING)) {
            return envVars.get(ConfigUtils.CONFIG_FILE_ENCODING);
        }

        if (commandLineArguments.isConfigFileEncodingSet()) {
            return commandLineArguments.getConfigFileEncoding();
        }

        return "UTF-8";
    }

    private static List determineLegacyConfigFilesFromArgs(CommandLineArguments commandLineArguments) {

        List legacyFiles = commandLineArguments.getConfigFilePathsFromEnv(false);
        legacyFiles.addAll(commandLineArguments.getConfigFiles().stream().filter(s -> !s.endsWith(".toml")).map(File::new).toList());

        return legacyFiles;
    }

    private static Map overrideConfiguration(Map existingConfiguration, Map newConfiguration) {
        Map combinedConfiguration = new HashMap<>();

        combinedConfiguration.putAll(existingConfiguration);
        combinedConfiguration.putAll(newConfiguration);

        return combinedConfiguration;
    }

    /**
     * If no user or password has been provided, prompt for it. If you want to avoid the prompt, pass in an empty user
     * or password.
     *
     * @param config The properties object to load to configuration into.
     * @return
     */
    private void promptForCredentialsIfMissing(final Map config,  final FluentConfiguration configuration) {
        final Console console = System.console();
        if (console == null) {
            // We are running in an automated build. Prompting is not possible.
            return;
        }

        if (!config.containsKey(ConfigUtils.URL)) {
            // URL is not set. We are doomed for failure anyway.
            return;
        }

        final String url = config.get(ConfigUtils.URL);

        boolean interactivePrompted =  false;

        final boolean hasUser = config.containsKey(ConfigUtils.USER);

        if (!hasUser



                && needsUser(url, config.getOrDefault(ConfigUtils.PASSWORD, null), configuration)) {
            configuration.dataSource(configuration.getUrl(),console.readLine("Database user: "), configuration.getPassword());
            interactivePrompted = true;
        }

        final boolean hasPassword = config.containsKey(ConfigUtils.PASSWORD);

        if (!hasPassword



                && needsPassword(url, config.get(ConfigUtils.USER), configuration)) {
            final char[] password = console.readPassword("Database password: ");
            configuration.dataSource(configuration.getUrl(), configuration.getUser(), password == null ? "" : String.valueOf(password));
            interactivePrompted = true;
        }

        if (interactivePrompted) {
            LOG.warn("Interactive prompt behavior is deprecated and will be removed in a future release - please consider alternatives like secrets management tools or environment variables");
        }
    }

    /**
     * Detect whether the JDBC URL specifies a known authentication mechanism that does not need a username.
     */
    boolean needsUser(String url, String password, Configuration configuration) {
        DatabaseType databaseType = DatabaseTypeRegister.getDatabaseTypeForUrl(url, configuration);
        if (databaseType.detectUserRequiredByUrl(url)) {









            return true;

        }

        return false;
    }

    /**
     * Detect whether the JDBC URL specifies a known authentication mechanism that does not need a password.
     */
    boolean needsPassword(String url, String username, Configuration configuration) {
        DatabaseType databaseType = DatabaseTypeRegister.getDatabaseTypeForUrl(url, configuration);
        if (databaseType.detectPasswordRequiredByUrl(url)) {









            return true;
        }

        return false;
    }

    /**
     * Filters the properties to remove the Flyway Commandline-specific ones.
     */
    private void filterProperties(Map config) {
        config.remove(ConfigUtils.CONFIG_FILES);
        config.remove(ConfigUtils.CONFIG_FILE_ENCODING);
    }

    private ClassLoader buildClassLoaderBasedOnJarDirs(ClassLoader parentClassLoader, Map config) {
        List jarFiles = new ArrayList<>(CommandLineConfigurationUtils.getJdbcDriverJarFiles());

        String jarDirs = config.get(ConfigUtils.JAR_DIRS);

        if (StringUtils.hasText(jarDirs)) {
            jarFiles.addAll(CommandLineConfigurationUtils.getJavaMigrationJarFiles(StringUtils.tokenizeToStringArray(jarDirs.replace(File.pathSeparator, ","), ",")));
        }

        if (!jarFiles.isEmpty()) {
            return ClassUtils.addJarsOrDirectoriesToClasspath(parentClassLoader, jarFiles);
        }

        return parentClassLoader;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy