Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Okstad <pokstad@gitlab.com>2020-02-11 18:31:10 +0300
committerPaul Okstad <pokstad@gitlab.com>2020-02-11 18:31:10 +0300
commit974010a7802b73674d06288dda327dff69fb7e82 (patch)
tree0b36169df38e6f2edfac7fe202343c36f251e7a1
parentc01cdbf81f575b2319bae3cc7c6dd469432b916e (diff)
parent9e453d793eaae751e02e7f17a34c8b9bb61c5695 (diff)
Merge branch 'rs-ruby-feature-flags' into 'master'
Pass Ruby-specific feature flags to the Ruby server See merge request gitlab-org/gitaly!1818
-rw-r--r--changelogs/unreleased/rs-ruby-feature-flags.yml5
-rw-r--r--internal/rubyserver/proxy.go10
-rw-r--r--internal/rubyserver/proxy_test.go17
-rw-r--r--ruby/lib/gitaly_server.rb5
-rw-r--r--ruby/lib/gitaly_server/feature_flags.rb59
-rw-r--r--ruby/spec/lib/gitaly_server/feature_flags_spec.rb45
6 files changed, 141 insertions, 0 deletions
diff --git a/changelogs/unreleased/rs-ruby-feature-flags.yml b/changelogs/unreleased/rs-ruby-feature-flags.yml
new file mode 100644
index 000000000..c044dcb53
--- /dev/null
+++ b/changelogs/unreleased/rs-ruby-feature-flags.yml
@@ -0,0 +1,5 @@
+---
+title: Pass Ruby-specific feature flags to the Ruby server
+merge_request: 1818
+author:
+type: added
diff --git a/internal/rubyserver/proxy.go b/internal/rubyserver/proxy.go
index 7535ab681..cbe0947ec 100644
--- a/internal/rubyserver/proxy.go
+++ b/internal/rubyserver/proxy.go
@@ -15,6 +15,9 @@ import (
// forwarded as-is to gitaly-ruby.
var ProxyHeaderWhitelist = []string{"gitaly-servers"}
+// Headers prefixed with this string get whitelisted automatically
+const rubyFeaturePrefix = "gitaly-feature-ruby-"
+
const (
storagePathHeader = "gitaly-storage-path"
repoPathHeader = "gitaly-repo-path"
@@ -63,6 +66,13 @@ func setHeaders(ctx context.Context, repo *gitalypb.Repository, mustExist bool)
)
if inMD, ok := metadata.FromIncomingContext(ctx); ok {
+ // Automatically whitelist any Ruby-specific feature flag
+ for header := range inMD {
+ if strings.HasPrefix(header, rubyFeaturePrefix) {
+ ProxyHeaderWhitelist = append(ProxyHeaderWhitelist, header)
+ }
+ }
+
for _, header := range ProxyHeaderWhitelist {
for _, v := range inMD[header] {
md = metadata.Join(md, metadata.Pairs(header, v))
diff --git a/internal/rubyserver/proxy_test.go b/internal/rubyserver/proxy_test.go
index 5b804c061..03936f41c 100644
--- a/internal/rubyserver/proxy_test.go
+++ b/internal/rubyserver/proxy_test.go
@@ -43,3 +43,20 @@ func TestSetHeadersPreservesWhitelistedMetadata(t *testing.T) {
require.Equal(t, []string{value}, outMd[key], "outgoing MD should contain whitelisted key")
}
+
+func TestRubyFeatureHeaders(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ key := "gitaly-feature-ruby-test-feature"
+ value := "true"
+ inCtx := metadata.NewIncomingContext(ctx, metadata.Pairs(key, value))
+
+ outCtx, err := SetHeaders(inCtx, testRepo)
+ require.NoError(t, err)
+
+ outMd, ok := metadata.FromOutgoingContext(outCtx)
+ require.True(t, ok, "outgoing context should have metadata")
+
+ require.Equal(t, []string{value}, outMd[key], "outgoing MD should contain whitelisted feature key")
+}
diff --git a/ruby/lib/gitaly_server.rb b/ruby/lib/gitaly_server.rb
index a57f4dc24..acbfd2f96 100644
--- a/ruby/lib/gitaly_server.rb
+++ b/ruby/lib/gitaly_server.rb
@@ -13,6 +13,7 @@ require_relative 'gitaly_server/wiki_service.rb'
require_relative 'gitaly_server/conflicts_service.rb'
require_relative 'gitaly_server/remote_service.rb'
require_relative 'gitaly_server/health_service.rb'
+require_relative 'gitaly_server/feature_flags.rb'
module GitalyServer
STORAGE_PATH_HEADER = 'gitaly-storage-path'.freeze
@@ -37,6 +38,10 @@ module GitalyServer
call.metadata.fetch(REPO_ALT_DIRS_HEADER)
end
+ def self.feature_flags(call)
+ FeatureFlags.new(call.metadata)
+ end
+
def self.client(call)
Client.new(call.metadata[GITALY_SERVERS_HEADER])
end
diff --git a/ruby/lib/gitaly_server/feature_flags.rb b/ruby/lib/gitaly_server/feature_flags.rb
new file mode 100644
index 000000000..fcfa994f8
--- /dev/null
+++ b/ruby/lib/gitaly_server/feature_flags.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module GitalyServer
+ # Interface to Ruby-specific feature flags passed to the Gitaly Ruby server
+ # via headers.
+ class FeatureFlags
+ # Only headers prefixed with this String will be made available
+ HEADER_PREFIX = 'gitaly-feature-ruby-'
+
+ def initialize(metadata)
+ @flags = metadata.select do |key, _|
+ key.start_with?(HEADER_PREFIX)
+ end
+ end
+
+ # Check if a given flag is enabled
+ #
+ # The `gitaly-feature-ruby-` prefix is optional, and underscores are
+ # translated to hyphens automatically.
+ #
+ # Examples
+ #
+ # enabled?('gitaly-feature-ruby-my-flag')
+ # => true
+ #
+ # enabled?(:my_flag)
+ # => true
+ #
+ # enabled?('my-flag')
+ # => true
+ #
+ # enabled?(:unknown_flag)
+ # => false
+ def enabled?(flag)
+ flag = normalize_flag(flag)
+
+ @flags.fetch(flag, false) == 'true'
+ end
+
+ def disabled?(flag)
+ !enabled?(flag)
+ end
+
+ def inspect
+ pairs = @flags.map { |name, value| "#{name}=#{value}" }
+ pairs.unshift(self.class.name)
+
+ "#<#{pairs.join(' ')}>"
+ end
+
+ private
+
+ def normalize_flag(flag)
+ flag = flag.to_s.delete_prefix(HEADER_PREFIX).tr('_', '-')
+
+ "#{HEADER_PREFIX}#{flag}"
+ end
+ end
+end
diff --git a/ruby/spec/lib/gitaly_server/feature_flags_spec.rb b/ruby/spec/lib/gitaly_server/feature_flags_spec.rb
new file mode 100644
index 000000000..56a911bcb
--- /dev/null
+++ b/ruby/spec/lib/gitaly_server/feature_flags_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitalyServer::FeatureFlags do
+ describe '#enabled?' do
+ let(:metadata) do
+ {
+ "#{described_class::HEADER_PREFIX}some-feature" => 'true',
+ 'gitaly-storage-path' => 'foo',
+ 'gitaly-repo-path' => 'bar'
+ }
+ end
+
+ subject { described_class.new(metadata) }
+
+ it 'returns true for an enabled flag' do
+ expect(subject.enabled?(:some_feature)).to eq(true)
+ end
+
+ it 'returns false for an unknown flag' do
+ expect(subject.enabled?(:missing_feature)).to eq(false)
+ end
+
+ it 'removes the prefix if provided' do
+ expect(subject.enabled?(metadata.keys.first)).to eq(true)
+ end
+
+ it 'translates underscores' do
+ expect(subject.enabled?('some-feature')).to eq(true)
+ end
+ end
+
+ describe '#disabled?' do
+ it 'is the inverse of `enabled?`' do
+ instance = described_class.new({})
+
+ expect(instance).to receive(:enabled?)
+ .with(:some_feature)
+ .and_return(false)
+
+ expect(instance.disabled?(:some_feature)).to eq(true)
+ end
+ end
+end