diff --git a/api/src/org/labkey/api/secrets/SecretProvider.java b/api/src/org/labkey/api/secrets/SecretProvider.java index 928d5268cc9..6401c06f5ac 100644 --- a/api/src/org/labkey/api/secrets/SecretProvider.java +++ b/api/src/org/labkey/api/secrets/SecretProvider.java @@ -30,4 +30,14 @@ public interface SecretProvider /** Human-readable name for this source, shown on the admin secrets page. */ @NotNull String getDescription(); + + /** + * Returns the name of the application this source is scoped to, or {@code null} if this + * source has no notion of an application name (the default). Used for attributing + * externally-billed resources (e.g., Vertex AI requests) back to the owning app. + */ + default @Nullable String getAppName() + { + return null; + } } diff --git a/api/src/org/labkey/api/secrets/SecretService.java b/api/src/org/labkey/api/secrets/SecretService.java index 8dd22cdd7d4..1ca7d08cd7f 100644 --- a/api/src/org/labkey/api/secrets/SecretService.java +++ b/api/src/org/labkey/api/secrets/SecretService.java @@ -90,4 +90,10 @@ static void setInstance(SecretService service) * The external provider takes priority over startup-property and environment-variable sources. */ @Nullable String getExternalProviderDescription(); + + /** + * Returns the application name reported by the highest-priority provider that has one + * (see {@link SecretProvider#getAppName()}), or {@code null} if no active provider reports one. + */ + @Nullable String getAppName(); } diff --git a/api/src/org/labkey/api/settings/AppProps.java b/api/src/org/labkey/api/settings/AppProps.java index 3ae76372530..064127076d6 100644 --- a/api/src/org/labkey/api/settings/AppProps.java +++ b/api/src/org/labkey/api/settings/AppProps.java @@ -21,6 +21,7 @@ import org.labkey.api.data.ContainerManager; import org.labkey.api.module.DefaultModule; import org.labkey.api.module.SupportedDatabase; +import org.labkey.api.secrets.SecretService; import org.labkey.api.util.ExceptionReportingLevel; import org.labkey.api.util.Path; import org.labkey.api.util.UsageReportingLevel; @@ -75,6 +76,17 @@ static WriteableAppProps getWriteableInstance() @Nullable String getEnlistmentId(); + /** + * Returns the DevOps-assigned name of the tenant/customer this deployment belongs to (e.g., "Hooli", + * "WNPRC"), or {@code null} if none is configured. Not customer-facing; currently sourced from + * whichever active {@link org.labkey.api.secrets.SecretProvider} reports one. + * @see SecretService#getAppName() + */ + default @Nullable String getAppName() + { + return SecretService.get().getAppName(); + } + boolean isCachingAllowed(); boolean isRecompileJspEnabled(); diff --git a/core/src/org/labkey/core/secrets/SecretServiceImpl.java b/core/src/org/labkey/core/secrets/SecretServiceImpl.java index b5d4cd064ae..4af583846d7 100644 --- a/core/src/org/labkey/core/secrets/SecretServiceImpl.java +++ b/core/src/org/labkey/core/secrets/SecretServiceImpl.java @@ -142,6 +142,18 @@ public void setExternalProvider(@NotNull SecretProvider provider) return provider != null ? provider.getDescription() : null; } + @Override + public @Nullable String getAppName() + { + for (SecretProvider provider : activeProviders()) + { + String appName = provider.getAppName(); + if (appName != null) + return appName; + } + return null; + } + public void shutdown() { SecretProvider external = _externalProvider; @@ -237,6 +249,41 @@ public void testExternalProviderPriority() assertEquals("from-external", svc.getSecret(prop)); } + @Test + public void testGetAppNameFromExternalProvider() + { + SecretServiceImpl svc = new SecretServiceImpl(); + svc.setExternalProvider(new SecretProvider() + { + @Override + public @Nullable String getSecret(String propertyName) + { + return null; + } + + @Override + public @NotNull String getDescription() + { + return "Test provider"; + } + + @Override + public @Nullable String getAppName() + { + return "MyApp"; + } + }); + + assertEquals("MyApp", svc.getAppName()); + } + + @Test + public void testGetAppNameNullWhenNoProviderReportsOne() + { + SecretServiceImpl svc = new SecretServiceImpl(); + assertNull(svc.getAppName()); + } + private StartupPropertySecretProvider startupProviderWith(String name, String value) { StartupPropertySecretProvider provider = new StartupPropertySecretProvider();