Leverage an undocumented feature to set configurations from the environment

Hello all,

I’d like to add a configuration module etc/RT_SiteConfig.d/FromEnv.pm with the following code:

###########################################################################
# Load configurations from environment. Not all configurations can be set,
# only those in CamelCase (e.g. rtname cannot). Supported environment
# variables MUST start with RTCFG, then words are separated by underscores.
#
# Example: RTCFG_ORGANIZATION      --> Organization
# Example: RTCFG_WEB_PORT          --> WebPort
# Example: RTCFG_LOG_TO_FILE_NAMED --> LogToFileNamed
#
# Relies on RT::Config::SetFromConfig to behave like it does, which is
# unfortunately not documented.
for my $env_key (keys(%ENV)) {
   my ($leader, @parts) = split m{_}mxs, $env_key;
   next if $leader ne 'RTCFG';
   my $rt_key = join '', map { ucfirst(lc($_)) } @parts;
   Set($rt_key, $ENV{$env_key});
}
1;

This usage of function Set() (i.e. using variable $rt_key that holds the name of the configuration, instead of the “usual” $WebPort and $LogToFileNamed) within the configuration module above is not documented and relies on the behaviour of method RT::Config::SetFromConfig() which is not documented as well.

I’d like to avoid using something that works today but is potentially broken tomorrow, so I would either ask to clarify in the docs that this behaviour of Set() (i.e. using a variable holding the name of the configuration) is supported or ask how else I can inject configurations read from the environment.

My use case is adapting RT to be run in Kubernetes, where I expect that the database credentials will be stored inside a Secret and then propagated into the Pod by means of one or more environment variables. This might possibly expand to other configurations held in a ConfigMap.

Thanks!

Using variables like you have in the example is standard syntax for Perl. It isn’t an undocumented feature of Request Tracker. The config file is Perl. I think you should be fine.

What might be more complicated is injecting more complicate data structures. You may want to consider using ConfigMap to mount a config file into the container.

Thanks @Andrew_Ruthven for looking into it.

I’m aware that the config file is Perl, I’ll try to elaborate my question better.

The Set() function available in the configuration file is injected in the RT package just before calling the Perl code of the configuration file, along with a bunch of variables ($rtname, $Organization, …) that are visible in the scope of the configuration file. This makes it possible to set configurations like this:

Set($rtname, 'foo.example.com');
Set($Organization, 'foo.example.com');
Set($WebPort, 8080);
# and so on...

The docs describe all configuration options in terms of those variables (all configuration options in the configuration options manual page start with the $ sigil), so my impression is that this is the only documented/supported way of writing configurations in terms of Perl code.

All those variables available in the configuration files end up being references to other variables (SCALAR, ARRAY, and HASH), that hold the actual configurations for their respective types.

On the other hand, looking at the current implementation, the following is possible and has the same effect as the example above:

my $rt_key;

$rt_key = 'rtname';
Set($rt_key, 'foo.example.com');

$rt_key = 'Organization';
Set($rt_key, 'foo.example.com');

$rt_key = 'WebPort';
Set($rt_key, 8080);

# and so on...

This works today but I could not find any mention of this being a supported and future-proof way of setting configurations. It works because the Set() function available in the configuration file is a wrapper around the RT::Config::SetFromConfig() method, which enables this specific alternative name instead of reference here:

    my $type;
    my $name = Symbol::Global::Name->find($opt);
    if ($name) {
        $type = ref $opt;
        $name =~ s/.*:://;
    } else {
        $name = $$opt;
        $type = $META{$name}->{'Type'} || 'SCALAR';
    }

This method SetFromConfig() is not documented either.

This lack of documentation means that the implementation might change in the future, preserving the currently documented behaviour but not the alternative way of passing a variable holding the name of the configuration instead of a reference to the target variable. Hence, if I rely on this undocumented behaviour today I might have a bad surprise in the future.

I hope my question is clearer now: is this name instead of reference way of using Set() inside the configuration file future proof or should I look for something different?

Ah ha, yeah, thank you for the explanation, that certainly makes sense.

Certainly a question for Best Practical then.

Being to pass in the config as you’re doing here would certainly be useful in a containerised world.