Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support all labkey.xml/ROOT.xml settings via application.properties #5142

Merged
merged 3 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions api/src/org/labkey/api/data/DbScope.java
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,9 @@ public DbScope(String dsName, LabKeyDataSource dataSource) throws ServletExcepti
* <p>Wraps a {@link DataSource}, validating the data source, adding LabKey-specific properties, and
* setting an application name that LabKey sends on every connection. With the exception of
* {@code ScopeQueryLoggingProfilerListener.TestCase}, there's a one-to-one correspondence between LabKeyDataSource
* and valid data sources defined in labkey.xml. That's not the case with {@link DbScopeLoader} and {@link DbScope}</p>
* and valid data sources defined in application.properties. That's not the case with {@link DbScopeLoader} and {@link DbScope}</p>
*
* <p>This class handles the special LabKey-specific properties that administrators can add to labkey.xml and
* <p>This class handles the special LabKey-specific properties that administrators can add to application.properties and
* associate with a data source. To add support for a new property, simply add a getter & setter to this class and
* then do something with the typed value in DbScope.</p>
*
Expand All @@ -302,7 +302,7 @@ public static class LabKeyDataSource
public static final String CPAS_DATA_SOURCE = "cpasDataSource";
private static final String DEFAULT_APPLICATION_NAME = "LabKey Server";

private final String _dsName; // DataSource name from labkey.xml
private final String _dsName; // DataSource name from application.properties
private final DataSource _ds;
private final DataSourcePropertyReader _dsPropertyReader;
private final @Nullable Properties _connectionProperties;
Expand Down Expand Up @@ -1532,7 +1532,7 @@ private static void verifyTomcatLibJars()

private static String _applicationName = null;

// Enumerate each jdbc DataSource in labkey.xml and initialize them
// Enumerate each jdbc DataSource and initialize them
public static void initializeDataSources()
{
verifyTomcatLibJars();
Expand Down Expand Up @@ -1564,8 +1564,8 @@ public static void initializeDataSources()
}
}

// Ensure that the labkeyDataSource (or cpasDataSource, for old installations) exists in
// labkey.xml / cpas.xml and create the associated database if it doesn't already exist.
// Ensure that the labkeyDataSource (or cpasDataSource, for old installations) exists
// and create the associated database if it doesn't already exist.
LabKeyDataSource primaryDS = LabKeyDataSource.setPrimaryDataSource(dataSources);
labkeyDsName = primaryDS.getDsName();
// Now that we've tagged the primary datasource we can prepare them all
Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/data/PropertyEncryption.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public enum PropertyEncryption
return "Test";
}
},
/** No encryption key was specified in labkey.xml, so throw ConfigurationException */
/** No encryption key was specified in application.properties, so throw ConfigurationException */
NoKey
{
@Override
Expand Down
4 changes: 2 additions & 2 deletions api/src/org/labkey/api/module/ModuleLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -1838,7 +1838,7 @@ private void afterUpgrade(boolean performedUpgrade, File lockFile)
verifyRequiredModules();
}

// If the "requiredModules" parameter is present in labkey.xml then fail startup if any specified module is missing.
// If the "requiredModules" parameter is present in application.properties then fail startup if any specified module is missing.
// Particularly interesting for compliant deployments, e.g., <Parameter name="requiredModules" value="Compliance"/>
private void verifyRequiredModules()
{
Expand Down Expand Up @@ -1987,7 +1987,7 @@ public Collection<Module> orderModules(Collection<Module> modules)
}

// Returns a set of data source names representing all external data sources that are required for module schemas.
// These are just the names that modules advertise; there's no guarantee that they're defined in labkey.xml or
// These are just the names that modules advertise; there's no guarantee that they're defined or
// valid. Be sure to null check after attempting to resolve each to a DbScope.
public Set<String> getAllModuleDataSourceNames()
{
Expand Down
6 changes: 3 additions & 3 deletions api/src/org/labkey/api/security/AuthenticationProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,9 @@ interface ResetPasswordProvider extends AuthenticationProvider
interface SecondaryAuthenticationProvider<AC extends SecondaryAuthenticationConfiguration<?>> extends AuthenticationProvider, AuthenticationConfigurationFactory<AC>
{
/**
* Bypass authentication from this provider. Might be configured via labkey.xml parameter to
* temporarily not require secondary authentication if this has been misconfigured or a 3rd
* party service provider is unavailable.
* Bypass authentication from this provider. Might be configured via context.bypass2FA=true property in
* application.properties to temporarily not require secondary authentication if this has been misconfigured or
* a 3rd party service provider is unavailable.
*/
boolean bypass();
}
Expand Down
8 changes: 4 additions & 4 deletions api/src/org/labkey/api/security/Encryption.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@

/**
* Easy to use wrappers for common encryption algorithms. Also includes related helper methods for shared operations
* such as generating salts & keys, and for retrieving & saving the labkey.xml encryption key and standard salt.
* such as generating salts & keys, and for retrieving & saving the application.properties encryption key and standard salt.
* WARNING: Do not change the core algorithms or parameters of existing implementations; changes will likely
* render existing data irrecoverable.
*/
Expand Down Expand Up @@ -416,9 +416,9 @@ public DecryptionException(String message, Throwable cause)
}

/**
* Return standard AES encryption algorithm. Generates a 128-bit key from the labkey.xml encryption key. All other
* encryption parameters are documented in AES(). Pass in a registered EncryptionMigrationHandler to prove that you
* can migrate your encrypted content.
* Return standard AES encryption algorithm. Generates a 128-bit key from the application.properties encryption key.
* All other encryption parameters are documented in AES(). Pass in a registered EncryptionMigrationHandler to prove
* that you can migrate your encrypted content.
*/
public static Algorithm getAES128(EncryptionMigrationHandler handler)
{
Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/settings/AppProps.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ static WriteableAppProps getWriteableInstance()

String getBLASTServerBaseURL();

/** @return the name of the Tomcat XML deployment descriptor based on the context path for this install - typically ROOT.xml or labkey.xml */
/** @return the name of the Tomcat XML deployment descriptor based on the context path for this install - now always application.properties */
String getWebappConfigurationFilename();

/**
Expand Down
6 changes: 2 additions & 4 deletions api/src/org/labkey/api/settings/AppPropsImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -469,14 +469,12 @@ public UsageReportingLevel getUsageReportingLevel()
}
}

// Get the name of the webapp configuration file, e.g., labkey.xml, cpas.xml, or ROOT.xml. Used in some error messages
// Get the name of the webapp configuration file, now always application.properties. Used in some error messages
// to provide suggestions to the admin.
@Override
public String getWebappConfigurationFilename()
{
String path = getContextPath();

return "".equals(path) ? "ROOT.xml" : path.substring(1) + ".xml";
return "application.properties";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this overridden somewhere so we can still return the .xml files? I think the tomcat10 branch will get merged before we are able to all be using embedded all the time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's now hard-coded to always return application.properties. I could do work to be able to differentiate between standalone and embedded Tomcat, but for the moment I'm proceeding optimistically that by 24.3 on-premise deployments (where users are most likely to see these messages) will all be configured via the properties file. If you think that's a bad idea, let me know

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems OK (now that I've looked at what this method actually does) and easy enough to rework if need be.

}

@Override
Expand Down
2 changes: 1 addition & 1 deletion core/src/org/labkey/core/CoreModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -1356,7 +1356,7 @@ public Set<DbSchema> getSchemasToTest()
{
Set<DbSchema> result = new LinkedHashSet<>(super.getSchemasToTest());

// Add the "labkey" schema in all module data sources as well... should match labkey.xml
// Add the "labkey" schema in all module data sources as well... should match application.properties
for (String dataSourceName : ModuleLoader.getInstance().getAllModuleDataSourceNames())
{
DbScope scope = DbScope.getDbScope(dataSourceName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ private void getConnectionPoolSizeWarnings(Warnings warnings, DbScope labkeyScop

if (null == maxTotal)
{
warnings.add(HtmlString.of("Could not determine the connection pool size for the labkeyDataSource; verify that the connection pool is properly configured in labkey.xml"));
warnings.add(HtmlString.of("Could not determine the connection pool size for the labkeyDataSource; verify that the connection pool is properly configured in application.properties"));
}
else if (showAllWarnings || maxTotal < 20)
{
Expand Down
65 changes: 56 additions & 9 deletions pipeline/src/org/labkey/pipeline/cluster/ClusterStartup.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.labkey.pipeline.cluster;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.SystemUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -41,14 +42,21 @@

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* Entry point for pipeline jobs that are invoked on a cluster node. After completion of the job, the process
Expand Down Expand Up @@ -193,26 +201,26 @@ public void testSuccess() throws IOException, InterruptedException
DummyPipelineJob job = new DummyPipelineJob(JunitUtil.getTestContainer(), TestContext.get().getUser(), DummyPipelineJob.Worker.success);
String output = executeJobRemote(createArgs(job), 0);
String jobLog = PageFlowUtil.getFileContentsAsString(job.getLogFile());
Assert.assertTrue("Couldn't find logging", jobLog.contains("Successful worker!"));
Assert.assertTrue("Couldn't find logging", output.contains("Exploding module archives"));
Assert.assertTrue("Couldn't find logging. \nProcess output: " + output + "\nJob log: " + jobLog, jobLog.contains("Successful worker!"));
Assert.assertTrue("Couldn't find logging. \nProcess output: " + output + "\nJob log: " + jobLog, output.contains("Exploding module archives"));
}

@Test
public void testFailure() throws IOException, InterruptedException
{
DummyPipelineJob job = new DummyPipelineJob(JunitUtil.getTestContainer(), TestContext.get().getUser(), DummyPipelineJob.Worker.failure);
executeJobRemote(createArgs(job), 1);
String output = executeJobRemote(createArgs(job), 1);
String jobLog = PageFlowUtil.getFileContentsAsString(job.getLogFile());
Assert.assertTrue("Couldn't find logging", jobLog.contains("Oopsies"));
Assert.assertTrue("Couldn't find logging", jobLog.contains("java.lang.UnsupportedOperationException"));
Assert.assertTrue("Couldn't find logging.\nProcess output: " + output + "\nJob log: " + jobLog, jobLog.contains("Oopsies"));
Assert.assertTrue("Couldn't find logging.\nProcess output: " + output + "\nJob log: " + jobLog, jobLog.contains("java.lang.UnsupportedOperationException"));
}

@Test
public void testExtractOnly() throws IOException, InterruptedException
{
List<String> args = createArgs(null);
String output = executeJobRemote(args, 0);
Assert.assertTrue("Couldn't find logging", output.contains("Exploding module archives"));
Assert.assertTrue("Couldn't find logging. \nProcess output: " + output, output.contains("Exploding module archives"));
}

@Test
Expand All @@ -223,7 +231,7 @@ public void testBadPath() throws IOException, InterruptedException
// Last argument is supposed to be the URI to the serialized job's file, hack it to something else
args.set(args.size() - 1, "NotAValidURI.json");
String output = executeJobRemote(args, 1);
Assert.assertTrue("Couldn't find logging", output.contains("Could not find serialized job file"));
Assert.assertTrue("Couldn't find logging. \nProcess output: " + output, output.contains("Could not find serialized job file"));
}

protected String executeJobRemote(List<String> args, int expectedExitCode) throws IOException, InterruptedException
Expand Down Expand Up @@ -254,9 +262,9 @@ protected String executeJobRemote(List<String> args, int expectedExitCode) throw
if (!proc.waitFor(1, TimeUnit.MINUTES))
{
proc.destroy();
Assert.fail("Process did not complete. Output:\n" + sb.toString());
Assert.fail("Process did not complete. Output:\n" + sb);
}
Assert.assertEquals("Wrong exit code, output: " + sb.toString(), expectedExitCode, proc.exitValue());
Assert.assertEquals("Wrong exit code, output: " + sb, expectedExitCode, proc.exitValue());
return sb.toString();
}

Expand All @@ -267,6 +275,15 @@ private List<String> createArgs(@Nullable PipelineJob job) throws IOException
args.add(System.getProperty("java.home") + "/bin/java" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : ""));
File labkeyBootstrap = new File(new File(new File(System.getProperty("catalina.home")), "lib"), "labkeyBootstrap.jar");

if (!labkeyBootstrap.exists())
{
labkeyBootstrap = extractBootstrapFromEmbedded();
if (labkeyBootstrap == null || !labkeyBootstrap.exists())
{
throw new IllegalStateException("Couldn't find labkeyBootstrap.jar");
}
}

// Uncomment this line if you want to debug the forked process
// args.add("-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=*:5005");
args.add("-cp");
Expand All @@ -286,5 +303,35 @@ private List<String> createArgs(@Nullable PipelineJob job) throws IOException

return args;
}

private File extractBootstrapFromEmbedded() throws IOException
{
// Look through the JAR files in the working directory, which is expected to contain the Spring Boot entrypoint
File pwd = new File(".");
File[] jars = pwd.listFiles(f -> f.getName().toLowerCase().endsWith(".jar"));
for (File jar : jars)
{
try (JarFile j = new JarFile(jar))
{
// Look inside the JAR for a labkeyBootstrap*.jar file
Iterator<JarEntry> entries = j.entries().asIterator();
while (entries.hasNext())
{
JarEntry entry = entries.next();
if (entry.getName().contains("labkeyBootstrap") && entry.getName().toLowerCase().endsWith(".jar"))
{
File result = new File("labkeyBootstrap.jar");
try (InputStream in = j.getInputStream(entry);
OutputStream out = new FileOutputStream(result))
{
IOUtils.copy(in, out);
}
return FileUtil.getAbsoluteCaseSensitiveFile(result);
}
}
}
}
return null;
}
}
}