-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Option to track build configuration for changes between builds #34713
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package io.quarkus.deployment.configuration.tracker; | ||
|
||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.regex.Pattern; | ||
|
||
import io.quarkus.runtime.annotations.ConfigPhase; | ||
import io.quarkus.runtime.annotations.ConfigRoot; | ||
import io.quarkus.util.GlobUtil; | ||
import io.smallrye.config.ConfigMapping; | ||
import io.smallrye.config.WithDefault; | ||
|
||
/** | ||
* Configuration options for application build time configuration usage tracking | ||
* and dumping. | ||
*/ | ||
@ConfigMapping(prefix = "quarkus.config-tracking") | ||
@ConfigRoot(phase = ConfigPhase.BUILD_TIME) | ||
public interface ConfigTrackingConfig { | ||
|
||
/** | ||
* Whether configuration dumping is enabled | ||
*/ | ||
@WithDefault("false") | ||
boolean enabled(); | ||
|
||
/** | ||
* Directory in which the configuration dump should be stored. | ||
* If not configured the {@code .quarkus} directory under the project directory will be used. | ||
*/ | ||
Optional<Path> directory(); | ||
|
||
/** | ||
* File in which the configuration dump should be stored. If not configured, the {@link #filePrefix} and | ||
* {@link #fileSuffix} will be used to generate the final file name. | ||
* If the configured file path is absolute, the {@link #directory} option will be ignored. Otherwise, | ||
* the path will be considered relative to the {@link #directory}. | ||
*/ | ||
Optional<Path> file(); | ||
|
||
/** | ||
* File name prefix. This option will be ignored in case {@link #file} is configured. | ||
*/ | ||
@WithDefault("quarkus") | ||
String filePrefix(); | ||
|
||
/** | ||
* File name suffix. This option will be ignored in case {@link #file} is configured. | ||
*/ | ||
@WithDefault("-config-dump") | ||
String fileSuffix(); | ||
|
||
/** | ||
* A list of config properties that should be excluded from the report. | ||
* GLOB patterns could be used instead of property names. | ||
*/ | ||
Optional<List<String>> exclude(); | ||
|
||
/** | ||
* Translates the value of {@link #exclude} to a list of {@link java.util.regex.Pattern}. | ||
* | ||
* @return list of patterns created from {@link #exclude} | ||
*/ | ||
default List<Pattern> getExcludePatterns() { | ||
return toPatterns(exclude()); | ||
} | ||
|
||
/** | ||
* A list of config properties whose values should be hashed in the report. | ||
* The values will be hashed using SHA-512 algorithm. | ||
* GLOB patterns could be used instead of property names. | ||
*/ | ||
Optional<List<String>> hashOptions(); | ||
|
||
/** | ||
* Translates the value of {@link #hashOptions()} to a list of {@link java.util.regex.Pattern}. | ||
* | ||
* @return list of patterns created from {@link #hashOptions()} | ||
*/ | ||
default List<Pattern> getHashOptionsPatterns() { | ||
return toPatterns(hashOptions()); | ||
} | ||
|
||
static List<Pattern> toPatterns(Optional<List<String>> globs) { | ||
if (globs.isEmpty()) { | ||
return List.of(); | ||
} | ||
var list = globs.get(); | ||
final List<Pattern> patterns = new ArrayList<>(list.size()); | ||
for (var s : list) { | ||
patterns.add(Pattern.compile(GlobUtil.toRegexPattern(s))); | ||
} | ||
return patterns; | ||
} | ||
|
||
/** | ||
* Whether to use a {@code ~} as an alias for user home directory in path values | ||
*/ | ||
@WithDefault("true") | ||
boolean useUserHomeAliasInPaths(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this seems out of place.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah sorry I read it as this was used for reading variables. ..so I grok the relevance but is there even a value in storing this as afaik we don't interpret ~ into ${user.home} when reading the values ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't tried using |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package io.quarkus.deployment.configuration.tracker; | ||
|
||
import static io.smallrye.config.SecretKeys.doLocked; | ||
|
||
import java.nio.file.Path; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
import jakarta.annotation.Priority; | ||
|
||
import org.eclipse.microprofile.config.Config; | ||
|
||
import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; | ||
import io.quarkus.runtime.LaunchMode; | ||
import io.smallrye.config.ConfigSourceInterceptor; | ||
import io.smallrye.config.ConfigSourceInterceptorContext; | ||
import io.smallrye.config.ConfigValue; | ||
import io.smallrye.config.Priorities; | ||
|
||
/** | ||
* Build configuration interceptor that records all the configuration options | ||
* and their values that are read during the build. | ||
*/ | ||
@Priority(Priorities.APPLICATION) | ||
public class ConfigTrackingInterceptor implements ConfigSourceInterceptor { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if we split the writing part from the interceptor? The interceptor could just record the values in a static Map, that could be accessed from anywhere. The writer's logic could then be extracted and applied when required. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it could be done better. I just didn't want to expose the interceptor itself with its public API to build steps. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made |
||
|
||
/** | ||
* A writer that persists collected configuration options and their values to a file | ||
*/ | ||
public interface ConfigurationWriter { | ||
void write(ConfigTrackingConfig config, BuildTimeConfigurationReader.ReadResult configReadResult, | ||
LaunchMode launchMode, Path buildDirectory); | ||
} | ||
|
||
/** | ||
* Provides an immutable map of options that were read during the build. | ||
*/ | ||
public interface ReadOptionsProvider { | ||
|
||
/** | ||
* An immutable map of options read during the build. | ||
* | ||
* @return immutable map of options read during the build | ||
*/ | ||
Map<String, String> getReadOptions(); | ||
} | ||
|
||
private boolean enabled; | ||
// it's a String value map to be able to represent null (not configured) values | ||
private Map<String, String> readOptions = Map.of(); | ||
private final ReadOptionsProvider readOptionsProvider = new ReadOptionsProvider() { | ||
@Override | ||
public Map<String, String> getReadOptions() { | ||
return Collections.unmodifiableMap(readOptions); | ||
} | ||
}; | ||
|
||
/** | ||
* Initializes the configuration tracker | ||
* | ||
* @param config configuration instance | ||
*/ | ||
public void configure(Config config) { | ||
enabled = config.getValue("quarkus.config-tracking.enabled", boolean.class); | ||
if (enabled) { | ||
readOptions = new ConcurrentHashMap<>(); | ||
} | ||
} | ||
|
||
@Override | ||
public ConfigValue getValue(ConfigSourceInterceptorContext context, String name) { | ||
if (!enabled) { | ||
return context.proceed(name); | ||
} | ||
final ConfigValue configValue = doLocked(() -> context.proceed(name)); | ||
readOptions.put(name, ConfigTrackingValueTransformer.asString(configValue)); | ||
return configValue; | ||
} | ||
|
||
/** | ||
* Read options orvipder. | ||
* | ||
* @return read options provider | ||
*/ | ||
public ReadOptionsProvider getReadOptionsProvider() { | ||
return readOptionsProvider; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you need to configure the interceptor, you can use a
ConfigSourceInterceptorFactory
instead. This provides you access to the context with the current config.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing that out. Given that I'd need to obtain the reference to the created interceptor, not sure whether it will end up being more elegant at the end though.