mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-10-31 10:39:30 +00:00 
			
		
		
		
	MNIOR: feat(apps): support config file (#17872)
* feat(apps): support config file - added support for app config files - removed AppPrivateConfig from the OpenMetadata server configuration * use dorpwizard utility classes for resolving environment variables in the config * moved fields to class level * format
This commit is contained in:
		
							parent
							
								
									37e75c542a
								
							
						
					
					
						commit
						a2ffa0814f
					
				| @ -23,7 +23,6 @@ import javax.validation.Valid; | |||||||
| import javax.validation.constraints.NotNull; | import javax.validation.constraints.NotNull; | ||||||
| import lombok.Getter; | import lombok.Getter; | ||||||
| import lombok.Setter; | import lombok.Setter; | ||||||
| import org.openmetadata.schema.api.configuration.apps.AppsPrivateConfiguration; |  | ||||||
| import org.openmetadata.schema.api.configuration.dataQuality.DataQualityConfiguration; | import org.openmetadata.schema.api.configuration.dataQuality.DataQualityConfiguration; | ||||||
| import org.openmetadata.schema.api.configuration.events.EventHandlerConfiguration; | import org.openmetadata.schema.api.configuration.events.EventHandlerConfiguration; | ||||||
| import org.openmetadata.schema.api.configuration.pipelineServiceClient.PipelineServiceClientConfiguration; | import org.openmetadata.schema.api.configuration.pipelineServiceClient.PipelineServiceClientConfiguration; | ||||||
| @ -114,9 +113,6 @@ public class OpenMetadataApplicationConfig extends Configuration { | |||||||
|   @JsonProperty("dataQualityConfiguration") |   @JsonProperty("dataQualityConfiguration") | ||||||
|   private DataQualityConfiguration dataQualityConfiguration; |   private DataQualityConfiguration dataQualityConfiguration; | ||||||
| 
 | 
 | ||||||
|   @JsonProperty("applications") |  | ||||||
|   private AppsPrivateConfiguration appsPrivateConfiguration; |  | ||||||
| 
 |  | ||||||
|   @JsonProperty("limits") |   @JsonProperty("limits") | ||||||
|   private LimitsConfiguration limitsConfiguration; |   private LimitsConfiguration limitsConfiguration; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,17 +1,17 @@ | |||||||
| package org.openmetadata.service.apps; | package org.openmetadata.service.apps; | ||||||
| 
 | 
 | ||||||
| import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty; |  | ||||||
| import static org.openmetadata.service.apps.scheduler.AppScheduler.APPS_JOB_GROUP; | import static org.openmetadata.service.apps.scheduler.AppScheduler.APPS_JOB_GROUP; | ||||||
| import static org.openmetadata.service.apps.scheduler.AppScheduler.APP_INFO_KEY; | import static org.openmetadata.service.apps.scheduler.AppScheduler.APP_INFO_KEY; | ||||||
| import static org.openmetadata.service.apps.scheduler.AppScheduler.APP_NAME; | import static org.openmetadata.service.apps.scheduler.AppScheduler.APP_NAME; | ||||||
| 
 | 
 | ||||||
|  | import io.dropwizard.configuration.ConfigurationException; | ||||||
|  | import java.io.IOException; | ||||||
| import java.lang.reflect.InvocationTargetException; | import java.lang.reflect.InvocationTargetException; | ||||||
| import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import lombok.Getter; | import lombok.Getter; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
| import org.openmetadata.schema.api.configuration.apps.AppPrivateConfig; | import org.openmetadata.schema.api.configuration.apps.AppPrivateConfig; | ||||||
| import org.openmetadata.schema.api.configuration.apps.AppsPrivateConfiguration; |  | ||||||
| import org.openmetadata.schema.entity.app.App; | import org.openmetadata.schema.entity.app.App; | ||||||
| import org.openmetadata.service.OpenMetadataApplicationConfig; | import org.openmetadata.service.OpenMetadataApplicationConfig; | ||||||
| import org.openmetadata.service.apps.scheduler.AppScheduler; | import org.openmetadata.service.apps.scheduler.AppScheduler; | ||||||
| @ -33,12 +33,11 @@ public class ApplicationHandler { | |||||||
| 
 | 
 | ||||||
|   @Getter private static ApplicationHandler instance; |   @Getter private static ApplicationHandler instance; | ||||||
|   private final OpenMetadataApplicationConfig config; |   private final OpenMetadataApplicationConfig config; | ||||||
|   private final AppsPrivateConfiguration privateConfiguration; |  | ||||||
|   private final AppRepository appRepository; |   private final AppRepository appRepository; | ||||||
|  |   private final ConfigurationReader configReader = new ConfigurationReader(); | ||||||
| 
 | 
 | ||||||
|   private ApplicationHandler(OpenMetadataApplicationConfig config) { |   private ApplicationHandler(OpenMetadataApplicationConfig config) { | ||||||
|     this.config = config; |     this.config = config; | ||||||
|     this.privateConfiguration = config.getAppsPrivateConfiguration(); |  | ||||||
|     this.appRepository = new AppRepository(); |     this.appRepository = new AppRepository(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -55,28 +54,28 @@ public class ApplicationHandler { | |||||||
|   public void setAppRuntimeProperties(App app) { |   public void setAppRuntimeProperties(App app) { | ||||||
|     app.setOpenMetadataServerConnection( |     app.setOpenMetadataServerConnection( | ||||||
|         new OpenMetadataConnectionBuilder(config, app.getBot().getName()).build()); |         new OpenMetadataConnectionBuilder(config, app.getBot().getName()).build()); | ||||||
| 
 |     try { | ||||||
|     if (privateConfiguration != null |       AppPrivateConfig appPrivateConfig = configReader.readConfigFromResource(app.getName()); | ||||||
|         && !nullOrEmpty(privateConfiguration.getAppsPrivateConfiguration())) { |  | ||||||
|       for (AppPrivateConfig appPrivateConfig : privateConfiguration.getAppsPrivateConfiguration()) { |  | ||||||
|         if (app.getName().equals(appPrivateConfig.getName())) { |  | ||||||
|       app.setPreview(appPrivateConfig.getPreview()); |       app.setPreview(appPrivateConfig.getPreview()); | ||||||
|       app.setPrivateConfiguration(appPrivateConfig.getParameters()); |       app.setPrivateConfiguration(appPrivateConfig.getParameters()); | ||||||
|         } |     } catch (IOException e) { | ||||||
|       } |       LOG.debug("Config file for app {} not found: ", app.getName(), e); | ||||||
|  |     } catch (ConfigurationException e) { | ||||||
|  |       LOG.error("Error reading config file for app {}", app.getName(), e); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public Boolean isPreview(String appName) { |   public Boolean isPreview(String appName) { | ||||||
|     if (privateConfiguration != null |     try { | ||||||
|         && !nullOrEmpty(privateConfiguration.getAppsPrivateConfiguration())) { |       AppPrivateConfig appPrivateConfig = configReader.readConfigFromResource(appName); | ||||||
|       for (AppPrivateConfig appPrivateConfig : privateConfiguration.getAppsPrivateConfiguration()) { |  | ||||||
|         if (appName.equals(appPrivateConfig.getName())) { |  | ||||||
|       return appPrivateConfig.getPreview(); |       return appPrivateConfig.getPreview(); | ||||||
|         } |     } catch (IOException e) { | ||||||
|       } |       LOG.debug("Config file for app {} not found: ", appName, e); | ||||||
|     } |  | ||||||
|       return false; |       return false; | ||||||
|  |     } catch (ConfigurationException e) { | ||||||
|  |       LOG.error("Error reading config file for app {}", appName, e); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public void triggerApplicationOnDemand( |   public void triggerApplicationOnDemand( | ||||||
|  | |||||||
| @ -0,0 +1,57 @@ | |||||||
|  | package org.openmetadata.service.apps; | ||||||
|  | 
 | ||||||
|  | import com.fasterxml.jackson.databind.ObjectMapper; | ||||||
|  | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; | ||||||
|  | import io.dropwizard.configuration.ConfigurationException; | ||||||
|  | import io.dropwizard.configuration.EnvironmentVariableSubstitutor; | ||||||
|  | import io.dropwizard.configuration.FileConfigurationSourceProvider; | ||||||
|  | import io.dropwizard.configuration.SubstitutingSourceProvider; | ||||||
|  | import io.dropwizard.configuration.YamlConfigurationFactory; | ||||||
|  | import java.io.File; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.net.URL; | ||||||
|  | import java.util.Map; | ||||||
|  | import org.apache.commons.text.StringSubstitutor; | ||||||
|  | import org.openmetadata.schema.api.configuration.apps.AppPrivateConfig; | ||||||
|  | import org.openmetadata.service.util.JsonUtils; | ||||||
|  | 
 | ||||||
|  | public class ConfigurationReader { | ||||||
|  |   private final StringSubstitutor substitutor; | ||||||
|  |   private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); | ||||||
|  |   private final YamlConfigurationFactory<Object> factory = | ||||||
|  |       new YamlConfigurationFactory<>(Object.class, null, mapper, "dw"); | ||||||
|  | 
 | ||||||
|  |   public ConfigurationReader(Map<String, String> envMap) { | ||||||
|  |     // envMap is for custom environment variables (e.g., for testing), defaulting to the system | ||||||
|  |     // environment. | ||||||
|  |     substitutor = | ||||||
|  |         envMap == null ? new EnvironmentVariableSubstitutor(false) : new StringSubstitutor(envMap); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public ConfigurationReader() { | ||||||
|  |     this(System.getenv()); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public AppPrivateConfig readConfigFromResource(String appName) | ||||||
|  |       throws IOException, ConfigurationException { | ||||||
|  |     String configFilePath = "applications/" + appName + "/config.yaml"; | ||||||
|  |     URL resource = ConfigurationReader.class.getClassLoader().getResource(configFilePath); | ||||||
|  |     if (resource == null) { | ||||||
|  |       throw new IOException("Configuration file not found: " + configFilePath); | ||||||
|  |     } | ||||||
|  |     File configFile = new File(resource.getFile()); | ||||||
|  |     return JsonUtils.convertValue(readConfigFile(configFile), AppPrivateConfig.class); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public Map<String, Object> readConfigFile(File configFile) | ||||||
|  |       throws IOException, ConfigurationException { | ||||||
|  |     try { | ||||||
|  |       return (Map<String, Object>) | ||||||
|  |           factory.build( | ||||||
|  |               new SubstitutingSourceProvider(new FileConfigurationSourceProvider(), substitutor), | ||||||
|  |               configFile.getAbsolutePath()); | ||||||
|  |     } catch (ClassCastException e) { | ||||||
|  |       throw new RuntimeException("Configuration file is not a valid YAML file", e); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -0,0 +1,57 @@ | |||||||
|  | package org.openmetadata.service.resources.apps; | ||||||
|  | 
 | ||||||
|  | import static org.junit.Assert.assertThrows; | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertEquals; | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertNotNull; | ||||||
|  | 
 | ||||||
|  | import io.dropwizard.configuration.ConfigurationException; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | import org.openmetadata.schema.api.configuration.apps.AppPrivateConfig; | ||||||
|  | import org.openmetadata.service.apps.ConfigurationReader; | ||||||
|  | 
 | ||||||
|  | public class ConfigurationReaderTest { | ||||||
|  | 
 | ||||||
|  |   @Test | ||||||
|  |   public void testReadConfigFile() throws IOException, ConfigurationException { | ||||||
|  |     ConfigurationReader reader = | ||||||
|  |         new ConfigurationReader( | ||||||
|  |             Map.of( | ||||||
|  |                 "ENV_VAR", | ||||||
|  |                 "resolvedValue", | ||||||
|  |                 "NESTED_ENV_VAR", | ||||||
|  |                 "nestedValue", | ||||||
|  |                 "LIST_ENV_VAR", | ||||||
|  |                 "value1")); | ||||||
|  |     AppPrivateConfig appConfig = reader.readConfigFromResource("TestApplication"); | ||||||
|  |     assertNotNull(appConfig); | ||||||
|  |     assertEquals("value1", appConfig.getParameters().getAdditionalProperties().get("key1")); | ||||||
|  |     assertEquals("resolvedValue", appConfig.getParameters().getAdditionalProperties().get("key2")); | ||||||
|  |     assertEquals("", appConfig.getParameters().getAdditionalProperties().get("emptyKey")); | ||||||
|  |     assertEquals("default", appConfig.getParameters().getAdditionalProperties().get("defaultKey")); | ||||||
|  |     Map<String, String> nested = | ||||||
|  |         (Map<String, String>) appConfig.getParameters().getAdditionalProperties().get("nested"); | ||||||
|  |     assertEquals("nestedValue", nested.get("nestedKey")); | ||||||
|  |     List<String> list = | ||||||
|  |         (List<String>) appConfig.getParameters().getAdditionalProperties().get("list"); | ||||||
|  |     assertEquals("value1", list.get(1)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @Test | ||||||
|  |   public void testInvalidConfig() { | ||||||
|  |     ConfigurationReader reader = new ConfigurationReader(); | ||||||
|  |     assertThrows(RuntimeException.class, () -> reader.readConfigFromResource("InvalidConfig")); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @Test | ||||||
|  |   public void missingConfig() { | ||||||
|  |     ConfigurationReader reader = new ConfigurationReader(); | ||||||
|  |     assertThrows( | ||||||
|  |         IOException.class, | ||||||
|  |         () -> { | ||||||
|  |           reader.readConfigFromResource("missing"); | ||||||
|  |         }); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -0,0 +1,3 @@ | |||||||
|  | --- | ||||||
|  | - a | ||||||
|  | - b | ||||||
| @ -0,0 +1,10 @@ | |||||||
|  | parameters: | ||||||
|  |   key1: value1 | ||||||
|  |   key2: ${ENV_VAR} | ||||||
|  |   emptyKey: ${UNDEFINED_ENV_VAR:-""} | ||||||
|  |   defaultKey: ${UNDEFINED_ENV_VAR:-default} | ||||||
|  |   nested: | ||||||
|  |     nestedKey: ${NESTED_ENV_VAR} | ||||||
|  |   list: | ||||||
|  |     - elem1 | ||||||
|  |     - ${LIST_ENV_VAR} | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Imri Paran
						Imri Paran