Skip to content

Commit 05794a2

Browse files
committed
Allow setting feature flags via configuration parameter
- It requires database is set up because feature flags are stored to DB (table `application_settings`) - Add configuration parameter GITLAB_FEATURE_FLAGS_ENABLE_TARGETS and GITLAB_FEATURE_FLAGS_DISABLE_TARGETS - Add ruby script to configure feature flags from command line and invoke runtime (from configure_gitlab())
1 parent 846a051 commit 05794a2

File tree

5 files changed

+201
-2
lines changed

5 files changed

+201
-2
lines changed

README.md

+58
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
- [External Issue Trackers](#external-issue-trackers)
5151
- [Host UID / GID Mapping](#host-uid--gid-mapping)
5252
- [Piwik](#piwik)
53+
- [Feature flags](#feature-flags)
5354
- [Exposing ssh port in dockerized gitlab-ce](docs/exposing-ssh-port.md)
5455
- [Available Configuration Parameters](#available-configuration-parameters)
5556
- [Maintenance](#maintenance)
@@ -801,6 +802,52 @@ These options should contain something like:
801802
- `PIWIK_URL=piwik.example.org`
802803
- `PIWIK_SITE_ID=42`
803804

805+
#### Feature flags
806+
807+
In this section, we talk about feature flags that administrators can change the state (See <https://docs.gitlab.com/ee/administration/feature_flags.html>). If you are looking for documentation for "Feature flags" that configured on project deploy settings, see <https://docs.gitlab.com/ee/operations/feature_flags.html>
808+
809+
GitLab adopted feature flags strategies to deploy features in an early stage of development so that they can be incrementally rolled out. GitLab administrators with access to the [Rails console](https://docs.gitlab.com/ee/administration/feature_flags.html#how-to-enable-and-disable-features-behind-flags) or the [Feature flags API](https://docs.gitlab.com/ee/api/features.html) can control them (note that `sameersbn/gitlab` is a container image that provides GitLab installations from the source).
810+
You can see all feature flags in GitLab at corresponding version of documentation: <https://docs.gitlab.com/ee/user/feature_flags.html>
811+
812+
For `sameersbn/gitlab`, you can control them via environment parameter [`GITLAB_FEATURE_FLAGS_DISABLE_TARGETS`](#gitlab_feature_flags_disable_targets) and [`GITLAB_FEATURE_FLAGS_ENABLE_TARGETS`](#gitlab_feature_flags_enable_targets) in addition to the above methods.
813+
This image searches yml files in [`${GITLAB_INSTALL_DIR}/config/feature_flags`](https://gitlab.com/gitlab-org/gitlab-foss/-/tree/master/config/feature_flags) (typically `/home/git/gitlab/config/feature_flags/`) recursively and use the file list as a source of active feature flags.
814+
815+
Here is a part of example `docker-compose.yml`:
816+
817+
````yml
818+
services:
819+
gitlab:
820+
image: sameersbn/gitlab:latest
821+
environment:
822+
- GITLAB_FEATURE_FLAGS_DISABLE_TARGETS=auto_devops_banner_disabled,ci_enable_live_trace
823+
- GITLAB_FEATURE_FLAGS_ENABLE_TARGETS=git_push_create_all_pipelines,build_service_proxy
824+
````
825+
826+
Once the container up, you can see following messages in container log like below.
827+
828+
````sh
829+
...
830+
Configuring gitlab::feature_flags...
831+
- specified feature flags: {:to_be_disabled=>["auto_devops_banner_disabled", "ci_enable_live_trace"], :to_be_enabled=>["git_push_create_all_pipelines", "build_service_proxy"]}
832+
- auto_devops_banner_disabled : off
833+
- ci_enable_live_trace : off
834+
- git_push_create_all_pipelines : on
835+
- build_service_proxy : on
836+
...
837+
````
838+
839+
If specified flag names are not included in the list, they will be ignored and appears to container log like below:
840+
841+
````sh
842+
...
843+
Configuring gitlab::feature_flags...
844+
- specified feature flags: {:to_be_disabled=>["auto_devops_banner_disabled", "invalid_flag_name"], :to_be_enabled=>["git_push_create_all_pipelines", "another_invalid_flag_name"]}
845+
- Following flags are probably invalid and have been ignored: invalid_flag_name,another_invalid_flag_name
846+
- auto_devops_banner_disabled : off
847+
- git_push_create_all_pipelines : on
848+
...
849+
````
850+
804851
#### Available Configuration Parameters
805852

806853
*Please refer the docker run command options for the `--env-file` flag where you can specify all required environment variables in a single file. This will save you from writing a potentially long docker run command. Alternatively you can use docker-compose. docker-compose users and Docker Swarm mode users can also use the [secrets and config file options](#docker-secrets-and-configs)*
@@ -1619,6 +1666,17 @@ The value of the `worker-src` directive in the `Content-Security-Policy` header.
16191666

16201667
The value of the `report-uri` directive in the `Content-Security-Policy` header
16211668

1669+
##### `GITLAB_FEATURE_FLAGS_DISABLE_TARGETS`
1670+
1671+
Comma separated list of feature flag names to be disabled. No whitespace is allowed.
1672+
You can see all feature flags in GitLab at corresponding version of documentation: <https://docs.gitlab.com/ee/user/feature_flags.html>
1673+
Feature flags name and its statement will be appear to container log. Note that some of the feature flags are implicitly enabled or disabled by GitLab itself, and are not appear to container log.
1674+
No defaults.
1675+
1676+
##### `GITLAB_FEATURE_FLAGS_ENABLE_TARGETS`
1677+
1678+
This parameter is the same as [`GITLAB_FEATURE_FLAGS_DISABLE_TARGETS`](#gitlab_feature_flags_enable_targets), except its purpose is to enable the feature flag. No defaults.
1679+
16221680
##### `SSL_SELF_SIGNED`
16231681

16241682
Set to `true` when using self signed ssl certificates. `false` by default.

assets/runtime/env-defaults

+4
Original file line numberDiff line numberDiff line change
@@ -637,3 +637,7 @@ GITLAB_CONTENT_SECURITY_POLICY_DIRECTIVES_SCRIPT_SRC=${GITLAB_CONTENT_SECURITY_P
637637
GITLAB_CONTENT_SECURITY_POLICY_DIRECTIVES_STYLE_SRC=${GITLAB_CONTENT_SECURITY_POLICY_DIRECTIVES_STYLE_SRC:-"'self' 'unsafe-inline'"}
638638
GITLAB_CONTENT_SECURITY_POLICY_DIRECTIVES_WORKER_SRC=${GITLAB_CONTENT_SECURITY_POLICY_DIRECTIVES_WORKER_SRC:-"'self' blob:"}
639639
GITLAB_CONTENT_SECURITY_POLICY_DIRECTIVES_REPORT_URI=${GITLAB_CONTENT_SECURITY_POLICY_DIRECTIVES_REPORT_URI:-}
640+
641+
## Feature Flags
642+
GITLAB_FEATURE_FLAGS_DISABLE_TARGETS=${GITLAB_FEATURE_FLAGS_DISABLE_TARGETS:-}
643+
GITLAB_FEATURE_FLAGS_ENABLE_TARGETS=${GITLAB_FEATURE_FLAGS_ENABLE_TARGETS:-}

assets/runtime/functions

+48-1
Original file line numberDiff line numberDiff line change
@@ -2006,8 +2006,55 @@ configure_gitlab() {
20062006
rm -rf ${GITLAB_INSTALL_DIR}/tmp/sockets/gitlab.socket
20072007
}
20082008

2009+
# feature flags are recorded to database (schema "application_settings") so requires DB is (at least) initialized
2010+
gitlab_configure_feature_flags() {
2011+
echo "Configuring gitlab::feature_flags..."
2012+
2013+
if [[ -z "${GITLAB_FEATURE_FLAGS_ENABLE_TARGETS}" && -z "${GITLAB_FEATURE_FLAGS_ENABLE_TARGETS}" ]]; then
2014+
# Do nothing and reports no error if no targets specified
2015+
echo "- No targets specified. skipping..."
2016+
return 0
2017+
fi
2018+
2019+
# Build command line argument for script only when target is specified
2020+
# If not, scripts fails because option specifier is recognized as feature flags for example
2021+
# like "--disable --enable" : for this case, --disable is recognized as a value of option "--enable"
2022+
if [[ -n "${GITLAB_FEATURE_FLAGS_DISABLE_TARGETS}" ]]; then
2023+
GITLAB_FEATURE_FLAGS_DISABLE_TARGETS="--disable ${GITLAB_FEATURE_FLAGS_DISABLE_TARGETS}"
2024+
fi
2025+
# The same goes for --enable (this is the last option passed to "rails runner" that will be run below)
2026+
# For this case (final option), it throws "missing argument" error for execution like:
2027+
# like "--disable feature1,feature2 --enable"
2028+
if [[ -n "${GITLAB_FEATURE_FLAGS_ENABLE_TARGETS}" ]]; then
2029+
GITLAB_FEATURE_FLAGS_ENABLE_TARGETS="--enable ${GITLAB_FEATURE_FLAGS_ENABLE_TARGETS}"
2030+
fi
2031+
2032+
PWD_ORG=${PWD}
2033+
cd "${GITLAB_INSTALL_DIR}"
2034+
2035+
# copy the script to temporal directory : to avoid permission issue
2036+
cp "${GITLAB_RUNTIME_DIR}/scripts/configure_feature_flags.rb" "${GITLAB_TEMP_DIR}/"
2037+
chown "${GITLAB_USER}:" "${GITLAB_TEMP_DIR}/configure_feature_flags.rb"
2038+
2039+
echo "- Launching rails runner to set feature flags. This will take some time...."
2040+
2041+
# If arguments are empty, the script will do nothing and print object dump like below:
2042+
# - specified feature flags: {:to_be_disabled=>[], :to_be_enabled=>[]}
2043+
# DO NOT qupte variables : word splitting must be enabled.
2044+
# If disabled, whole string like '--disable feature_name_1,feature_name_2'
2045+
# will be recognized as single option and results to invalid argument error
2046+
#
2047+
# shellcheck disable=SC2086
2048+
exec_as_git bundle exec rails runner "${GITLAB_TEMP_DIR}/configure_feature_flags.rb" \
2049+
${GITLAB_FEATURE_FLAGS_DISABLE_TARGETS} \
2050+
${GITLAB_FEATURE_FLAGS_ENABLE_TARGETS}
2051+
2052+
rm "${GITLAB_TEMP_DIR}/configure_feature_flags.rb"
2053+
cd "${PWD_ORG}"
2054+
}
2055+
20092056
configure_gitlab_requires_db() {
2010-
:
2057+
gitlab_configure_feature_flags
20112058
}
20122059

20132060
configure_gitlab_shell() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env ruby
2+
3+
require "optparse"
4+
require "set"
5+
6+
# sameersbn/docker-gitlab
7+
# Ruby script to configure feature flags via CLI
8+
# Intended to be executed in the context of Rails Runner of Gitlab application
9+
# (to get valid "Feature" module, defined in (gitlab root)/lib/feature.rb)
10+
# https://guides.rubyonrails.org/command_line.html#bin-rails-runner
11+
# bundle exec rails runner <path to this script> -- --enable <enable target> --disable <disable target>
12+
13+
class FeatureFlagCLI
14+
def available_feature_flags()
15+
# Feature flag lists are stored in (Gitlab root directory)/config/feature_flags/
16+
# We can get the directory by accessing "root" property of "Gitlab" Module
17+
# (may returns /home/git/gitlab for sameersbn/docker-gitlab)
18+
feature_flag_yamls = Dir.glob("#{Gitlab.root}/config/feature_flags/**/*.yml")
19+
20+
if Gitlab.ee?
21+
feature_flag_yamls.concat(Dir.glob("#{Gitlab.root}/ee/config/feature_flags/**/*.yml"))
22+
end if
23+
24+
list = feature_flag_yamls.map { |p| File.basename(p, File.extname(p)) }
25+
list
26+
end
27+
28+
def parse_options(argv = ARGV)
29+
op = OptionParser.new
30+
31+
opts = {
32+
to_be_disabled: [],
33+
to_be_enabled: [],
34+
# TODO support "opt out", "opt out removed"
35+
# to_be_opted_out: [],
36+
# opt_out_removed: [],
37+
}
38+
39+
op.on("-d", "--disable feature_a,feature_b,feature_c", Array, "comma-separated list of feature flags to be disabled (defaults: ${opts[:to_be_disabled]})") { |v|
40+
opts[:to_be_disabled] = v.uniq
41+
}
42+
op.on("-e", "--enable feature_a,feature_b,feature_c", Array, "comma-separated list of feature flags to be enabled (defaults: ${opts[:to_be_enabled]})") { |v|
43+
opts[:to_be_enabled] = v.uniq
44+
}
45+
46+
begin
47+
args = op.parse(argv)
48+
succeed = true
49+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
50+
puts e.message
51+
puts op.help
52+
succeed = false
53+
end
54+
55+
[succeed, opts, args]
56+
end
57+
58+
def run
59+
succeed, opts, args = parse_options
60+
if succeed
61+
puts "- specified feature flags: #{opts.to_s}"
62+
63+
available_flags = self.available_feature_flags
64+
disable_targets = available_flags & opts[:to_be_disabled]
65+
enable_targets = available_flags & opts[:to_be_disabled]
66+
67+
disable_targets.each do |feature|
68+
Feature.disable(feature)
69+
end
70+
71+
enable_targets.each do |feature|
72+
Feature.enable(feature)
73+
end
74+
75+
invalid_enable_targets = opts[:to_be_enabled] - enable_targets
76+
invalid_disable_targets = opts[:to_be_disabled] - disable_targets
77+
invalid_targets = invalid_disable_targets | invalid_enable_targets
78+
if invalid_targets.length > 0
79+
puts "- Following flags are probably invalid and have been ignored: #{invalid_enable_targets.to_a.join(",")}"
80+
end
81+
end
82+
83+
Feature.all
84+
end
85+
end
86+
87+
features = FeatureFlagCLI.new.run
88+
puts features.map { |f|
89+
format("- feature %<name>s : %<state>s", name: f.name, state: f.state)
90+
}

entrypoint.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ case ${1} in
2121
/usr/bin/supervisord -nc /etc/supervisor/supervisord.conf &
2222
SUPERVISOR_PID=$!
2323
migrate_database
24-
configure_gitlab_requires_db
2524
kill -15 $SUPERVISOR_PID
2625
if ps h -p $SUPERVISOR_PID > /dev/null ; then
2726
wait $SUPERVISOR_PID || true
2827
fi
2928
rm -rf /var/run/supervisor.sock
29+
configure_gitlab_requires_db
3030
exec /usr/bin/supervisord -nc /etc/supervisor/supervisord.conf
3131
;;
3232
app:init)

0 commit comments

Comments
 (0)