diff --git a/.projections.json.example b/.projections.json.example
index f8db0051e33f2ed7cec00f743c123af4a4185914_LnByb2plY3Rpb25zLmpzb24uZXhhbXBsZQ==..120b23950e6893931eef61fecada8c9387ee8623_LnByb2plY3Rpb25zLmpzb24uZXhhbXBsZQ== 100644
--- a/.projections.json.example
+++ b/.projections.json.example
@@ -7,5 +7,29 @@
     "alternate": "lib/{}.rb",
     "type": "test",
     "dispatch": "bundle exec rspec {file}"
+  },
+  "files/gitlab-cookbooks/*.rb" : {
+    "alternate": "spec/chef/cookbooks/{}_spec.rb",
+    "type": "source"
+  },
+  "spec/chef/cookbooks/*_spec.rb": {
+    "alternate": "files/gitlab-cookbooks/{}.rb",
+    "type": "test"
+  },
+  "files/gitlab-ctl-commands/*.rb" : {
+    "alternate": "spec/chef/gitlab-ctl-commands/{}_spec.rb",
+    "type": "source"
+  },
+  "spec/chef/gitlab-ctl-commands/*_spec.rb": {
+    "alternate": "files/gitlab-ctl-commands/{}.rb",
+    "type": "test"
+  },
+  "files/gitlab-ctl-commands-ee/*.rb" : {
+    "alternate": "spec/chef/gitlab-ctl-commands-ee/{}_spec.rb",
+    "type": "source"
+  },
+  "spec/chef/gitlab-ctl-commands-ee/*_spec.rb": {
+    "alternate": "files/gitlab-ctl-commands-ee/{}.rb",
+    "type": "test"
   }
 }
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index f8db0051e33f2ed7cec00f743c123af4a4185914_LnJ1Ym9jb3BfdG9kby55bWw=..120b23950e6893931eef61fecada8c9387ee8623_LnJ1Ym9jb3BfdG9kby55bWw= 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -51,9 +51,9 @@
 # Cop supports --auto-correct.
 Layout/SpaceAroundMethodCallOperator:
   Exclude:
-    - 'spec/chef/recipes/postgresql_spec.rb'
-    - 'spec/gitlab-ctl-commands-ee/lib/postgresql/ee_spec.rb'
-    - 'spec/libraries/logging_helper_spec.rb'
+    - 'spec/chef/cookbooks/postgresql/recipes/postgresql_spec.rb'
+    - 'spec/chef/gitlab-ctl-commands-ee/lib/postgresql/ee_spec.rb'
+    - 'spec/chef/cookbooks/package/libraries/helpers/logging_helper_spec.rb'
 
 # Offense count: 1
 # Cop supports --auto-correct.
@@ -66,7 +66,7 @@
 Lint/ConstantDefinitionInBlock:
   Exclude:
     - 'files/gitlab-ctl-commands/upgrade.rb'
-    - 'spec/chef/secrets_spec.rb'
-    - 'spec/gitlab-ctl-commands/gitlab_ctl_spec.rb'
+    - 'spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb'
+    - 'spec/chef/gitlab-ctl-commands/gitlab_ctl_spec.rb'
     - 'spec/lib/gitlab/build/image_spec.rb'
     - 'spec/lib/gitlab/tasks/qa_spec.rb'
@@ -71,7 +71,7 @@
     - 'spec/lib/gitlab/build/image_spec.rb'
     - 'spec/lib/gitlab/tasks/qa_spec.rb'
-    - 'spec/support/shared_examples/geo_promotion_command_accepts_option_shared_examples.rb'
-    - 'spec/support/shared_examples/gitlab_geo_promotion_commands_shared_examples.rb'
+    - 'spec/chef/support/shared_examples/geo_promotion_command_accepts_option_shared_examples.rb'
+    - 'spec/chef/support/shared_examples/gitlab_geo_promotion_commands_shared_examples.rb'
 
 # Offense count: 1
 # Cop supports --auto-correct.
@@ -226,11 +226,11 @@
     - 'files/gitlab-ctl-commands-ee/lib/geo/replication.rb'
     - 'files/gitlab-ctl-commands/lib/gitlab_ctl/util.rb'
     - 'lib/gitlab/build/trigger.rb'
-    - 'spec/gitlab-ctl-commands-ee/lib/geo/promote_to_primary_node_spec.rb'
-    - 'spec/gitlab-ctl-commands-ee/lib/geo/promotion_preflight_checks_spec.rb'
-    - 'spec/gitlab-ctl-commands-ee/lib/geo/replication_spec.rb'
-    - 'spec/gitlab-ctl-commands/lib/gitlab_ctl_backup_spec.rb'
-    - 'spec/gitlab-ctl-commands/lib/util_spec.rb'
+    - 'spec/chef/gitlab-ctl-commands-ee/lib/geo/promote_to_primary_node_spec.rb'
+    - 'spec/chef/gitlab-ctl-commands-ee/lib/geo/promotion_preflight_checks_spec.rb'
+    - 'spec/chef/gitlab-ctl-commands-ee/lib/geo/replication_spec.rb'
+    - 'spec/chef/gitlab-ctl-commands/lib/gitlab_ctl_backup_spec.rb'
+    - 'spec/chef/gitlab-ctl-commands/lib/util_spec.rb'
 
 # Offense count: 1
 # Cop supports --auto-correct.
@@ -311,11 +311,11 @@
   Exclude:
     - 'files/gitlab-cookbooks/gitlab/libraries/registry.rb'
     - 'files/gitlab-cookbooks/package/libraries/helpers/redhat_helper.rb'
-    - 'spec/chef/recipes/add_trusted_certs_spec.rb'
-    - 'spec/chef/recipes/gitlab-workhorse_spec.rb'
-    - 'spec/chef/recipes/mailroom_spec.rb'
-    - 'spec/chef/recipes/puma_spec.rb'
-    - 'spec/chef/recipes/registry_spec.rb'
-    - 'spec/chef/recipes/sidekiq_spec.rb'
-    - 'spec/chef/recipes/unicorn_spec.rb'
+    - 'spec/chef/cookbooks/gitlab/recipes/add_trusted_certs_spec.rb'
+    - 'spec/chef/cookbooks/gitlab/recipes/gitlab-workhorse_spec.rb'
+    - 'spec/chef/cookbooks/gitlab/recipes/mailroom_spec.rb'
+    - 'spec/chef/cookbooks/gitlab/recipes/puma_spec.rb'
+    - 'spec/chef/cookbooks/registry/recipes/registry_spec.rb'
+    - 'spec/chef/cookbooks/gitlab/recipes/sidekiq_spec.rb'
+    - 'spec/chef/cookbooks/gitlab/recipes/unicorn_spec.rb'
     - 'spec/chef_helper.rb'
@@ -321,5 +321,5 @@
     - 'spec/chef_helper.rb'
-    - 'spec/gitlab-ctl-commands-ee/lib/geo/promote_to_primary_node_spec.rb'
+    - 'spec/chef/gitlab-ctl-commands-ee/lib/geo/promote_to_primary_node_spec.rb'
 
 # Offense count: 249
 # Cop supports --auto-correct.
diff --git a/spec/chef/consul/libraries/consul_helper_spec.rb b/spec/chef/consul/libraries/consul_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2NvbnN1bC9saWJyYXJpZXMvY29uc3VsX2hlbHBlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/consul/libraries/consul_helper_spec.rb
+++ /dev/null
@@ -1,162 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe ConsulHelper do
-  let(:chef_run) { converge_config }
-  subject { described_class.new(chef_run.node) }
-
-  describe '#running_version' do
-    let(:consul_api_output) { instance_double('response', code: '200', body: '{"Config": { "Version": "1.8.10" }}') }
-
-    before do
-      # Un-doing the stub added in chef_helper
-      allow_any_instance_of(described_class).to receive(:running_version).and_call_original
-      allow(Gitlab).to receive(:[]).and_call_original
-      allow(subject).to receive(:get_api).and_yield(consul_api_output)
-    end
-
-    context 'when consul is not running' do
-      it 'returns nil' do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('consul').and_return(false)
-
-        expect(subject.running_version).to be_nil
-      end
-    end
-
-    context 'when consul is running' do
-      before do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('consul').and_return(true)
-      end
-
-      it 'parses version from consul api output properly' do
-        expect(subject.running_version).to eq('1.8.10')
-      end
-    end
-  end
-
-  describe '#installed_version' do
-    let(:consul_cli_output) do
-      <<~MSG
-        Consul v1.7.8
-        Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)
-      MSG
-    end
-
-    before do
-      # Un-doing the stub added in chef_helper
-      allow_any_instance_of(described_class).to receive(:installed_version).and_call_original
-      allow(Gitlab).to receive(:[]).and_call_original
-      allow(VersionHelper).to receive(:version).with(/consul version/).and_return(consul_cli_output)
-    end
-
-    context 'when consul is not running' do
-      it 'returns nil' do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('consul').and_return(false)
-
-        expect(subject.installed_version).to be_nil
-      end
-    end
-
-    context 'when consul is running' do
-      before do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('consul').and_return(true)
-      end
-
-      it 'parses consul output properly' do
-        expect(subject.installed_version).to eq('1.7.8')
-      end
-    end
-  end
-
-  describe '#api_port' do
-    before do
-      allow(Gitlab).to receive(:[]).and_call_original
-    end
-
-    it 'falls back to default ports' do
-      stub_gitlab_rb(
-        consul: {
-          enable: true
-        }
-      )
-
-      expect(subject.api_port('http')).to eq(8500)
-      expect(subject.api_port('https')).to eq(8501)
-      expect(subject.configuration).not_to match(%r{"http":8500})
-      expect(subject.configuration).not_to match(%r{"https":8501})
-    end
-
-    it 'uses the configured ports' do
-      stub_gitlab_rb(
-        consul: {
-          enable: true,
-          http_port: 18500,
-          https_port: 18501
-        }
-      )
-
-      expect(subject.api_port('http')).to eq(18500)
-      expect(subject.api_port('https')).to eq(18501)
-      expect(subject.configuration).to match(%r{"http":18500})
-      expect(subject.configuration).to match(%r{"https":18501})
-    end
-
-    it 'uses the ports from configuration override' do
-      stub_gitlab_rb(
-        consul: {
-          enable: true,
-          http_port: 18500,
-          https_port: 18501,
-          configuration: {
-            ports: {
-              http: 28500,
-              https: 28501,
-            }
-          }
-        }
-      )
-
-      expect(subject.api_port('http')).to eq(28500)
-      expect(subject.api_port('https')).to eq(28501)
-      expect(subject.configuration).to match(%r{"http":28500})
-      expect(subject.configuration).to match(%r{"https":28501})
-    end
-  end
-
-  describe '#api_url' do
-    before do
-      allow(Gitlab).to receive(:[]).and_call_original
-    end
-
-    it 'uses http by default' do
-      stub_gitlab_rb(
-        consul: {
-          enable: true
-        }
-      )
-
-      expect(subject.api_url).to eq('http://localhost:8500')
-    end
-
-    it 'uses https when tls is enabled' do
-      stub_gitlab_rb(
-        consul: {
-          enable: true,
-          use_tls: true
-        }
-      )
-
-      expect(subject.api_url).to eq('https://localhost:8501')
-    end
-
-    it 'uses https when http port is disabled' do
-      stub_gitlab_rb(
-        consul: {
-          enable: true,
-          http_port: -1
-        }
-      )
-
-      expect(subject.api_url).to eq('https://localhost:8501')
-    end
-  end
-end
diff --git a/spec/chef/consul/libraries/failover_helper_spec.rb b/spec/chef/consul/libraries/failover_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2NvbnN1bC9saWJyYXJpZXMvZmFpbG92ZXJfaGVscGVyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/consul/libraries/failover_helper_spec.rb
+++ /dev/null
@@ -1,176 +0,0 @@
-require 'chef_helper'
-
-require 'json'
-require 'logger'
-
-NodeData = Struct.new('NodeData', :name, :address, :leader?)
-
-RSpec.describe FailoverHelper::SplitBrain do
-  let(:node_list) { %w(cats kittens) }
-
-  context 'when passed a list of nodes' do
-    it 'sets primary_nodes to that list' do
-      brain = FailoverHelper::SplitBrain.new("Nobody here but us felines", node_list)
-      expect(brain.primary_nodes).to match_array(node_list)
-    end
-  end
-end
-
-RSpec.describe FailoverHelper::LeaderFinder do
-  let(:total_nodes) { 3 }
-  let(:failover) { FailoverHelper::LeaderFinder.new("{}", service_data) }
-  let(:primary_node) { NodeData.new("example_primary_node", "10.0.0.42", true) }
-  let(:splitbrain_primary_node) { NodeData.new("example_splitbrain_node", "10.0.0.41", true) }
-  let(:follower_node_01) { NodeData.new("example_node_02", "10.0.0.43", false) }
-  let(:follower_node_02) { NodeData.new("example_node_03", "10.0.0.44", false) }
-
-  def service_data
-    service_data = FailoverHelper::ServiceData.new
-    service_data.service_name = "example_service"
-    service_data.check_field = "Status"
-    service_data.leader_value = "passing"
-
-    service_data
-  end
-
-  def generate_parsed_data(unhealthy_primary: false, multiple_primaries: false)
-    parsed_data = []
-    nodes = [primary_node, follower_node_01, follower_node_02]
-
-    nodes.push(splitbrain_primary_node) if multiple_primaries
-
-    nodes.each do |node|
-      if node.leader?
-        leader_status = "passing"
-        health = unhealthy_primary ? "failing" : "passing"
-      else
-        leader_status = "warning"
-        health = "passing"
-      end
-
-      node_info = {
-        "Node" => {
-          "Node" => node.name,
-          "Address" => node.address
-        },
-        "Checks" => [
-          {
-            "Node" => node.name,
-            "CheckID" => "serfHealth",
-            "Status" => health
-          },
-          {
-            "Node" => node.name,
-            "CheckID" => "example_service",
-            "Status" => leader_status
-          }
-        ]
-      }
-
-      parsed_data.push(node_info)
-    end
-
-    parsed_data
-  end
-
-  def stub_healthy_primary_watcher_data
-    parsed_data = generate_parsed_data
-
-    allow_any_instance_of(FailoverHelper::LeaderFinder).to receive(:parse).and_return(parsed_data)
-  end
-
-  def stub_unhealthy_primary_watcher_data
-    parsed_data = generate_parsed_data(unhealthy_primary: true)
-
-    allow_any_instance_of(FailoverHelper::LeaderFinder).to receive(:parse).and_return(parsed_data)
-  end
-
-  def stub_multiple_primary_nodes_watcher_data
-    parsed_data = generate_parsed_data(multiple_primaries: true)
-
-    allow_any_instance_of(FailoverHelper::LeaderFinder).to receive(:parse).and_return(parsed_data)
-  end
-
-  describe "#parse" do
-    let(:bad_json) { "Nobody here but us errors!" }
-
-    context 'when passed invalid JSON data' do
-      it 'throws a JSON::Parser error' do
-        expect { FailoverHelper::LeaderFinder.new(bad_json, service_data) }.to raise_error(JSON::ParserError)
-      end
-    end
-  end
-
-  context 'when valid JSON is received from watch' do
-    context 'when there is more than one primary node found' do
-      before do
-        stub_multiple_primary_nodes_watcher_data
-      end
-
-      describe "#primary_node" do
-        it 'throws a SplitBrain error' do
-          expect { failover.primary_node }.to raise_error(FailoverHelper::SplitBrain, "Split brain detected, multiple primary nodes found!")
-        end
-      end
-    end
-
-    context 'when all nodes are healthy' do
-      before do
-        stub_healthy_primary_watcher_data
-      end
-
-      describe "#healthy_nodes" do
-        it 'returns the correct number of healthy nodes' do
-          expect(failover.healthy_nodes.length).to eq(total_nodes)
-        end
-      end
-
-      describe "#leader_nodes" do
-        context 'when there is one leader' do
-          it 'returns only one leader' do
-            expect(failover.leader_nodes.length).to eq(1)
-          end
-
-          it 'returns the correct node' do
-            primary_node = failover.leader_nodes.first
-            expect(primary_node.name).to eq(primary_node.name)
-          end
-        end
-      end
-
-      describe "#primary_node_address" do
-        context 'when the node name resolves via DNS' do
-          it 'returns the machine name' do
-            allow_any_instance_of(Resolv::DNS).to receive(:getaddress).and_return(true)
-            expect(failover.primary_node_address).to eq(primary_node.name)
-          end
-        end
-
-        context 'when the node name does not resolve via DNS' do
-          it 'returns the machine IP address' do
-            allow_any_instance_of(Resolv::DNS).to receive(:getaddress).and_raise(Resolv::ResolvError)
-            expect(failover.primary_node_address).to eq(primary_node.address)
-          end
-        end
-      end
-    end
-
-    context 'when the primary node is not healthy in a single cluster setup' do
-      before do
-        stub_unhealthy_primary_watcher_data
-      end
-
-      describe "#healthy_nodes" do
-        it 'returns the correct number of healthy nodes' do
-          expect(failover.healthy_nodes.length).to eq(total_nodes - 1)
-        end
-      end
-
-      describe "#leader_nodes" do
-        it 'throws a PrimaryMissing exception' do
-          expect { failover.leader_nodes }.to raise_error(FailoverHelper::PrimaryMissing, "No healthy primary node found.")
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/consul/recipes/configure_services_spec.rb b/spec/chef/consul/recipes/configure_services_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2NvbnN1bC9yZWNpcGVzL2NvbmZpZ3VyZV9zZXJ2aWNlc19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/consul/recipes/configure_services_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'consul::configure_services' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe 'enabling services' do
-    before do
-      stub_gitlab_rb(
-        consul: {
-          enable: true,
-          services: %w(postgresql)
-        }
-      )
-    end
-
-    it 'includes the enable_service recipe for the corresponding service' do
-      expect(chef_run).to include_recipe('consul::enable_service_postgresql')
-    end
-  end
-
-  describe 'disabling services' do
-    before do
-      stub_gitlab_rb(
-        consul: {
-          enable: true,
-          services: []
-        }
-      )
-    end
-
-    it 'includes the disable_service for all known services' do
-      expect(chef_run).to include_recipe('consul::disable_service_postgresql')
-    end
-  end
-end
diff --git a/spec/chef/consul/recipes/consul_spec.rb b/spec/chef/consul/recipes/consul_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2NvbnN1bC9yZWNpcGVzL2NvbnN1bF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/consul/recipes/consul_spec.rb
+++ /dev/null
@@ -1,359 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'consul' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
-  let(:consul_conf) { '/var/opt/gitlab/consul/config.json' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'disabled by default' do
-    it 'includes the disable recipe' do
-      expect(chef_run).to include_recipe('consul::disable')
-    end
-  end
-
-  describe 'consul::disable' do
-    it_behaves_like 'disabled runit service', 'consul'
-  end
-
-  context 'when enabled' do
-    before do
-      stub_gitlab_rb(
-        consul: {
-          enable: true,
-          config_dir: '/fake/config.d',
-          data_dir: '/fake/data',
-          custom_config_dir: '/custom/dir'
-        }
-      )
-    end
-
-    it 'includes the enable recipe' do
-      expect(chef_run).to include_recipe('consul::enable')
-    end
-
-    describe 'consul::enable' do
-      it_behaves_like 'enabled runit service', 'consul', 'gitlab-consul', 'gitlab-consul', 'gitlab-consul', 'gitlab-consul'
-
-      it 'creates the consul system user and group' do
-        expect(chef_run).to create_account('Consul user and group').with(username: 'gitlab-consul', groupname: 'gitlab-consul')
-      end
-
-      it 'includes the configure_services recipe' do
-        expect(chef_run).to include_recipe('consul::configure_services')
-      end
-
-      it 'only enables the agent by default' do
-        expect(chef_run).to render_file(consul_conf).with_content { |content|
-          expect(content).to match(%r{"server":false})
-        }
-      end
-
-      it 'does not include nil values in its configuration' do
-        expect(chef_run).to render_file(consul_conf).with_content { |content|
-          expect(content).not_to match(%r{"encryption":})
-        }
-      end
-
-      it 'does not include server default values in its configuration' do
-        expect(chef_run).to render_file(consul_conf).with_content { |content|
-          expect(content).not_to match(%r{"bootstrap_expect":3})
-        }
-      end
-
-      it 'creates the necessary directories' do
-        expect(chef_run).to create_directory('/fake/config.d')
-        expect(chef_run).to create_directory('/fake/data')
-        expect(chef_run).to create_directory('/var/log/gitlab/consul')
-      end
-
-      it 'notifies other resources on configuration change' do
-        config_json = chef_run.file('/var/opt/gitlab/consul/config.json')
-        expect(config_json).to notify('execute[reload consul]').to(:run)
-        expect(config_json).to notify('ruby_block[consul config change]').to(:run)
-      end
-
-      it 'renders run file with specified options' do
-        expect(chef_run).to render_file('/opt/gitlab/sv/consul/run').with_content(%r{-config-dir /fake/config.d})
-        expect(chef_run).to render_file('/opt/gitlab/sv/consul/run').with_content(%r{-config-dir /custom/dir})
-        expect(chef_run).to render_file('/opt/gitlab/sv/consul/run').with_content(%r{-data-dir /fake/data})
-      end
-    end
-
-    context 'with default options' do
-      it 'allows the user to specify node name' do
-        stub_gitlab_rb(
-          consul: {
-            enable: true
-          }
-        )
-        expect(chef_run).to render_file(consul_conf).with_content { |content|
-          expect(content).to match(%r{"datacenter":"gitlab_consul"})
-          expect(content).to match(%r{"disable_update_check":true})
-          expect(content).to match(%r{"enable_script_checks":true})
-          expect(content).to match(%r{"node_name":"fauxhai.local"})
-          expect(content).to match(%r{"rejoin_after_leave":true})
-          expect(content).to match(%r{"server":false})
-        }
-      end
-    end
-
-    context 'with non-default options' do
-      before do
-        stub_gitlab_rb(
-          consul: {
-            enable: true,
-            node_name: 'fakenodename',
-            username: 'foo',
-            group: 'bar',
-          }
-        )
-      end
-
-      it 'allows the user to specify node name' do
-        expect(chef_run).to render_file(consul_conf).with_content('"node_name":"fakenodename"')
-      end
-
-      it 'creates the consul system user and group' do
-        expect(chef_run).to create_account('Consul user and group').with(username: 'foo', groupname: 'bar')
-      end
-
-      it_behaves_like 'enabled runit service', 'consul', 'foo', 'bar', 'foo', 'bar'
-    end
-
-    context 'server enabled' do
-      before do
-        stub_gitlab_rb(
-          consul: {
-            enable: true,
-            configuration: {
-              server: true
-            }
-          }
-        )
-      end
-
-      it 'enables the server functionality' do
-        expect(chef_run.node['consul']['configuration']['server']).to eq true
-        expect(chef_run).to render_file(consul_conf).with_content { |content|
-          expect(content).to match(%r{"server":true})
-          expect(content).to match(%r{"bootstrap_expect":3})
-        }
-      end
-    end
-
-    describe 'pending restart check' do
-      context 'when running version is same as installed version' do
-        before do
-          allow_any_instance_of(ConsulHelper).to receive(:running_version).and_return('1.9.6')
-          allow_any_instance_of(ConsulHelper).to receive(:installed_version).and_return('1.9.6')
-        end
-
-        it 'does not raise a warning' do
-          expect(chef_run).not_to run_ruby_block('warn pending consul restart')
-        end
-      end
-
-      context 'when running version is different than installed version' do
-        before do
-          allow_any_instance_of(ConsulHelper).to receive(:running_version).and_return('1.6.4')
-          allow_any_instance_of(ConsulHelper).to receive(:installed_version).and_return('1.9.6')
-        end
-
-        it 'raises a warning' do
-          expect(chef_run).to run_ruby_block('warn pending consul restart')
-        end
-      end
-    end
-  end
-
-  describe 'encryption' do
-    it 'is not enabled by default' do
-      stub_gitlab_rb(
-        consul: {
-          enable: true,
-        }
-      )
-
-      expect(chef_run).to render_file(consul_conf).with_content { |content|
-        expect(content).not_to match(%r{"encrypt":})
-        expect(content).not_to match(%r{"encrypt_verify_incoming":})
-        expect(content).not_to match(%r{"encrypt_verify_outgoing":})
-      }
-    end
-
-    context 'new datacenter' do
-      it 'uses encryption key and falls back to defaults' do
-        stub_gitlab_rb(
-          consul: {
-            enable: true,
-            encryption_key: 'fake_key'
-          }
-        )
-
-        expect(chef_run).to render_file(consul_conf).with_content { |content|
-          expect(content).to match(%r{"encrypt":"fake_key"})
-          expect(content).not_to match(%r{"encrypt_verify_incoming":})
-          expect(content).not_to match(%r{"encrypt_verify_outgoing":})
-        }
-      end
-    end
-
-    context 'existing datacenter' do
-      it 'uses encryption key and specified verification settings' do
-        stub_gitlab_rb(
-          consul: {
-            enable: true,
-            encryption_key: 'fake_key',
-            encryption_verify_incoming: false,
-            encryption_verify_outgoing: true,
-          }
-        )
-
-        expect(chef_run).to render_file(consul_conf).with_content { |content|
-          expect(content).to match(%r{"encrypt":"fake_key"})
-          expect(content).to match(%r{"encrypt_verify_incoming":false})
-          expect(content).to match(%r{"encrypt_verify_outgoing":true})
-        }
-      end
-    end
-
-    describe 'secure configuration' do
-      context 'client mode' do
-        it 'use tls for outgoing' do
-          stub_gitlab_rb(
-            consul: {
-              enable: true,
-              use_tls: true,
-              tls_ca_file: '/fake/ca.crt.pem',
-            }
-          )
-
-          expect(chef_run).to render_file(consul_conf).with_content { |content|
-            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
-            expect(content).to match(%r{"verify_outgoing":true})
-            expect(content).to match(%r{"verify_incoming":false})
-            expect(content).not_to match(%r{"https":8501})
-            expect(content).not_to match(%r{"cert_file":})
-            expect(content).not_to match(%r{"key_file":})
-          }
-        end
-
-        it 'use tls client authentication' do
-          stub_gitlab_rb(
-            consul: {
-              enable: true,
-              use_tls: true,
-              tls_ca_file: '/fake/ca.crt.pem',
-              tls_certificate_file: '/fake/client.crt.pem',
-              tls_key_file: '/fake/client.key.pem'
-            }
-          )
-
-          expect(chef_run).to render_file(consul_conf).with_content { |content|
-            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
-            expect(content).to match(%r{"cert_file":"/fake/client.crt.pem"})
-            expect(content).to match(%r{"key_file":"/fake/client.key.pem"})
-            expect(content).to match(%r{"verify_outgoing":true})
-            expect(content).to match(%r{"verify_incoming":false})
-            expect(content).not_to match(%r{"https":8501})
-          }
-        end
-
-        it 'use tls for incoming and outgoing' do
-          stub_gitlab_rb(
-            consul: {
-              enable: true,
-              use_tls: true,
-              tls_ca_file: '/fake/ca.crt.pem',
-              tls_certificate_file: '/fake/server.crt.pem',
-              tls_key_file: '/fake/server.key.pem',
-              https_port: 8501,
-            }
-          )
-
-          expect(chef_run).to render_file(consul_conf).with_content { |content|
-            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
-            expect(content).to match(%r{"cert_file":"/fake/server.crt.pem"})
-            expect(content).to match(%r{"key_file":"/fake/server.key.pem"})
-            expect(content).to match(%r{"verify_outgoing":true})
-            expect(content).to match(%r{"verify_incoming":false})
-            expect(content).to match(%r{"https":8501})
-          }
-        end
-
-        it 'use tls for incoming and outgoing and verify incoming' do
-          stub_gitlab_rb(
-            consul: {
-              enable: true,
-              use_tls: true,
-              tls_ca_file: '/fake/ca.crt.pem',
-              tls_certificate_file: '/fake/server.crt.pem',
-              tls_key_file: '/fake/server.key.pem',
-              tls_verify_client: true,
-              https_port: 8501
-            }
-          )
-
-          expect(chef_run).to render_file(consul_conf).with_content { |content|
-            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
-            expect(content).to match(%r{"cert_file":"/fake/server.crt.pem"})
-            expect(content).to match(%r{"key_file":"/fake/server.key.pem"})
-            expect(content).to match(%r{"verify_outgoing":true})
-            expect(content).to match(%r{"verify_incoming":true})
-            expect(content).to match(%r{"https":8501})
-          }
-        end
-      end
-
-      context 'server mode' do
-        it 'use tls for incoming and outgoing and verify incoming by default' do
-          stub_gitlab_rb(
-            consul: {
-              enable: true,
-              configuration: { server: true },
-              use_tls: true,
-              tls_ca_file: '/fake/ca.crt.pem',
-              tls_certificate_file: '/fake/server.crt.pem',
-              tls_key_file: '/fake/server.key.pem'
-            }
-          )
-
-          expect(chef_run).to render_file(consul_conf).with_content { |content|
-            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
-            expect(content).to match(%r{"cert_file":"/fake/server.crt.pem"})
-            expect(content).to match(%r{"key_file":"/fake/server.key.pem"})
-            expect(content).to match(%r{"verify_outgoing":true})
-            expect(content).to match(%r{"verify_incoming":true})
-            expect(content).to match(%r{"https":8501})
-          }
-        end
-
-        it 'use tls for incoming and outgoing without verifying incoming' do
-          stub_gitlab_rb(
-            consul: {
-              enable: true,
-              configuration: { server: true },
-              use_tls: true,
-              tls_ca_file: '/fake/ca.crt.pem',
-              tls_certificate_file: '/fake/server.crt.pem',
-              tls_key_file: '/fake/server.key.pem',
-              tls_verify_client: false
-            }
-          )
-
-          expect(chef_run).to render_file(consul_conf).with_content { |content|
-            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
-            expect(content).to match(%r{"cert_file":"/fake/server.crt.pem"})
-            expect(content).to match(%r{"key_file":"/fake/server.key.pem"})
-            expect(content).to match(%r{"verify_outgoing":true})
-            expect(content).to match(%r{"verify_incoming":false})
-            expect(content).to match(%r{"https":8501})
-          }
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/consul/recipes/consul_watcher_spec.rb b/spec/chef/consul/recipes/consul_watcher_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2NvbnN1bC9yZWNpcGVzL2NvbnN1bF93YXRjaGVyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/consul/recipes/consul_watcher_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'consul::watchers' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-  let(:watcher_conf) { '/var/opt/gitlab/consul/config.d/watcher_postgresql.json' }
-  let(:watcher_check) { '/var/opt/gitlab/consul/scripts/failover_pgbouncer' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-
-    stub_gitlab_rb(
-      consul: {
-        enable: true,
-        watchers: %w(postgresql)
-      }
-    )
-  end
-
-  it 'includes the watcher recipe' do
-    expect(chef_run).to include_recipe('consul::watchers')
-  end
-
-  it 'creates the watcher config file' do
-    rendered = {
-      'watches' => [
-        {
-          'type' => 'service',
-          'service' => 'postgresql',
-          'args' => [watcher_check]
-        }
-      ]
-    }
-
-    expect(chef_run).to render_file(watcher_conf).with_content { |content|
-      expect(JSON.parse(content)).to eq(rendered)
-    }
-  end
-
-  it 'creates the watcher handler file' do
-    expect(chef_run).to render_file(watcher_check)
-  end
-end
diff --git a/spec/chef/consul/recipes/disable_service_postgresql_spec.rb b/spec/chef/consul/recipes/disable_service_postgresql_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2NvbnN1bC9yZWNpcGVzL2Rpc2FibGVfc2VydmljZV9wb3N0Z3Jlc3FsX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/consul/recipes/disable_service_postgresql_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'consul::disable_service_postgresql' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default', 'consul::disable_service_postgresql') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'default' do
-    before do
-      stub_gitlab_rb(
-        consul: {
-          enable: true,
-          services: []
-        }
-      )
-    end
-
-    it 'deletes the service configuration file' do
-      expect(chef_run).to delete_file('/var/opt/gitlab/consul/config.d/postgresql_service.json')
-    end
-  end
-end
diff --git a/spec/chef/consul/recipes/enable_service_postgresq_spec.rb b/spec/chef/consul/recipes/enable_service_postgresq_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2NvbnN1bC9yZWNpcGVzL2VuYWJsZV9zZXJ2aWNlX3Bvc3RncmVzcV9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/consul/recipes/enable_service_postgresq_spec.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'consul::enable_service_postgresql' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'default' do
-    context 'on a regular cluster' do
-      before do
-        stub_gitlab_rb(
-          patroni: {
-            enable: true
-          },
-          consul: {
-            enable: true,
-            services: %w(postgresql)
-          }
-        )
-      end
-
-      it 'renders the service configuration file' do
-        rendered = {
-          'service' => {
-            'name' => 'postgresql',
-            'address' => '',
-            'port' => 5432,
-            'check' => {
-              'id' => 'service:postgresql',
-              'args' => ['/opt/gitlab/bin/gitlab-ctl', 'patroni', 'check-leader'],
-              'interval' => '10s',
-              'status' => 'failing'
-            }
-          }
-        }
-        expect(chef_run).to render_file('/var/opt/gitlab/consul/config.d/postgresql_service.json').with_content { |content|
-          expect(JSON.parse(content)).to eq(rendered)
-        }
-      end
-    end
-
-    context 'on a standby cluster' do
-      before do
-        stub_gitlab_rb(
-          patroni: {
-            enable: true,
-            standby_cluster: {
-              enable: true
-            }
-          },
-          consul: {
-            enable: true,
-            services: %w(postgresql)
-          }
-        )
-      end
-
-      it 'renders the service configuration file' do
-        rendered = {
-          'service' => {
-            'name' => 'postgresql',
-            'address' => '',
-            'port' => 5432,
-            'check' => {
-              'id' => 'service:postgresql',
-              'args' => ['/opt/gitlab/bin/gitlab-ctl', 'patroni', 'check-standby-leader'],
-              'interval' => '10s',
-              'status' => 'failing'
-            }
-          }
-        }
-        expect(chef_run).to render_file('/var/opt/gitlab/consul/config.d/postgresql_service.json').with_content { |content|
-          expect(JSON.parse(content)).to eq(rendered)
-        }
-      end
-    end
-  end
-end
diff --git a/spec/chef/consul/resources/consul_service_spec.rb b/spec/chef/consul/resources/consul_service_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2NvbnN1bC9yZXNvdXJjZXMvY29uc3VsX3NlcnZpY2Vfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/consul/resources/consul_service_spec.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'consul_service' do
-  let(:runner) do
-    ChefSpec::SoloRunner.new(step_into: %w(consul_service))
-  end
-
-  context 'create' do
-    before do
-      runner.node.automatic['ipaddress'] = '10.1.1.1'
-    end
-
-    context 'with service address and port properties' do
-      let(:chef_run) { runner.converge('test_consul::consul_service_address_port') }
-
-      it 'creates the Consul service file' do
-        expect(chef_run).to render_file('/var/opt/gitlab/consul/config.d/node-exporter-service.json')
-          .with_content('{"service":{"name":"node-exporter","address":"10.1.1.1","port":1234}}')
-      end
-
-      it 'notifies the Consul service to reload' do
-        expect(chef_run.file('/var/opt/gitlab/consul/config.d/node-exporter-service.json'))
-          .to notify 'execute[reload consul]'
-      end
-    end
-
-    context 'with a socket property and no reload' do
-      let(:chef_run) { runner.converge('test_consul::consul_service_socket') }
-
-      it 'creates the Consul service file' do
-        expect(chef_run).to render_file('/var/opt/gitlab/consul/config.d/node-exporter-service.json')
-          .with_content('{"service":{"name":"node-exporter","address":"10.1.1.1","port":5678}}')
-      end
-
-      it 'does not notify the Consul service to reload' do
-        expect(chef_run.file('/var/opt/gitlab/consul/config.d/node-exporter-service.json'))
-          .not_to notify 'execute[reload consul]'
-      end
-    end
-
-    context 'with symantic metadata' do
-      let(:chef_run) { runner.converge('test_consul::consul_service_meta') }
-
-      it 'creates the Consul service file' do
-        expect(chef_run).to render_file('/var/opt/gitlab/consul/config.d/node-exporter-service.json')
-          .with_content('{"service":{"name":"node-exporter","address":"10.1.1.1","port":1234,"meta":{"some_key":"value"}}}')
-      end
-    end
-  end
-
-  context 'delete' do
-    context 'default do reload' do
-      let(:chef_run) { runner.converge('test_consul::consul_service_delete') }
-
-      it 'deletes the Consul service file' do
-        expect(chef_run).to delete_file('/var/opt/gitlab/consul/config.d/delete-me-service.json')
-      end
-
-      it 'notifies the Consul service to reload' do
-        expect(chef_run.file('/var/opt/gitlab/consul/config.d/delete-me-service.json'))
-          .to notify 'execute[reload consul]'
-      end
-    end
-
-    context 'do not reload' do
-      let(:chef_run) { runner.converge('test_consul::consul_service_delete_no_reload') }
-
-      it 'does not notify the Consul service to reload' do
-        expect(chef_run.file('/var/opt/gitlab/consul/config.d/delete-no-reload-service.json'))
-          .not_to notify 'execute[reload consul]'
-      end
-    end
-  end
-end
diff --git a/spec/chef/cookbooks/consul/libraries/consul_helper_spec.rb b/spec/chef/cookbooks/consul/libraries/consul_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9jb25zdWwvbGlicmFyaWVzL2NvbnN1bF9oZWxwZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/consul/libraries/consul_helper_spec.rb
@@ -0,0 +1,162 @@
+require 'chef_helper'
+
+RSpec.describe ConsulHelper do
+  let(:chef_run) { converge_config }
+  subject { described_class.new(chef_run.node) }
+
+  describe '#running_version' do
+    let(:consul_api_output) { instance_double('response', code: '200', body: '{"Config": { "Version": "1.8.10" }}') }
+
+    before do
+      # Un-doing the stub added in chef_helper
+      allow_any_instance_of(described_class).to receive(:running_version).and_call_original
+      allow(Gitlab).to receive(:[]).and_call_original
+      allow(subject).to receive(:get_api).and_yield(consul_api_output)
+    end
+
+    context 'when consul is not running' do
+      it 'returns nil' do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('consul').and_return(false)
+
+        expect(subject.running_version).to be_nil
+      end
+    end
+
+    context 'when consul is running' do
+      before do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('consul').and_return(true)
+      end
+
+      it 'parses version from consul api output properly' do
+        expect(subject.running_version).to eq('1.8.10')
+      end
+    end
+  end
+
+  describe '#installed_version' do
+    let(:consul_cli_output) do
+      <<~MSG
+        Consul v1.7.8
+        Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)
+      MSG
+    end
+
+    before do
+      # Un-doing the stub added in chef_helper
+      allow_any_instance_of(described_class).to receive(:installed_version).and_call_original
+      allow(Gitlab).to receive(:[]).and_call_original
+      allow(VersionHelper).to receive(:version).with(/consul version/).and_return(consul_cli_output)
+    end
+
+    context 'when consul is not running' do
+      it 'returns nil' do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('consul').and_return(false)
+
+        expect(subject.installed_version).to be_nil
+      end
+    end
+
+    context 'when consul is running' do
+      before do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('consul').and_return(true)
+      end
+
+      it 'parses consul output properly' do
+        expect(subject.installed_version).to eq('1.7.8')
+      end
+    end
+  end
+
+  describe '#api_port' do
+    before do
+      allow(Gitlab).to receive(:[]).and_call_original
+    end
+
+    it 'falls back to default ports' do
+      stub_gitlab_rb(
+        consul: {
+          enable: true
+        }
+      )
+
+      expect(subject.api_port('http')).to eq(8500)
+      expect(subject.api_port('https')).to eq(8501)
+      expect(subject.configuration).not_to match(%r{"http":8500})
+      expect(subject.configuration).not_to match(%r{"https":8501})
+    end
+
+    it 'uses the configured ports' do
+      stub_gitlab_rb(
+        consul: {
+          enable: true,
+          http_port: 18500,
+          https_port: 18501
+        }
+      )
+
+      expect(subject.api_port('http')).to eq(18500)
+      expect(subject.api_port('https')).to eq(18501)
+      expect(subject.configuration).to match(%r{"http":18500})
+      expect(subject.configuration).to match(%r{"https":18501})
+    end
+
+    it 'uses the ports from configuration override' do
+      stub_gitlab_rb(
+        consul: {
+          enable: true,
+          http_port: 18500,
+          https_port: 18501,
+          configuration: {
+            ports: {
+              http: 28500,
+              https: 28501,
+            }
+          }
+        }
+      )
+
+      expect(subject.api_port('http')).to eq(28500)
+      expect(subject.api_port('https')).to eq(28501)
+      expect(subject.configuration).to match(%r{"http":28500})
+      expect(subject.configuration).to match(%r{"https":28501})
+    end
+  end
+
+  describe '#api_url' do
+    before do
+      allow(Gitlab).to receive(:[]).and_call_original
+    end
+
+    it 'uses http by default' do
+      stub_gitlab_rb(
+        consul: {
+          enable: true
+        }
+      )
+
+      expect(subject.api_url).to eq('http://localhost:8500')
+    end
+
+    it 'uses https when tls is enabled' do
+      stub_gitlab_rb(
+        consul: {
+          enable: true,
+          use_tls: true
+        }
+      )
+
+      expect(subject.api_url).to eq('https://localhost:8501')
+    end
+
+    it 'uses https when http port is disabled' do
+      stub_gitlab_rb(
+        consul: {
+          enable: true,
+          http_port: -1
+        }
+      )
+
+      expect(subject.api_url).to eq('https://localhost:8501')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/consul/libraries/failover_helper_spec.rb b/spec/chef/cookbooks/consul/libraries/failover_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9jb25zdWwvbGlicmFyaWVzL2ZhaWxvdmVyX2hlbHBlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/consul/libraries/failover_helper_spec.rb
@@ -0,0 +1,176 @@
+require 'chef_helper'
+
+require 'json'
+require 'logger'
+
+NodeData = Struct.new('NodeData', :name, :address, :leader?)
+
+RSpec.describe FailoverHelper::SplitBrain do
+  let(:node_list) { %w(cats kittens) }
+
+  context 'when passed a list of nodes' do
+    it 'sets primary_nodes to that list' do
+      brain = FailoverHelper::SplitBrain.new("Nobody here but us felines", node_list)
+      expect(brain.primary_nodes).to match_array(node_list)
+    end
+  end
+end
+
+RSpec.describe FailoverHelper::LeaderFinder do
+  let(:total_nodes) { 3 }
+  let(:failover) { FailoverHelper::LeaderFinder.new("{}", service_data) }
+  let(:primary_node) { NodeData.new("example_primary_node", "10.0.0.42", true) }
+  let(:splitbrain_primary_node) { NodeData.new("example_splitbrain_node", "10.0.0.41", true) }
+  let(:follower_node_01) { NodeData.new("example_node_02", "10.0.0.43", false) }
+  let(:follower_node_02) { NodeData.new("example_node_03", "10.0.0.44", false) }
+
+  def service_data
+    service_data = FailoverHelper::ServiceData.new
+    service_data.service_name = "example_service"
+    service_data.check_field = "Status"
+    service_data.leader_value = "passing"
+
+    service_data
+  end
+
+  def generate_parsed_data(unhealthy_primary: false, multiple_primaries: false)
+    parsed_data = []
+    nodes = [primary_node, follower_node_01, follower_node_02]
+
+    nodes.push(splitbrain_primary_node) if multiple_primaries
+
+    nodes.each do |node|
+      if node.leader?
+        leader_status = "passing"
+        health = unhealthy_primary ? "failing" : "passing"
+      else
+        leader_status = "warning"
+        health = "passing"
+      end
+
+      node_info = {
+        "Node" => {
+          "Node" => node.name,
+          "Address" => node.address
+        },
+        "Checks" => [
+          {
+            "Node" => node.name,
+            "CheckID" => "serfHealth",
+            "Status" => health
+          },
+          {
+            "Node" => node.name,
+            "CheckID" => "example_service",
+            "Status" => leader_status
+          }
+        ]
+      }
+
+      parsed_data.push(node_info)
+    end
+
+    parsed_data
+  end
+
+  def stub_healthy_primary_watcher_data
+    parsed_data = generate_parsed_data
+
+    allow_any_instance_of(FailoverHelper::LeaderFinder).to receive(:parse).and_return(parsed_data)
+  end
+
+  def stub_unhealthy_primary_watcher_data
+    parsed_data = generate_parsed_data(unhealthy_primary: true)
+
+    allow_any_instance_of(FailoverHelper::LeaderFinder).to receive(:parse).and_return(parsed_data)
+  end
+
+  def stub_multiple_primary_nodes_watcher_data
+    parsed_data = generate_parsed_data(multiple_primaries: true)
+
+    allow_any_instance_of(FailoverHelper::LeaderFinder).to receive(:parse).and_return(parsed_data)
+  end
+
+  describe "#parse" do
+    let(:bad_json) { "Nobody here but us errors!" }
+
+    context 'when passed invalid JSON data' do
+      it 'throws a JSON::Parser error' do
+        expect { FailoverHelper::LeaderFinder.new(bad_json, service_data) }.to raise_error(JSON::ParserError)
+      end
+    end
+  end
+
+  context 'when valid JSON is received from watch' do
+    context 'when there is more than one primary node found' do
+      before do
+        stub_multiple_primary_nodes_watcher_data
+      end
+
+      describe "#primary_node" do
+        it 'throws a SplitBrain error' do
+          expect { failover.primary_node }.to raise_error(FailoverHelper::SplitBrain, "Split brain detected, multiple primary nodes found!")
+        end
+      end
+    end
+
+    context 'when all nodes are healthy' do
+      before do
+        stub_healthy_primary_watcher_data
+      end
+
+      describe "#healthy_nodes" do
+        it 'returns the correct number of healthy nodes' do
+          expect(failover.healthy_nodes.length).to eq(total_nodes)
+        end
+      end
+
+      describe "#leader_nodes" do
+        context 'when there is one leader' do
+          it 'returns only one leader' do
+            expect(failover.leader_nodes.length).to eq(1)
+          end
+
+          it 'returns the correct node' do
+            primary_node = failover.leader_nodes.first
+            expect(primary_node.name).to eq(primary_node.name)
+          end
+        end
+      end
+
+      describe "#primary_node_address" do
+        context 'when the node name resolves via DNS' do
+          it 'returns the machine name' do
+            allow_any_instance_of(Resolv::DNS).to receive(:getaddress).and_return(true)
+            expect(failover.primary_node_address).to eq(primary_node.name)
+          end
+        end
+
+        context 'when the node name does not resolve via DNS' do
+          it 'returns the machine IP address' do
+            allow_any_instance_of(Resolv::DNS).to receive(:getaddress).and_raise(Resolv::ResolvError)
+            expect(failover.primary_node_address).to eq(primary_node.address)
+          end
+        end
+      end
+    end
+
+    context 'when the primary node is not healthy in a single cluster setup' do
+      before do
+        stub_unhealthy_primary_watcher_data
+      end
+
+      describe "#healthy_nodes" do
+        it 'returns the correct number of healthy nodes' do
+          expect(failover.healthy_nodes.length).to eq(total_nodes - 1)
+        end
+      end
+
+      describe "#leader_nodes" do
+        it 'throws a PrimaryMissing exception' do
+          expect { failover.leader_nodes }.to raise_error(FailoverHelper::PrimaryMissing, "No healthy primary node found.")
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/consul/recipes/configure_services_spec.rb b/spec/chef/cookbooks/consul/recipes/configure_services_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9jb25zdWwvcmVjaXBlcy9jb25maWd1cmVfc2VydmljZXNfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/consul/recipes/configure_services_spec.rb
@@ -0,0 +1,39 @@
+require 'chef_helper'
+
+RSpec.describe 'consul::configure_services' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe 'enabling services' do
+    before do
+      stub_gitlab_rb(
+        consul: {
+          enable: true,
+          services: %w(postgresql)
+        }
+      )
+    end
+
+    it 'includes the enable_service recipe for the corresponding service' do
+      expect(chef_run).to include_recipe('consul::enable_service_postgresql')
+    end
+  end
+
+  describe 'disabling services' do
+    before do
+      stub_gitlab_rb(
+        consul: {
+          enable: true,
+          services: []
+        }
+      )
+    end
+
+    it 'includes the disable_service for all known services' do
+      expect(chef_run).to include_recipe('consul::disable_service_postgresql')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/consul/recipes/consul_spec.rb b/spec/chef/cookbooks/consul/recipes/consul_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9jb25zdWwvcmVjaXBlcy9jb25zdWxfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/consul/recipes/consul_spec.rb
@@ -0,0 +1,359 @@
+require 'chef_helper'
+
+RSpec.describe 'consul' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
+  let(:consul_conf) { '/var/opt/gitlab/consul/config.json' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'disabled by default' do
+    it 'includes the disable recipe' do
+      expect(chef_run).to include_recipe('consul::disable')
+    end
+  end
+
+  describe 'consul::disable' do
+    it_behaves_like 'disabled runit service', 'consul'
+  end
+
+  context 'when enabled' do
+    before do
+      stub_gitlab_rb(
+        consul: {
+          enable: true,
+          config_dir: '/fake/config.d',
+          data_dir: '/fake/data',
+          custom_config_dir: '/custom/dir'
+        }
+      )
+    end
+
+    it 'includes the enable recipe' do
+      expect(chef_run).to include_recipe('consul::enable')
+    end
+
+    describe 'consul::enable' do
+      it_behaves_like 'enabled runit service', 'consul', 'gitlab-consul', 'gitlab-consul', 'gitlab-consul', 'gitlab-consul'
+
+      it 'creates the consul system user and group' do
+        expect(chef_run).to create_account('Consul user and group').with(username: 'gitlab-consul', groupname: 'gitlab-consul')
+      end
+
+      it 'includes the configure_services recipe' do
+        expect(chef_run).to include_recipe('consul::configure_services')
+      end
+
+      it 'only enables the agent by default' do
+        expect(chef_run).to render_file(consul_conf).with_content { |content|
+          expect(content).to match(%r{"server":false})
+        }
+      end
+
+      it 'does not include nil values in its configuration' do
+        expect(chef_run).to render_file(consul_conf).with_content { |content|
+          expect(content).not_to match(%r{"encryption":})
+        }
+      end
+
+      it 'does not include server default values in its configuration' do
+        expect(chef_run).to render_file(consul_conf).with_content { |content|
+          expect(content).not_to match(%r{"bootstrap_expect":3})
+        }
+      end
+
+      it 'creates the necessary directories' do
+        expect(chef_run).to create_directory('/fake/config.d')
+        expect(chef_run).to create_directory('/fake/data')
+        expect(chef_run).to create_directory('/var/log/gitlab/consul')
+      end
+
+      it 'notifies other resources on configuration change' do
+        config_json = chef_run.file('/var/opt/gitlab/consul/config.json')
+        expect(config_json).to notify('execute[reload consul]').to(:run)
+        expect(config_json).to notify('ruby_block[consul config change]').to(:run)
+      end
+
+      it 'renders run file with specified options' do
+        expect(chef_run).to render_file('/opt/gitlab/sv/consul/run').with_content(%r{-config-dir /fake/config.d})
+        expect(chef_run).to render_file('/opt/gitlab/sv/consul/run').with_content(%r{-config-dir /custom/dir})
+        expect(chef_run).to render_file('/opt/gitlab/sv/consul/run').with_content(%r{-data-dir /fake/data})
+      end
+    end
+
+    context 'with default options' do
+      it 'allows the user to specify node name' do
+        stub_gitlab_rb(
+          consul: {
+            enable: true
+          }
+        )
+        expect(chef_run).to render_file(consul_conf).with_content { |content|
+          expect(content).to match(%r{"datacenter":"gitlab_consul"})
+          expect(content).to match(%r{"disable_update_check":true})
+          expect(content).to match(%r{"enable_script_checks":true})
+          expect(content).to match(%r{"node_name":"fauxhai.local"})
+          expect(content).to match(%r{"rejoin_after_leave":true})
+          expect(content).to match(%r{"server":false})
+        }
+      end
+    end
+
+    context 'with non-default options' do
+      before do
+        stub_gitlab_rb(
+          consul: {
+            enable: true,
+            node_name: 'fakenodename',
+            username: 'foo',
+            group: 'bar',
+          }
+        )
+      end
+
+      it 'allows the user to specify node name' do
+        expect(chef_run).to render_file(consul_conf).with_content('"node_name":"fakenodename"')
+      end
+
+      it 'creates the consul system user and group' do
+        expect(chef_run).to create_account('Consul user and group').with(username: 'foo', groupname: 'bar')
+      end
+
+      it_behaves_like 'enabled runit service', 'consul', 'foo', 'bar', 'foo', 'bar'
+    end
+
+    context 'server enabled' do
+      before do
+        stub_gitlab_rb(
+          consul: {
+            enable: true,
+            configuration: {
+              server: true
+            }
+          }
+        )
+      end
+
+      it 'enables the server functionality' do
+        expect(chef_run.node['consul']['configuration']['server']).to eq true
+        expect(chef_run).to render_file(consul_conf).with_content { |content|
+          expect(content).to match(%r{"server":true})
+          expect(content).to match(%r{"bootstrap_expect":3})
+        }
+      end
+    end
+
+    describe 'pending restart check' do
+      context 'when running version is same as installed version' do
+        before do
+          allow_any_instance_of(ConsulHelper).to receive(:running_version).and_return('1.9.6')
+          allow_any_instance_of(ConsulHelper).to receive(:installed_version).and_return('1.9.6')
+        end
+
+        it 'does not raise a warning' do
+          expect(chef_run).not_to run_ruby_block('warn pending consul restart')
+        end
+      end
+
+      context 'when running version is different than installed version' do
+        before do
+          allow_any_instance_of(ConsulHelper).to receive(:running_version).and_return('1.6.4')
+          allow_any_instance_of(ConsulHelper).to receive(:installed_version).and_return('1.9.6')
+        end
+
+        it 'raises a warning' do
+          expect(chef_run).to run_ruby_block('warn pending consul restart')
+        end
+      end
+    end
+  end
+
+  describe 'encryption' do
+    it 'is not enabled by default' do
+      stub_gitlab_rb(
+        consul: {
+          enable: true,
+        }
+      )
+
+      expect(chef_run).to render_file(consul_conf).with_content { |content|
+        expect(content).not_to match(%r{"encrypt":})
+        expect(content).not_to match(%r{"encrypt_verify_incoming":})
+        expect(content).not_to match(%r{"encrypt_verify_outgoing":})
+      }
+    end
+
+    context 'new datacenter' do
+      it 'uses encryption key and falls back to defaults' do
+        stub_gitlab_rb(
+          consul: {
+            enable: true,
+            encryption_key: 'fake_key'
+          }
+        )
+
+        expect(chef_run).to render_file(consul_conf).with_content { |content|
+          expect(content).to match(%r{"encrypt":"fake_key"})
+          expect(content).not_to match(%r{"encrypt_verify_incoming":})
+          expect(content).not_to match(%r{"encrypt_verify_outgoing":})
+        }
+      end
+    end
+
+    context 'existing datacenter' do
+      it 'uses encryption key and specified verification settings' do
+        stub_gitlab_rb(
+          consul: {
+            enable: true,
+            encryption_key: 'fake_key',
+            encryption_verify_incoming: false,
+            encryption_verify_outgoing: true,
+          }
+        )
+
+        expect(chef_run).to render_file(consul_conf).with_content { |content|
+          expect(content).to match(%r{"encrypt":"fake_key"})
+          expect(content).to match(%r{"encrypt_verify_incoming":false})
+          expect(content).to match(%r{"encrypt_verify_outgoing":true})
+        }
+      end
+    end
+
+    describe 'secure configuration' do
+      context 'client mode' do
+        it 'use tls for outgoing' do
+          stub_gitlab_rb(
+            consul: {
+              enable: true,
+              use_tls: true,
+              tls_ca_file: '/fake/ca.crt.pem',
+            }
+          )
+
+          expect(chef_run).to render_file(consul_conf).with_content { |content|
+            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
+            expect(content).to match(%r{"verify_outgoing":true})
+            expect(content).to match(%r{"verify_incoming":false})
+            expect(content).not_to match(%r{"https":8501})
+            expect(content).not_to match(%r{"cert_file":})
+            expect(content).not_to match(%r{"key_file":})
+          }
+        end
+
+        it 'use tls client authentication' do
+          stub_gitlab_rb(
+            consul: {
+              enable: true,
+              use_tls: true,
+              tls_ca_file: '/fake/ca.crt.pem',
+              tls_certificate_file: '/fake/client.crt.pem',
+              tls_key_file: '/fake/client.key.pem'
+            }
+          )
+
+          expect(chef_run).to render_file(consul_conf).with_content { |content|
+            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
+            expect(content).to match(%r{"cert_file":"/fake/client.crt.pem"})
+            expect(content).to match(%r{"key_file":"/fake/client.key.pem"})
+            expect(content).to match(%r{"verify_outgoing":true})
+            expect(content).to match(%r{"verify_incoming":false})
+            expect(content).not_to match(%r{"https":8501})
+          }
+        end
+
+        it 'use tls for incoming and outgoing' do
+          stub_gitlab_rb(
+            consul: {
+              enable: true,
+              use_tls: true,
+              tls_ca_file: '/fake/ca.crt.pem',
+              tls_certificate_file: '/fake/server.crt.pem',
+              tls_key_file: '/fake/server.key.pem',
+              https_port: 8501,
+            }
+          )
+
+          expect(chef_run).to render_file(consul_conf).with_content { |content|
+            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
+            expect(content).to match(%r{"cert_file":"/fake/server.crt.pem"})
+            expect(content).to match(%r{"key_file":"/fake/server.key.pem"})
+            expect(content).to match(%r{"verify_outgoing":true})
+            expect(content).to match(%r{"verify_incoming":false})
+            expect(content).to match(%r{"https":8501})
+          }
+        end
+
+        it 'use tls for incoming and outgoing and verify incoming' do
+          stub_gitlab_rb(
+            consul: {
+              enable: true,
+              use_tls: true,
+              tls_ca_file: '/fake/ca.crt.pem',
+              tls_certificate_file: '/fake/server.crt.pem',
+              tls_key_file: '/fake/server.key.pem',
+              tls_verify_client: true,
+              https_port: 8501
+            }
+          )
+
+          expect(chef_run).to render_file(consul_conf).with_content { |content|
+            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
+            expect(content).to match(%r{"cert_file":"/fake/server.crt.pem"})
+            expect(content).to match(%r{"key_file":"/fake/server.key.pem"})
+            expect(content).to match(%r{"verify_outgoing":true})
+            expect(content).to match(%r{"verify_incoming":true})
+            expect(content).to match(%r{"https":8501})
+          }
+        end
+      end
+
+      context 'server mode' do
+        it 'use tls for incoming and outgoing and verify incoming by default' do
+          stub_gitlab_rb(
+            consul: {
+              enable: true,
+              configuration: { server: true },
+              use_tls: true,
+              tls_ca_file: '/fake/ca.crt.pem',
+              tls_certificate_file: '/fake/server.crt.pem',
+              tls_key_file: '/fake/server.key.pem'
+            }
+          )
+
+          expect(chef_run).to render_file(consul_conf).with_content { |content|
+            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
+            expect(content).to match(%r{"cert_file":"/fake/server.crt.pem"})
+            expect(content).to match(%r{"key_file":"/fake/server.key.pem"})
+            expect(content).to match(%r{"verify_outgoing":true})
+            expect(content).to match(%r{"verify_incoming":true})
+            expect(content).to match(%r{"https":8501})
+          }
+        end
+
+        it 'use tls for incoming and outgoing without verifying incoming' do
+          stub_gitlab_rb(
+            consul: {
+              enable: true,
+              configuration: { server: true },
+              use_tls: true,
+              tls_ca_file: '/fake/ca.crt.pem',
+              tls_certificate_file: '/fake/server.crt.pem',
+              tls_key_file: '/fake/server.key.pem',
+              tls_verify_client: false
+            }
+          )
+
+          expect(chef_run).to render_file(consul_conf).with_content { |content|
+            expect(content).to match(%r{"ca_file":"/fake/ca.crt.pem"})
+            expect(content).to match(%r{"cert_file":"/fake/server.crt.pem"})
+            expect(content).to match(%r{"key_file":"/fake/server.key.pem"})
+            expect(content).to match(%r{"verify_outgoing":true})
+            expect(content).to match(%r{"verify_incoming":false})
+            expect(content).to match(%r{"https":8501})
+          }
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/consul/recipes/consul_watcher_spec.rb b/spec/chef/cookbooks/consul/recipes/consul_watcher_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9jb25zdWwvcmVjaXBlcy9jb25zdWxfd2F0Y2hlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/consul/recipes/consul_watcher_spec.rb
@@ -0,0 +1,42 @@
+require 'chef_helper'
+
+RSpec.describe 'consul::watchers' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+  let(:watcher_conf) { '/var/opt/gitlab/consul/config.d/watcher_postgresql.json' }
+  let(:watcher_check) { '/var/opt/gitlab/consul/scripts/failover_pgbouncer' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+
+    stub_gitlab_rb(
+      consul: {
+        enable: true,
+        watchers: %w(postgresql)
+      }
+    )
+  end
+
+  it 'includes the watcher recipe' do
+    expect(chef_run).to include_recipe('consul::watchers')
+  end
+
+  it 'creates the watcher config file' do
+    rendered = {
+      'watches' => [
+        {
+          'type' => 'service',
+          'service' => 'postgresql',
+          'args' => [watcher_check]
+        }
+      ]
+    }
+
+    expect(chef_run).to render_file(watcher_conf).with_content { |content|
+      expect(JSON.parse(content)).to eq(rendered)
+    }
+  end
+
+  it 'creates the watcher handler file' do
+    expect(chef_run).to render_file(watcher_check)
+  end
+end
diff --git a/spec/chef/cookbooks/consul/recipes/disable_service_postgresql_spec.rb b/spec/chef/cookbooks/consul/recipes/disable_service_postgresql_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9jb25zdWwvcmVjaXBlcy9kaXNhYmxlX3NlcnZpY2VfcG9zdGdyZXNxbF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/consul/recipes/disable_service_postgresql_spec.rb
@@ -0,0 +1,24 @@
+require 'chef_helper'
+
+RSpec.describe 'consul::disable_service_postgresql' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default', 'consul::disable_service_postgresql') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'default' do
+    before do
+      stub_gitlab_rb(
+        consul: {
+          enable: true,
+          services: []
+        }
+      )
+    end
+
+    it 'deletes the service configuration file' do
+      expect(chef_run).to delete_file('/var/opt/gitlab/consul/config.d/postgresql_service.json')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/consul/recipes/enable_service_postgresq_spec.rb b/spec/chef/cookbooks/consul/recipes/enable_service_postgresq_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9jb25zdWwvcmVjaXBlcy9lbmFibGVfc2VydmljZV9wb3N0Z3Jlc3Ffc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/consul/recipes/enable_service_postgresq_spec.rb
@@ -0,0 +1,80 @@
+require 'chef_helper'
+
+RSpec.describe 'consul::enable_service_postgresql' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'default' do
+    context 'on a regular cluster' do
+      before do
+        stub_gitlab_rb(
+          patroni: {
+            enable: true
+          },
+          consul: {
+            enable: true,
+            services: %w(postgresql)
+          }
+        )
+      end
+
+      it 'renders the service configuration file' do
+        rendered = {
+          'service' => {
+            'name' => 'postgresql',
+            'address' => '',
+            'port' => 5432,
+            'check' => {
+              'id' => 'service:postgresql',
+              'args' => ['/opt/gitlab/bin/gitlab-ctl', 'patroni', 'check-leader'],
+              'interval' => '10s',
+              'status' => 'failing'
+            }
+          }
+        }
+        expect(chef_run).to render_file('/var/opt/gitlab/consul/config.d/postgresql_service.json').with_content { |content|
+          expect(JSON.parse(content)).to eq(rendered)
+        }
+      end
+    end
+
+    context 'on a standby cluster' do
+      before do
+        stub_gitlab_rb(
+          patroni: {
+            enable: true,
+            standby_cluster: {
+              enable: true
+            }
+          },
+          consul: {
+            enable: true,
+            services: %w(postgresql)
+          }
+        )
+      end
+
+      it 'renders the service configuration file' do
+        rendered = {
+          'service' => {
+            'name' => 'postgresql',
+            'address' => '',
+            'port' => 5432,
+            'check' => {
+              'id' => 'service:postgresql',
+              'args' => ['/opt/gitlab/bin/gitlab-ctl', 'patroni', 'check-standby-leader'],
+              'interval' => '10s',
+              'status' => 'failing'
+            }
+          }
+        }
+        expect(chef_run).to render_file('/var/opt/gitlab/consul/config.d/postgresql_service.json').with_content { |content|
+          expect(JSON.parse(content)).to eq(rendered)
+        }
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/consul/resources/consul_service_spec.rb b/spec/chef/cookbooks/consul/resources/consul_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9jb25zdWwvcmVzb3VyY2VzL2NvbnN1bF9zZXJ2aWNlX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/consul/resources/consul_service_spec.rb
@@ -0,0 +1,74 @@
+require 'chef_helper'
+
+RSpec.describe 'consul_service' do
+  let(:runner) do
+    ChefSpec::SoloRunner.new(step_into: %w(consul_service))
+  end
+
+  context 'create' do
+    before do
+      runner.node.automatic['ipaddress'] = '10.1.1.1'
+    end
+
+    context 'with service address and port properties' do
+      let(:chef_run) { runner.converge('test_consul::consul_service_address_port') }
+
+      it 'creates the Consul service file' do
+        expect(chef_run).to render_file('/var/opt/gitlab/consul/config.d/node-exporter-service.json')
+          .with_content('{"service":{"name":"node-exporter","address":"10.1.1.1","port":1234}}')
+      end
+
+      it 'notifies the Consul service to reload' do
+        expect(chef_run.file('/var/opt/gitlab/consul/config.d/node-exporter-service.json'))
+          .to notify 'execute[reload consul]'
+      end
+    end
+
+    context 'with a socket property and no reload' do
+      let(:chef_run) { runner.converge('test_consul::consul_service_socket') }
+
+      it 'creates the Consul service file' do
+        expect(chef_run).to render_file('/var/opt/gitlab/consul/config.d/node-exporter-service.json')
+          .with_content('{"service":{"name":"node-exporter","address":"10.1.1.1","port":5678}}')
+      end
+
+      it 'does not notify the Consul service to reload' do
+        expect(chef_run.file('/var/opt/gitlab/consul/config.d/node-exporter-service.json'))
+          .not_to notify 'execute[reload consul]'
+      end
+    end
+
+    context 'with symantic metadata' do
+      let(:chef_run) { runner.converge('test_consul::consul_service_meta') }
+
+      it 'creates the Consul service file' do
+        expect(chef_run).to render_file('/var/opt/gitlab/consul/config.d/node-exporter-service.json')
+          .with_content('{"service":{"name":"node-exporter","address":"10.1.1.1","port":1234,"meta":{"some_key":"value"}}}')
+      end
+    end
+  end
+
+  context 'delete' do
+    context 'default do reload' do
+      let(:chef_run) { runner.converge('test_consul::consul_service_delete') }
+
+      it 'deletes the Consul service file' do
+        expect(chef_run).to delete_file('/var/opt/gitlab/consul/config.d/delete-me-service.json')
+      end
+
+      it 'notifies the Consul service to reload' do
+        expect(chef_run.file('/var/opt/gitlab/consul/config.d/delete-me-service.json'))
+          .to notify 'execute[reload consul]'
+      end
+    end
+
+    context 'do not reload' do
+      let(:chef_run) { runner.converge('test_consul::consul_service_delete_no_reload') }
+
+      it 'does not notify the Consul service to reload' do
+        expect(chef_run.file('/var/opt/gitlab/consul/config.d/delete-no-reload-service.json'))
+          .not_to notify 'execute[reload consul]'
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/crond/recipes/crond_enable_spec.rb b/spec/chef/cookbooks/crond/recipes/crond_enable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9jcm9uZC9yZWNpcGVzL2Nyb25kX2VuYWJsZV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/crond/recipes/crond_enable_spec.rb
@@ -0,0 +1,24 @@
+require 'chef_helper'
+
+RSpec.describe 'crond::enable' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('crond::enable') }
+
+  it "should create a log directory" do
+    expect(chef_run).to create_directory("/var/log/gitlab/crond").with(
+      owner: "root"
+    )
+  end
+
+  it "should create a cron.d directory" do
+    expect(chef_run).to create_directory("/var/opt/gitlab/crond").with(
+      recursive: true,
+      owner: "root"
+    )
+  end
+
+  it "should pass correct options to runit_service" do
+    expect(chef_run).to render_file("/opt/gitlab/sv/crond/run").with_content(/--include=\/var\/opt\/gitlab\/crond/)
+  end
+
+  it_behaves_like "enabled runit service", "crond", "root", "root"
+end
diff --git a/spec/chef/cookbooks/crond/resources/crond_job_spec.rb b/spec/chef/cookbooks/crond/resources/crond_job_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9jcm9uZC9yZXNvdXJjZXMvY3JvbmRfam9iX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/crond/resources/crond_job_spec.rb
@@ -0,0 +1,69 @@
+require 'chef_helper'
+
+RSpec.describe 'crond_job' do
+  context 'crond enabled' do
+    let(:runner) do
+      ChefSpec::SoloRunner.new(step_into: %w(crond_job)) do |node|
+        node.normal['crond']['cron_d'] = "prefix"
+      end
+    end
+
+    context 'delete' do
+      let(:chef_run) { runner.converge("crond::enable", "test_crond::crond_job_delete") }
+
+      it 'should delete the file' do
+        expect(chef_run).to delete_file("prefix/delete")
+      end
+
+      it 'should notify the service' do
+        expect(chef_run.file("prefix/delete"))
+          .to notify("runit_service[crond]").to(:restart)
+      end
+    end
+
+    context 'minimal' do
+      let(:chef_run) { runner.converge("crond::enable", "test_crond::crond_job_minimal") }
+
+      it 'should set up the file' do
+        expect(chef_run).to create_file("prefix/minimal").with(
+          owner: "root",
+          content: "* * * * * rspec echo 'Hello world'\n"
+        )
+      end
+
+      it 'should notify the service' do
+        expect(chef_run.file("prefix/minimal"))
+          .to notify("runit_service[crond]").to(:restart)
+      end
+    end
+  end
+
+  context 'crond disabled' do
+    let(:runner) do
+      ChefSpec::SoloRunner.new(step_into: %w(crond_job)) do |node|
+        node.normal['crond']['cron_d'] = "prefix"
+      end
+    end
+
+    context 'delete' do
+      let(:chef_run) { runner.converge("test_crond::crond_job_delete") }
+
+      it 'should delete the file' do
+        expect(chef_run).to delete_file("prefix/delete")
+      end
+
+      it 'should not try and notify the service' do
+        expect(chef_run.file("prefix/delete"))
+          .not_to notify("runit_service[crond]")
+      end
+    end
+
+    context 'minimal' do
+      let(:chef_run) { runner.converge("test_crond::crond_job_minimal") }
+
+      it 'should not set up the file' do
+        expect(chef_run).not_to create_file("prefix/minimal")
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitaly/libraries/gitaly_spec.rb b/spec/chef/cookbooks/gitaly/libraries/gitaly_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRhbHkvbGlicmFyaWVzL2dpdGFseV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitaly/libraries/gitaly_spec.rb
@@ -0,0 +1,19 @@
+require 'chef_helper'
+
+RSpec.describe Gitaly do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+  before { allow(Gitlab).to receive(:[]).and_call_original }
+
+  describe 'by default' do
+    it 'provides settings needed for gitaly to run' do
+      expect(chef_run.node['gitaly']['env']).to include(
+        'HOME' => '/var/opt/gitlab',
+        'PATH' => '/opt/gitlab/bin:/opt/gitlab/embedded/bin:/bin:/usr/bin'
+      )
+    end
+
+    it 'does not include known settings in the environment' do
+      expect(chef_run.node['gitaly']['env']).not_to include('GITALY_ENABLE')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb b/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRhbHkvcmVjaXBlcy9naXRhbHlfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb
@@ -0,0 +1,890 @@
+require 'chef_helper'
+
+RSpec.describe 'gitaly' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  let(:config_path) { '/var/opt/gitlab/gitaly/config.toml' }
+  let(:gitaly_config) { chef_run.template(config_path) }
+  let(:internal_socket_dir) { '/var/opt/gitlab/gitaly/user_defined/internal_sockets' }
+  let(:socket_path) { '/tmp/gitaly.socket' }
+  let(:listen_addr) { 'localhost:7777' }
+  let(:tls_listen_addr) { 'localhost:8888' }
+  let(:certificate_path) { '/path/to/cert.pem' }
+  let(:key_path) { '/path/to/key.pem' }
+  let(:prometheus_listen_addr) { 'localhost:9000' }
+  let(:logging_level) { 'warn' }
+  let(:logging_format) { 'default' }
+  let(:logging_sentry_dsn) { 'https://my_key:my_secret@sentry.io/test_project' }
+  let(:logging_ruby_sentry_dsn) { 'https://my_key:my_secret@sentry.io/test_project-ruby' }
+  let(:logging_sentry_environment) { 'production' }
+  let(:prometheus_grpc_latency_buckets) do
+    '[0.001, 0.005, 0.025, 0.1, 0.5, 1.0, 10.0, 30.0, 60.0, 300.0, 1500.0]'
+  end
+  let(:auth_token) { '123secret456' }
+  let(:auth_transitioning) { true }
+  let(:ruby_max_rss) { 1000000 }
+  let(:graceful_restart_timeout) { '20m' }
+  let(:ruby_graceful_restart_timeout) { '30m' }
+  let(:ruby_restart_delay) { '10m' }
+  let(:ruby_num_workers) { 5 }
+  let(:ruby_rugged_git_config_search_path) { '/path/to/opt/gitlab/embedded/etc' }
+  let(:git_catfile_cache_size) { 50 }
+  let(:git_bin_path) { '/path/to/usr/bin/git' }
+  let(:use_bundled_git) { true }
+  let(:open_files_ulimit) { 10000 }
+  let(:default_vars) do
+    {
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
+      'TZ' => ':/etc/localtime',
+      'HOME' => '/var/opt/gitlab',
+      'PATH' => '/opt/gitlab/bin:/opt/gitlab/embedded/bin:/bin:/usr/bin',
+      'ICU_DATA' => '/opt/gitlab/embedded/share/icu/current',
+      'PYTHONPATH' => '/opt/gitlab/embedded/lib/python3.9/site-packages',
+      'WRAPPER_JSON_LOGGING' => 'true',
+      "GITALY_PID_FILE" => '/var/opt/gitlab/gitaly/gitaly.pid',
+    }
+  end
+
+  let(:gitlab_url) { 'http://localhost:3000' }
+  let(:workhorse_addr) { 'localhost:4000' }
+  let(:gitaly_custom_hooks_dir) { '/path/to/gitaly/custom/hooks' }
+  let(:gitlab_shell_custom_hooks_dir) { '/path/to/gitlab-shell/custom/hooks' }
+  let(:user) { 'user123' }
+  let(:password) { 'password321' }
+  let(:ca_file) { '/path/to/ca_file' }
+  let(:ca_path) { '/path/to/ca_path' }
+  let(:self_signed_cert) { true }
+  let(:read_timeout) { 123 }
+  let(:daily_maintenance_start_hour) { 21 }
+  let(:daily_maintenance_start_minute) { 9 }
+  let(:daily_maintenance_duration) { '45m' }
+  let(:daily_maintenance_storages) { ["default"] }
+  let(:daily_maintenance_disabled) { false }
+  let(:cgroups_count) { 10 }
+  let(:cgroups_mountpoint) { '/sys/fs/cgroup' }
+  let(:cgroups_hierarchy_root) { 'gitaly' }
+  let(:cgroups_memory_enabled) { true }
+  let(:cgroups_memory_limit) { 1048576 }
+  let(:cgroups_cpu_enabled) { true }
+  let(:cgroups_cpu_shares) { 512 }
+  let(:pack_objects_cache_enabled) { true }
+  let(:pack_objects_cache_dir) { '/pack-objects-cache' }
+  let(:pack_objects_cache_max_age) { '10m' }
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'by default' do
+    it_behaves_like "enabled runit service", "gitaly", "root", "root"
+
+    it 'creates expected directories with correct permissions' do
+      expect(chef_run).to create_directory('/var/opt/gitlab/gitaly').with(user: 'git', mode: '0700')
+      expect(chef_run).to create_directory('/var/log/gitlab/gitaly').with(user: 'git', mode: '0700')
+    end
+
+    it 'creates a default VERSION file and restarts service' do
+      expect(chef_run).to create_version_file('Create version file for Gitaly').with(
+        version_file_path: '/var/opt/gitlab/gitaly/VERSION',
+        version_check_cmd: "/opt/gitlab/embedded/bin/ruby -rdigest/sha2 -e 'puts %(sha256:) + Digest::SHA256.file(%(/opt/gitlab/embedded/bin/gitaly)).hexdigest'"
+      )
+
+      expect(chef_run.version_file('Create version file for Gitaly')).to notify('runit_service[gitaly]').to(:hup)
+    end
+
+    it 'creates a default RUBY_VERSION file and restarts service' do
+      expect(chef_run).to create_version_file('Create Ruby version file for Gitaly').with(
+        version_file_path: '/var/opt/gitlab/gitaly/RUBY_VERSION',
+        version_check_cmd: '/opt/gitlab/embedded/bin/ruby --version'
+      )
+
+      expect(chef_run.version_file('Create Ruby version file for Gitaly')).to notify('runit_service[gitaly]').to(:hup)
+    end
+
+    it 'populates gitaly config.toml with defaults' do
+      expect(chef_run).to render_file(config_path).with_content { |content|
+        expect(content).to include("socket_path = '/var/opt/gitlab/gitaly/gitaly.socket'")
+        expect(content).to include("internal_socket_dir = '/var/opt/gitlab/gitaly/internal_sockets'")
+        expect(content).to include("bin_dir = '/opt/gitlab/embedded/bin'")
+        expect(content).to include(%(rugged_git_config_search_path = "/opt/gitlab/embedded/etc"))
+      }
+
+      expect(chef_run).not_to render_file(config_path)
+        .with_content("listen_addr = '#{listen_addr}'")
+      expect(chef_run).not_to render_file(config_path)
+        .with_content("tls_listen_addr =")
+      expect(chef_run).not_to render_file(config_path)
+       .with_content("certificate_path  =")
+      expect(chef_run).not_to render_file(config_path)
+       .with_content("key_path  =")
+      expect(chef_run).not_to render_file(config_path)
+        .with_content("prometheus_listen_addr = '#{prometheus_listen_addr}'")
+      expect(chef_run).not_to render_file(config_path)
+        .with_content(%r{\[logging\]\s+level = '#{logging_level}'\s+format = '#{logging_format}'\s+sentry_dsn = '#{logging_sentry_dsn}'})
+      expect(chef_run).not_to render_file(config_path)
+        .with_content(%r{\[logging\]\s+level = '#{logging_level}'\s+format = '#{logging_format}'\s+ruby_sentry_dsn = '#{logging_ruby_sentry_dsn}'})
+      expect(chef_run).not_to render_file(config_path)
+        .with_content(%r{\[logging\]\s+level = '#{logging_level}'\s+format = '#{logging_format}'\s+sentry_environment = '#{logging_sentry_environment}'})
+      expect(chef_run).not_to render_file(config_path)
+        .with_content(%r{\[prometheus\]\s+grpc_latency_buckets = #{Regexp.escape(prometheus_grpc_latency_buckets)}})
+      expect(chef_run).not_to render_file(config_path)
+        .with_content(%r{\[auth\]\s+token = })
+      expect(chef_run).not_to render_file(config_path)
+        .with_content('transitioning =')
+      expect(chef_run).not_to render_file(config_path)
+        .with_content('max_rss =')
+      expect(chef_run).not_to render_file(config_path)
+        .with_content('graceful_restart_timeout =')
+      expect(chef_run).not_to render_file(config_path)
+        .with_content('restart_delay =')
+      expect(chef_run).not_to render_file(config_path)
+        .with_content('num_workers =')
+      expect(chef_run).not_to render_file(config_path)
+        .with_content(%r{\[logging\]\s+level})
+      expect(chef_run).not_to render_file(config_path)
+        .with_content(%r{catfile_cache_size})
+      expect(chef_run).not_to render_file(config_path)
+        .with_content('[pack_objects_cache]')
+    end
+
+    it 'populates gitaly config.toml with default git binary path' do
+      expect(chef_run).to render_file(config_path)
+        .with_content("bin_path = '/opt/gitlab/embedded/bin/git'")
+    end
+
+    it 'populates gitaly config.toml with default storages' do
+      expect(chef_run).to render_file(config_path)
+        .with_content(%r{\[\[storage\]\]\s+name = 'default'\s+path = '/var/opt/gitlab/git-data/repositories'})
+    end
+
+    it 'renders the runit run script with defaults' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/run')
+        .with_content(%r{ulimit -n 15000})
+    end
+
+    it 'does not append timestamp in logs if logging format is json' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/log/run')
+        .with_content(/exec svlogd \/var\/log\/gitlab\/gitaly/)
+    end
+
+    it 'populates gitaly config.toml with gitlab-shell values' do
+      expect(chef_run).to render_file(config_path)
+        .with_content(%r{\[gitlab-shell\]\s+dir = "/opt/gitlab/embedded/service/gitlab-shell"})
+    end
+
+    it 'populates gitaly config.toml with gitlab-workhorse socket' do
+      expect(chef_run).to render_file(config_path)
+        .with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket'\s+relative_url_root = ''})
+    end
+  end
+
+  context 'with user settings' do
+    before do
+      stub_gitlab_rb(
+        gitaly: {
+          socket_path: socket_path,
+          internal_socket_dir: internal_socket_dir,
+          listen_addr: listen_addr,
+          tls_listen_addr: tls_listen_addr,
+          certificate_path: certificate_path,
+          key_path: key_path,
+          prometheus_listen_addr: prometheus_listen_addr,
+          logging_level: logging_level,
+          logging_format: logging_format,
+          logging_sentry_dsn: logging_sentry_dsn,
+          logging_ruby_sentry_dsn: logging_ruby_sentry_dsn,
+          logging_sentry_environment: logging_sentry_environment,
+          prometheus_grpc_latency_buckets: prometheus_grpc_latency_buckets,
+          auth_token: auth_token,
+          auth_transitioning: auth_transitioning,
+          graceful_restart_timeout: graceful_restart_timeout,
+          ruby_max_rss: ruby_max_rss,
+          ruby_graceful_restart_timeout: ruby_graceful_restart_timeout,
+          ruby_restart_delay: ruby_restart_delay,
+          ruby_num_workers: ruby_num_workers,
+          git_catfile_cache_size: git_catfile_cache_size,
+          git_bin_path: git_bin_path,
+          use_bundled_git: true,
+          open_files_ulimit: open_files_ulimit,
+          ruby_rugged_git_config_search_path: ruby_rugged_git_config_search_path,
+          daily_maintenance_start_hour: daily_maintenance_start_hour,
+          daily_maintenance_start_minute: daily_maintenance_start_minute,
+          daily_maintenance_duration: daily_maintenance_duration,
+          daily_maintenance_storages: daily_maintenance_storages,
+          daily_maintenance_disabled: daily_maintenance_disabled,
+          cgroups_count: cgroups_count,
+          cgroups_mountpoint: cgroups_mountpoint,
+          cgroups_hierarchy_root: cgroups_hierarchy_root,
+          cgroups_memory_enabled: cgroups_memory_enabled,
+          cgroups_memory_limit: cgroups_memory_limit,
+          cgroups_cpu_enabled: cgroups_cpu_enabled,
+          cgroups_cpu_shares: cgroups_cpu_shares,
+          pack_objects_cache_enabled: pack_objects_cache_enabled,
+          pack_objects_cache_dir: pack_objects_cache_dir,
+          pack_objects_cache_max_age: pack_objects_cache_max_age,
+          custom_hooks_dir: gitaly_custom_hooks_dir
+        },
+        gitlab_rails: {
+          internal_api_url: gitlab_url
+        },
+        gitlab_shell: {
+          http_settings: {
+            read_timeout: read_timeout,
+            user: user,
+            password: password,
+            ca_file: ca_file,
+            ca_path: ca_path,
+            self_signed_cert: self_signed_cert
+          }
+        },
+        gitlab_workhorse: {
+          listen_network: 'tcp',
+          listen_addr: workhorse_addr,
+        },
+        user: {
+          username: 'foo',
+          group: 'bar'
+        }
+      )
+    end
+
+    it_behaves_like "enabled runit service", "gitaly", "root", "root"
+
+    it 'creates expected directories with correct permissions' do
+      expect(chef_run).to create_directory(internal_socket_dir).with(user: 'foo', mode: '0700')
+    end
+
+    it 'populates gitaly config.toml with custom values' do
+      expect(chef_run).to render_file(config_path)
+        .with_content("socket_path = '#{socket_path}'")
+      expect(chef_run).to render_file(config_path)
+        .with_content("internal_socket_dir = '#{internal_socket_dir}'")
+      expect(chef_run).to render_file(config_path)
+        .with_content("bin_dir = '/opt/gitlab/embedded/bin'")
+      expect(chef_run).to render_file(config_path)
+        .with_content("listen_addr = 'localhost:7777'")
+      expect(chef_run).to render_file(config_path)
+        .with_content("graceful_restart_timeout = '#{graceful_restart_timeout}'")
+      expect(chef_run).to render_file(config_path)
+        .with_content("bin_path = '#{git_bin_path}'")
+      expect(chef_run).to render_file(config_path)
+        .with_content("use_bundled_binaries = true")
+
+      gitaly_logging_section = Regexp.new([
+        %r{\[logging\]},
+        %r{level = '#{logging_level}'},
+        %r{format = '#{logging_format}'},
+        %r{sentry_dsn = '#{logging_sentry_dsn}'},
+        %r{ruby_sentry_dsn = '#{logging_ruby_sentry_dsn}'},
+        %r{sentry_environment = '#{logging_sentry_environment}'},
+      ].map(&:to_s).join('\s+'))
+
+      gitaly_ruby_section = Regexp.new([
+        %r{\[gitaly-ruby\]},
+        %r{dir = "/opt/gitlab/embedded/service/gitaly-ruby"},
+        %r{max_rss = #{ruby_max_rss}},
+        %r{graceful_restart_timeout = '#{Regexp.escape(ruby_graceful_restart_timeout)}'},
+        %r{restart_delay = '#{Regexp.escape(ruby_restart_delay)}'},
+        %r{num_workers = #{ruby_num_workers}},
+      ].map(&:to_s).join('\s+'))
+
+      gitlab_shell_section = Regexp.new([
+        %r{\[gitlab-shell\]},
+        %r{dir = "/opt/gitlab/embedded/service/gitlab-shell"},
+      ].map(&:to_s).join('\s+'))
+
+      gitlab_section = Regexp.new([
+        %r{\[gitlab\]},
+        %r{url = '#{Regexp.escape(gitlab_url)}'},
+        %r{\[gitlab.http-settings\]},
+        %r{read_timeout = #{read_timeout}},
+        %r{user = '#{Regexp.escape(user)}'},
+        %r{password = '#{Regexp.escape(password)}'},
+        %r{ca_file = '#{Regexp.escape(ca_file)}'},
+        %r{ca_path = '#{Regexp.escape(ca_path)}'},
+        %r{self_signed_cert = #{self_signed_cert}},
+      ].map(&:to_s).join('\s+'))
+
+      hooks_section = Regexp.new([
+        %r{\[hooks\]},
+        %r{custom_hooks_dir = '#{Regexp.escape(gitaly_custom_hooks_dir)}'},
+      ].map(&:to_s).join('\s+'))
+
+      maintenance_section = Regexp.new([
+        %r{\[daily_maintenance\]},
+        %r{start_hour = #{daily_maintenance_start_hour}},
+        %r{start_minute = #{daily_maintenance_start_minute}},
+        %r{duration = '#{daily_maintenance_duration}'},
+        %r{storages = #{Regexp.escape(daily_maintenance_storages.to_s)}},
+      ].map(&:to_s).join('\s+'))
+
+      cgroups_section = Regexp.new([
+        %r{\[cgroups\]},
+        %r{count = #{cgroups_count}},
+        %r{mountpoint = '#{cgroups_mountpoint}'},
+        %r{hierarchy_root = '#{cgroups_hierarchy_root}'},
+      ].map(&:to_s).join('\s+'))
+
+      cgroups_memory_section = Regexp.new([
+        %r{\[cgroups\.memory\]},
+        %r{enabled = #{cgroups_memory_enabled}},
+        %r{limit = #{cgroups_memory_limit}},
+      ].map(&:to_s).join('\s+'))
+
+      cgroups_cpu_section = Regexp.new([
+        %r{\[cgroups\.cpu\]},
+        %r{enabled = #{cgroups_cpu_enabled}},
+        %r{shares = #{cgroups_cpu_shares}},
+      ].map(&:to_s).join('\s+'))
+
+      pack_objects_cache_section = Regexp.new([
+        %r{\[pack_objects_cache\]\nenabled = true},
+        %r{dir = '#{pack_objects_cache_dir}'},
+        %r{max_age = '#{pack_objects_cache_max_age}'},
+      ].map(&:to_s).join('\s+'))
+
+      expect(chef_run).to render_file(config_path).with_content { |content|
+        expect(content).to include("tls_listen_addr = 'localhost:8888'")
+        expect(content).to include("certificate_path = '/path/to/cert.pem'")
+        expect(content).to include("key_path = '/path/to/key.pem'")
+        expect(content).to include(%(rugged_git_config_search_path = "#{ruby_rugged_git_config_search_path}"))
+        expect(content).to include("prometheus_listen_addr = 'localhost:9000'")
+        expect(content).to match(gitaly_logging_section)
+        expect(content).to match(%r{\[prometheus\]\s+grpc_latency_buckets = #{Regexp.escape(prometheus_grpc_latency_buckets)}})
+        expect(content).to match(%r{\[auth\]\s+token = '#{Regexp.escape(auth_token)}'\s+transitioning = true})
+        expect(content).to match(gitaly_ruby_section)
+        expect(content).to match(%r{\[git\]\s+catfile_cache_size = 50})
+        expect(content).to match(gitlab_shell_section)
+        expect(content).to match(gitlab_section)
+        expect(content).to match(hooks_section)
+        expect(content).to match(maintenance_section)
+        expect(content).to match(cgroups_section)
+        expect(content).to match(cgroups_memory_section)
+        expect(content).to match(cgroups_cpu_section)
+        expect(content).to match(pack_objects_cache_section)
+      }
+    end
+
+    it 'populates gitaly config.toml with custom deprecated values' do
+      stub_gitlab_rb(
+        gitlab_shell: {
+          custom_hooks_dir: gitlab_shell_custom_hooks_dir
+        }
+      )
+
+      hooks_section = Regexp.new([
+        %r{\[hooks\]},
+        %r{custom_hooks_dir = '#{Regexp.escape(gitlab_shell_custom_hooks_dir)}'},
+      ].map(&:to_s).join('\s+'))
+
+      expect(chef_run).to render_file(config_path).with_content { |content|
+        expect(content).to match(hooks_section)
+      }
+    end
+
+    it 'renders the runit run script with custom values' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/run')
+        .with_content(%r{ulimit -n #{open_files_ulimit}})
+    end
+
+    it 'renders the runit run script with cgroup root creation' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/run').with_content { |content|
+        expect(content).to match(%r{mkdir -m 0700 -p #{cgroups_mountpoint}/memory/#{cgroups_hierarchy_root}})
+        expect(content).to match(%r{mkdir -m 0700 -p #{cgroups_mountpoint}/cpu/#{cgroups_hierarchy_root}})
+        expect(content).to match(%r{chown foo:bar #{cgroups_mountpoint}/memory/#{cgroups_hierarchy_root}})
+        expect(content).to match(%r{chown foo:bar #{cgroups_mountpoint}/cpu/#{cgroups_hierarchy_root}})
+      }
+    end
+
+    it 'populates sv related log files' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/log/run')
+        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/gitaly/)
+    end
+
+    context 'when using multiple maintenance storage entries' do
+      let(:daily_maintenance_storages) { %w(storage0 storage1 storage2) }
+
+      it 'renders daily_maintenance with multiple storage entries' do
+        expect(chef_run).to render_file(config_path).with_content { |content|
+          expect(content).to include("storages = #{daily_maintenance_storages}")
+        }
+      end
+    end
+
+    context 'when maintenance is disabled' do
+      let(:daily_maintenance_disabled) { true }
+
+      it 'renders daily_maintenance with disabled set to true' do
+        expect(chef_run).to render_file(config_path).with_content { |content|
+          expect(content).to include("[daily_maintenance]\ndisabled = true\n\n")
+        }
+      end
+    end
+
+    context 'when using gitaly storage configuration' do
+      before do
+        stub_gitlab_rb(
+          gitaly: {
+            storage: [
+              {
+                'name' => 'default',
+                'path' => '/tmp/path-1'
+              },
+              {
+                'name' => 'nfs1',
+                'path' => '/mnt/nfs1'
+              }
+            ]
+          }
+        )
+      end
+
+      it 'populates gitaly config.toml with custom storages' do
+        expect(chef_run).to render_file(config_path)
+          .with_content(%r{\[\[storage\]\]\s+name = 'default'\s+path = '/tmp/path-1'})
+        expect(chef_run).to render_file(config_path)
+          .with_content(%r{\[\[storage\]\]\s+name = 'nfs1'\s+path = '/mnt/nfs1'})
+      end
+    end
+
+    context 'when using git_data_dirs storage configuration' do
+      context 'using local gitaly' do
+        before do
+          stub_gitlab_rb(
+            {
+              git_data_dirs:
+              {
+                'default' => { 'path' => '/tmp/default/git-data' },
+                'nfs1' => { 'path' => '/mnt/nfs1' }
+              }
+            }
+          )
+        end
+
+        it 'populates gitaly config.toml with custom storages' do
+          expect(chef_run).to render_file(config_path)
+            .with_content(%r{\[\[storage\]\]\s+name = 'default'\s+path = '/tmp/default/git-data/repositories'})
+          expect(chef_run).to render_file(config_path)
+            .with_content(%r{\[\[storage\]\]\s+name = 'nfs1'\s+path = '/mnt/nfs1/repositories'})
+          expect(chef_run).not_to render_file(config_path)
+            .with_content('gitaly_address: "/var/opt/gitlab/gitaly/gitaly.socket"')
+        end
+      end
+
+      context 'using external gitaly' do
+        before do
+          stub_gitlab_rb(
+            {
+              git_data_dirs:
+              {
+                'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
+              }
+            }
+          )
+        end
+
+        it 'populates gitaly config.toml with custom storages' do
+          expect(chef_run).to render_file(config_path)
+            .with_content(%r{\[\[storage\]\]\s+name = 'default'\s+path = '/var/opt/gitlab/git-data/repositories'})
+          expect(chef_run).not_to render_file(config_path)
+            .with_content('gitaly_address: "tcp://gitaly.internal:8075"')
+        end
+      end
+    end
+  end
+
+  context 'when gitaly is disabled' do
+    before do
+      stub_gitlab_rb(gitaly: { enable: false })
+    end
+
+    it_behaves_like "disabled runit service", "gitaly"
+
+    it 'does not create the gitaly directories' do
+      expect(chef_run).not_to create_directory('/var/opt/gitlab/gitaly')
+      expect(chef_run).not_to create_directory('/var/log/gitlab/gitaly')
+      expect(chef_run).not_to create_directory('/opt/gitlab/etc/gitaly')
+      expect(chef_run).not_to create_file('/var/opt/gitlab/gitaly/config.toml')
+    end
+  end
+
+  context 'when using concurrency configuration' do
+    before do
+      stub_gitlab_rb(
+        {
+          gitaly: {
+            concurrency: [
+              {
+                'rpc' => "/gitaly.SmartHTTPService/PostReceivePack",
+                'max_per_repo' => 20
+              }, {
+                'rpc' => "/gitaly.SSHService/SSHUploadPack",
+                'max_per_repo' => 5
+              }
+            ]
+          }
+        }
+      )
+    end
+
+    it 'populates gitaly config.toml with custom concurrency configurations' do
+      expect(chef_run).to render_file(config_path)
+        .with_content(%r{\[\[concurrency\]\]\s+rpc = "/gitaly.SmartHTTPService/PostReceivePack"\s+max_per_repo = 20})
+      expect(chef_run).to render_file(config_path)
+        .with_content(%r{\[\[concurrency\]\]\s+rpc = "/gitaly.SSHService/SSHUploadPack"\s+max_per_repo = 5})
+    end
+  end
+
+  shared_examples 'empty concurrency configuration' do
+    it 'does not generate a gitaly concurrency configuration' do
+      expect(chef_run).not_to render_file(config_path)
+        .with_content(%r{\[\[concurrency\]\]})
+    end
+  end
+
+  context 'when not using concurrency configuration' do
+    context 'when concurrency configuration is not set' do
+      before do
+        stub_gitlab_rb(
+          {
+            gitaly: {
+            }
+          }
+        )
+      end
+
+      it_behaves_like 'empty concurrency configuration'
+    end
+
+    context 'when concurrency configuration is empty' do
+      before do
+        stub_gitlab_rb(
+          {
+            gitaly: {
+              concurrency: []
+            }
+          }
+        )
+      end
+
+      it_behaves_like 'empty concurrency configuration'
+    end
+
+    context 'when max_queue_size and max_queue_wait are empty' do
+      before do
+        stub_gitlab_rb(
+          {
+            gitaly: {
+              concurrency: [
+                {
+                  'rpc' => "/gitaly.SmartHTTPService/PostReceivePack",
+                  'max_per_repo' => 20,
+                }, {
+                  'rpc' => "/gitaly.SSHService/SSHUploadPack",
+                  'max_per_repo' => 5,
+                }
+              ]
+            }
+          }
+        )
+      end
+
+      it 'populates gitaly config.toml without max_queue_size and max_queue_wait' do
+        expect(chef_run).to render_file(config_path).with_content { |content|
+          expect(content).not_to include("max_queue_size")
+          expect(content).not_to include("max_queue_wait")
+        }
+      end
+    end
+
+    context 'when max_per_repo is empty' do
+      before do
+        stub_gitlab_rb(
+          {
+            gitaly: {
+              concurrency: [
+                {
+                  'rpc' => "/gitaly.SmartHTTPService/PostReceivePack",
+                  'max_queue_size' => '10s'
+                }, {
+                  'rpc' => "/gitaly.SSHService/SSHUploadPack",
+                  'max_queue_size' => '10s'
+                }
+              ]
+            }
+          }
+        )
+      end
+
+      it 'populates gitaly config.toml without max_per_repo' do
+        expect(chef_run).to render_file(config_path).with_content { |content|
+          expect(content).not_to include("max_per_repo")
+        }
+      end
+    end
+
+    context 'when max_queue_wait is set' do
+      before do
+        stub_gitlab_rb(
+          {
+            gitaly: {
+              concurrency: [
+                {
+                  'rpc' => "/gitaly.SmartHTTPService/PostReceivePack",
+                  'max_queue_wait' => "10s",
+                }
+              ]
+            }
+          }
+        )
+      end
+
+      it 'populates gitaly config.toml with quoted max_queue_wait' do
+        expect(chef_run).to render_file(config_path)
+        .with_content(%r{\[\[concurrency\]\]\s+rpc = "/gitaly.SmartHTTPService/PostReceivePack"\s+max_queue_wait = "10s"})
+      end
+    end
+  end
+
+  context 'populates default env variables' do
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitaly/env').with_variables(default_vars)
+    end
+  end
+
+  context 'computes env variables based on other values' do
+    before do
+      stub_gitlab_rb(
+        {
+          user: {
+            home: "/my/random/path"
+          }
+        }
+      )
+    end
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitaly/env').with_variables(
+        default_vars.merge(
+          {
+            'HOME' => '/my/random/path',
+          }
+        )
+      )
+    end
+  end
+
+  context 'with a non-default workhorse unix socket' do
+    context 'with only a listen address set' do
+      before do
+        stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/fake/workhorse/socket' })
+      end
+
+      it 'create config file with provided values' do
+        expect(chef_run).to render_file(config_path)
+          .with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Ffake%2Fworkhorse%2Fsocket'\s+relative_url_root = ''})
+      end
+    end
+
+    context 'with only a socket directory set' do
+      before do
+        stub_gitlab_rb(gitlab_workhorse: { sockets_directory: '/fake/workhorse/sockets' })
+      end
+
+      it 'create config file with provided values' do
+        expect(chef_run).to render_file(config_path)
+          .with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Ffake%2Fworkhorse%2Fsockets%2Fsocket'\s+relative_url_root = ''})
+      end
+    end
+
+    context 'with a listen_address and a sockets_directory set' do
+      before do
+        stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/sockets/in/the/wind', sockets_directory: '/sockets/in/the' })
+      end
+
+      it 'create config file with provided values' do
+        expect(chef_run).to render_file(config_path)
+          .with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Fsockets%2Fin%2Fthe%2Fwind'\s+relative_url_root = ''})
+      end
+    end
+  end
+
+  context 'with a tcp workhorse listener' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'http://example.com/gitlab',
+        gitlab_workhorse: {
+          listen_network: 'tcp',
+          listen_addr: 'localhost:1234'
+        }
+      )
+    end
+
+    it 'create config file with only the URL set' do
+      expect(chef_run).to render_file(config_path).with_content { |content|
+        expect(content).to match(%r{\[gitlab\]\s+url = 'http://localhost:1234/gitlab'})
+        expect(content).not_to match(/relative_url_root/)
+      }
+    end
+  end
+
+  context 'with relative path in external_url' do
+    before do
+      stub_gitlab_rb(external_url: 'http://example.com/gitlab')
+    end
+
+    it 'create config file with the relative_url_root set' do
+      expect(chef_run).to render_file(config_path)
+        .with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket'\s+relative_url_root = '/gitlab'})
+    end
+  end
+
+  context 'with cgroups mountpoint and hierarchy_root' do
+    before do
+      stub_gitlab_rb(
+        gitaly: {
+          cgroups_mountpoint: '/sys/fs/cgroup',
+          cgroups_hierarchy_root: 'gitaly'
+        }
+      )
+    end
+  end
+
+  include_examples "consul service discovery", "gitaly", "gitaly"
+end
+
+RSpec.describe 'gitaly::git_data_dirs' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when user has not specified git_data_dir' do
+    it 'defaults to correct path' do
+      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages'])
+        .to eql('default' => { 'path' => '/var/opt/gitlab/git-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' })
+    end
+  end
+
+  context 'when gitaly is set to use a listen_addr' do
+    before do
+      stub_gitlab_rb(git_data_dirs: {
+                       'default' => {
+                         'path' => '/tmp/user/git-data'
+                       }
+                     }, gitaly: {
+                       socket_path: '',
+                       listen_addr: 'localhost:8123'
+                     })
+    end
+
+    it 'correctly sets the repository storage directories' do
+      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages'])
+        .to eql('default' => { 'path' => '/tmp/user/git-data/repositories', 'gitaly_address' => 'tcp://localhost:8123' })
+    end
+  end
+
+  context 'when gitaly is set to use a tls_listen_addr' do
+    before do
+      stub_gitlab_rb(git_data_dirs: {
+                       'default' => {
+                         'path' => '/tmp/user/git-data'
+                       }
+                     }, gitaly: {
+                       socket_path: '', tls_listen_addr: 'localhost:8123'
+                     })
+    end
+
+    it 'correctly sets the repository storage directories' do
+      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages'])
+        .to eql('default' => { 'path' => '/tmp/user/git-data/repositories', 'gitaly_address' => 'tls://localhost:8123' })
+    end
+  end
+
+  context 'when both tls and socket' do
+    before do
+      stub_gitlab_rb(git_data_dirs: {
+                       'default' => {
+                         'path' => '/tmp/user/git-data'
+                       }
+                     }, gitaly: {
+                       socket_path: '/some/socket/path.socket', tls_listen_addr: 'localhost:8123'
+                     })
+    end
+
+    it 'TlS should take precedence' do
+      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages'])
+        .to eql('default' => { 'path' => '/tmp/user/git-data/repositories', 'gitaly_address' => 'tls://localhost:8123' })
+    end
+  end
+
+  context 'when git_data_dirs is set to multiple directories' do
+    before do
+      stub_gitlab_rb({
+                       git_data_dirs: {
+                         'default' => { 'path' => '/tmp/default/git-data' },
+                         'overflow' => { 'path' => '/tmp/other/git-overflow-data' }
+                       }
+                     })
+    end
+
+    it 'correctly sets the repository storage directories' do
+      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages']).to eql({
+                                                                                        'default' => { 'path' => '/tmp/default/git-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
+                                                                                        'overflow' => { 'path' => '/tmp/other/git-overflow-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' }
+                                                                                      })
+    end
+  end
+
+  context 'when git_data_dirs is set to multiple directories with different gitaly addresses' do
+    before do
+      stub_gitlab_rb({
+                       git_data_dirs: {
+                         'default' => { 'path' => '/tmp/default/git-data' },
+                         'overflow' => { 'path' => '/tmp/other/git-overflow-data', 'gitaly_address' => 'tcp://localhost:8123', 'gitaly_token' => '123secret456gitaly' }
+                       }
+                     })
+    end
+
+    it 'correctly sets the repository storage directories' do
+      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages']).to eql({
+                                                                                        'default' => { 'path' => '/tmp/default/git-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
+                                                                                        'overflow' => { 'path' => '/tmp/other/git-overflow-data/repositories', 'gitaly_address' => 'tcp://localhost:8123', 'gitaly_token' => '123secret456gitaly' }
+                                                                                      })
+    end
+  end
+
+  context 'when path not defined in git_data_dirs' do
+    before do
+      stub_gitlab_rb(
+        {
+          git_data_dirs:
+          {
+            'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
+          }
+        }
+      )
+    end
+
+    it 'correctly sets the repository storage directories' do
+      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages']).to eql({ 'default' => { 'path' => '/var/opt/gitlab/git-data/repositories', 'gitaly_address' => 'tcp://gitaly.internal:8075' } })
+    end
+  end
+
+  context 'when git_data_dirs is set with symbol keys rather than string keys' do
+    before do
+      with_symbol_keys = {
+        default: { path: '/tmp/default/git-data' },
+        overflow: { path: '/tmp/other/git-overflow-data' }
+      }
+
+      allow(Gitlab).to receive(:[]).with('git_data_dirs').and_return(with_symbol_keys)
+    end
+
+    it 'correctly sets the repository storage directories' do
+      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages']).to eql({
+                                                                                        'default' => { 'path' => '/tmp/default/git-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
+                                                                                        'overflow' => { 'path' => '/tmp/other/git-overflow-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' }
+                                                                                      })
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/libraries/gitlab_geo_helper_spec.rb b/spec/chef/cookbooks/gitlab-ee/libraries/gitlab_geo_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvbGlicmFyaWVzL2dpdGxhYl9nZW9faGVscGVyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/libraries/gitlab_geo_helper_spec.rb
@@ -0,0 +1,59 @@
+require 'chef_helper'
+
+RSpec.describe GitlabGeoHelper do
+  cached(:chef_run) { converge_config(is_ee: true) }
+  subject { described_class.new(chef_run.node) }
+
+  describe '#migrated?' do
+    let(:state_file) { '/tmp/geo-db-migrate-xyz-123' }
+
+    before do
+      allow(subject).to receive(:db_migrate_status_file) { state_file }
+    end
+
+    it 'returns true when migration file exists and content is zero' do
+      stub_file_content(state_file, '0')
+
+      expect(subject.migrated?).to eq true
+    end
+
+    it 'returns false when migration file doesnt exist' do
+      allow(File).to receive(:exist?).with(state_file) { false }
+
+      expect(subject.migrated?).to eq false
+    end
+
+    it 'returns false when content is not zero' do
+      stub_file_content(state_file, '1')
+
+      expect(subject.migrated?).to eq false
+    end
+  end
+
+  describe '#db_migrate_status_file' do
+    let(:connection_digest) { '32596e8376077c3ef8d5cf52f15279ba' }
+    let(:revision) { '8b21d70' }
+
+    before do
+      allow(subject).to receive(:connection_digest) { connection_digest }
+      allow(subject).to receive(:revision) { revision }
+    end
+
+    it 'contains connection digest' do
+      expect(subject.db_migrate_status_file).to include(connection_digest)
+    end
+
+    it 'contains revision number' do
+      expect(subject.db_migrate_status_file).to include(revision)
+    end
+
+    it 'follows specific pattern' do
+      expect(subject.db_migrate_status_file).to include("geo-db-migrate-#{connection_digest}-#{revision}")
+    end
+  end
+
+  def stub_file_content(fullpath, content)
+    allow(File).to receive(:exist?).with(fullpath) { true }
+    allow(IO).to receive(:read).with(fullpath) { content }
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/libraries/sentinel_helper_spec.rb b/spec/chef/cookbooks/gitlab-ee/libraries/sentinel_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvbGlicmFyaWVzL3NlbnRpbmVsX2hlbHBlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/libraries/sentinel_helper_spec.rb
@@ -0,0 +1,46 @@
+require 'chef_helper'
+
+RSpec.describe SentinelHelper do
+  let(:chef_run) { converge_config(is_ee: true) }
+  subject { described_class.new(chef_run.node) }
+  before { allow(Gitlab).to receive(:[]).and_call_original }
+
+  context '#myid' do
+    context 'when retrieving from config' do
+      it 'fails when myid is not 40 hex-characters long' do
+        stub_gitlab_rb(
+          sentinel: {
+            myid: 'wrongid'
+          }
+        )
+
+        expect { subject.myid }.to raise_error RuntimeError
+      end
+
+      it 'works when myid is 40 hex-characters long' do
+        stub_gitlab_rb(
+          sentinel: {
+            myid: '1234567890abcdef1234567890abcdef12345678'
+          }
+        )
+
+        expect { subject.myid }.not_to raise_error
+      end
+    end
+
+    context 'when no config is defined' do
+      let(:myid) { 'abcdef1234567890abcdef1234567890abcdef1' }
+
+      it 'generates a random myid' do
+        expect(subject.myid).not_to be_empty
+      end
+
+      it 'persist generated value into JSON file' do
+        allow(subject).to receive(:generate_myid).at_least(:once) { myid }
+
+        expect(subject).to receive(:save_to_file).with({ 'myid' => myid })
+        subject.myid
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/libraries/sentinel_spec.rb b/spec/chef/cookbooks/gitlab-ee/libraries/sentinel_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvbGlicmFyaWVzL3NlbnRpbmVsX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/libraries/sentinel_spec.rb
@@ -0,0 +1,68 @@
+# This spec is to test the Sentinel helper and whether the values parsed
+# are the ones we expect
+require 'chef_helper'
+
+RSpec.describe 'Sentinel' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::config') }
+  let(:node) { chef_run.node }
+  subject { ::Sentinel }
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context '.parse_variables' do
+    context 'When sentinel is enabled' do
+      before do
+        stub_gitlab_rb(
+          redis_sentinel_role: {
+            enable: true,
+          }
+        )
+      end
+      it 'delegates to parse_sentinel_settings' do
+        expect(subject).to receive(:parse_sentinel_settings)
+
+        subject.parse_variables
+      end
+
+      context 'when redis announce_ip is defined' do
+        let(:redis_master_ip) { '1.1.1.1' }
+        let(:redis_announce_ip) { '10.10.10.10' }
+        before do
+          stub_gitlab_rb(
+            redis: {
+              master_ip: redis_master_ip,
+              announce_ip: redis_announce_ip
+            }
+          )
+        end
+
+        it 'Sentinel announce_ip is autofilled based on redis announce_ip' do
+          expect(node['gitlab']['sentinel']['announce_ip']).to eq redis_announce_ip
+
+          subject.parse_sentinel_settings
+        end
+      end
+
+      context 'when redis announce_port is defined' do
+        let(:redis_announce_port) { 6370 }
+        let(:sentinel_port) { 26370 }
+        before do
+          stub_gitlab_rb(
+            sentinel: {
+              port: sentinel_port
+            },
+            redis: {
+              announce_port: redis_announce_port
+            }
+          )
+        end
+        it 'Sentinel announce_port is autofilled based on sentinel port' do
+          expect(node['gitlab']['sentinel']['announce_port']).to eq sentinel_port
+
+          subject.parse_sentinel_settings
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/recipes/default_spec.rb b/spec/chef/cookbooks/gitlab-ee/recipes/default_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvcmVjaXBlcy9kZWZhdWx0X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/recipes/default_spec.rb
@@ -0,0 +1,33 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab-ee::default' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'postgresql is enabled' do
+    context 'pgbouncer will not connect to postgresql' do
+      it 'should always include the pgbouncer_user recipe' do
+        expect(chef_run).to include_recipe('pgbouncer::user')
+      end
+    end
+
+    context 'pgbouncer will connect to postgresql' do
+      before do
+        stub_gitlab_rb(
+          {
+            postgresql: {
+              pgbouncer_user_password: 'fakepassword'
+            }
+          }
+        )
+      end
+
+      it 'should include the pgbouncer_user recipe' do
+        expect(chef_run).to include_recipe('pgbouncer::user')
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/recipes/geo-logcursor_spec.rb b/spec/chef/cookbooks/gitlab-ee/recipes/geo-logcursor_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvcmVjaXBlcy9nZW8tbG9nY3Vyc29yX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/recipes/geo-logcursor_spec.rb
@@ -0,0 +1,41 @@
+#
+# Copyright:: Copyright (c) 2017 GitLab Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef_helper'
+
+RSpec.describe 'gitlab-ee::geo-logcursor' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe 'when disabled' do
+    it_behaves_like 'disabled runit service', 'geo-logcursor'
+  end
+
+  describe 'when enabled' do
+    before do
+      stub_gitlab_rb(
+        geo_logcursor: {
+          enable: true,
+        }
+      )
+    end
+
+    it_behaves_like 'enabled runit service', 'geo-logcursor', 'root', 'root'
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/recipes/geo-postgresql_spec.rb b/spec/chef/cookbooks/gitlab-ee/recipes/geo-postgresql_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvcmVjaXBlcy9nZW8tcG9zdGdyZXNxbF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/recipes/geo-postgresql_spec.rb
@@ -0,0 +1,388 @@
+require 'chef_helper'
+
+RSpec.describe 'geo postgresql' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
+  let(:postgresql_conf) { '/var/opt/gitlab/geo-postgresql/data/postgresql.conf' }
+  let(:runtime_conf) { '/var/opt/gitlab/geo-postgresql/data/runtime.conf' }
+  let(:pg_hba_conf) { '/var/opt/gitlab/geo-postgresql/data/pg_hba.conf' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow_any_instance_of(GeoPgHelper).to receive(:version).and_return(PGVersion.new('best_version'))
+    allow_any_instance_of(GeoPgHelper).to receive(:running_version).and_return(PGVersion.new('best_version'))
+    allow_any_instance_of(GeoPgHelper).to receive(:database_version).and_return(PGVersion.new('best_version'))
+
+    # Workaround for Chef reloading instances across different examples
+    allow_any_instance_of(GeoPgHelper).to receive(:bootstrapped?).and_return(true)
+    allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).and_call_original
+    allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).with('geo-postgresql').and_return(true)
+  end
+
+  context 'when geo postgres is enabled' do
+    before do
+      stub_gitlab_rb(geo_postgresql: { enable: true })
+    end
+
+    it 'includes the postgresql::bin recipe' do
+      expect(chef_run).to include_recipe('postgresql::bin')
+    end
+
+    it 'includes the postgresql_user recipe' do
+      expect(chef_run).to include_recipe('postgresql::user')
+    end
+
+    it 'includes the postgresql_sysctl recipe' do
+      expect(chef_run).to include_recipe('postgresql::sysctl')
+    end
+
+    it 'does not warn the user that a restart is needed by default' do
+      allow_any_instance_of(GeoPgHelper).to receive(:is_running?).and_return(true)
+      expect(chef_run).not_to run_ruby_block('warn pending geo-postgresql restart')
+    end
+
+    it 'notifies restarts postgresql when the postgresql runit run file changes' do
+      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).and_call_original
+      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).with('geo-postgresql').and_return(true)
+
+      psql_service = chef_run.service('geo-postgresql')
+      expect(psql_service).not_to subscribe_to('template[/opt/gitlab/sv/geo-postgresql/run]').on(:restart).delayed
+    end
+
+    it 'includes runtime.conf in postgresql.conf' do
+      expect(chef_run).to render_file(postgresql_conf)
+        .with_content(/include 'runtime.conf'/)
+    end
+
+    it 'creates the gitlab_geo role in the geo-postgresql database, without a password' do
+      expect(chef_run).to create_postgresql_user('gitlab_geo').with(password: nil)
+    end
+
+    it 'creates gitlabhq_geo_production database' do
+      params = {
+        owner: 'gitlab_geo'
+      }
+      expect(chef_run).to create_postgresql_database('gitlabhq_geo_production').with(params)
+    end
+
+    context 'with default settings' do
+      it_behaves_like 'enabled runit service', 'geo-postgresql', 'root', 'root'
+
+      context 'when rendering postgresql.conf' do
+        it 'correctly sets the shared_preload_libraries default setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['shared_preload_libraries']).to be_nil
+
+          expect(chef_run).to render_file(postgresql_conf)
+            .with_content(/shared_preload_libraries = ''/)
+        end
+
+        it 'sets archive settings' do
+          expect(chef_run).to render_file(
+            postgresql_conf
+          ).with_content(/archive_mode = off/)
+        end
+
+        it 'sets the max_replication_slots setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['max_replication_slots']).to eq(0)
+
+          expect(chef_run).to render_file(
+            postgresql_conf
+          ).with_content(/max_replication_slots = 0/)
+        end
+
+        it 'sets the synchronous_commit setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['synchronous_standby_names']).to eq('')
+
+          expect(chef_run).to render_file(
+            postgresql_conf
+          ).with_content(/synchronous_standby_names = ''/)
+        end
+
+        it 'does not set dynamic_shared_memory_type by default' do
+          expect(chef_run).not_to render_file(
+            postgresql_conf
+          ).with_content(/^dynamic_shared_memory_type = /)
+        end
+
+        it 'sets the max_locks_per_transaction setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['max_locks_per_transaction'])
+            .to eq(128)
+
+          expect(chef_run).to render_file(
+            postgresql_conf
+          ).with_content(/max_locks_per_transaction = 128/)
+        end
+      end
+      context 'when rendering runtime.conf' do
+        it 'correctly sets the log_line_prefix default setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['log_line_prefix']).to be_nil
+
+          expect(chef_run).to render_file(runtime_conf)
+            .with_content(/log_line_prefix = ''/)
+        end
+
+        it 'sets max_standby settings' do
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/max_standby_archive_delay = 30s/)
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/max_standby_streaming_delay = 30s/)
+        end
+
+        it 'sets archive settings' do
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/archive_command = ''/)
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/archive_timeout = 0/)
+        end
+
+        it 'sets the synchronous_commit setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['synchronous_commit']).to eq('on')
+
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/synchronous_commit = on/)
+        end
+
+        it 'sets the hot_standby_feedback setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['hot_standby_feedback'])
+            .to eq('off')
+
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/hot_standby_feedback = off/)
+        end
+
+        it 'sets the random_page_cost setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['random_page_cost'])
+            .to eq(2.0)
+
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/random_page_cost = 2\.0/)
+        end
+
+        it 'sets the log_temp_files setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['log_temp_files'])
+            .to eq(-1)
+
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/log_temp_files = -1/)
+        end
+
+        it 'sets the log_checkpoints setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['log_checkpoints'])
+            .to eq('off')
+
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/log_checkpoints = off/)
+        end
+
+        it 'sets idle_in_transaction_session_timeout' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['idle_in_transaction_session_timeout'])
+            .to eq('60000')
+
+          expect(chef_run).to render_file(runtime_conf)
+            .with_content(/idle_in_transaction_session_timeout = 60000/)
+        end
+
+        it 'sets effective_io_concurrency' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['effective_io_concurrency'])
+            .to eq(1)
+
+          expect(chef_run).to render_file(runtime_conf)
+            .with_content(/effective_io_concurrency = 1/)
+        end
+
+        it 'sets max_worker_processes' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['max_worker_processes'])
+            .to eq(8)
+
+          expect(chef_run).to render_file(runtime_conf)
+            .with_content(/max_worker_processes = 8/)
+        end
+
+        it 'sets max_parallel_workers_per_gather' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['max_parallel_workers_per_gather'])
+            .to eq(0)
+
+          expect(chef_run).to render_file(runtime_conf)
+            .with_content(/max_parallel_workers_per_gather = 0/)
+        end
+
+        it 'sets log_lock_waits' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['log_lock_waits'])
+            .to eq(1)
+
+          expect(chef_run).to render_file(runtime_conf)
+            .with_content(/log_lock_waits = 1/)
+        end
+
+        it 'sets deadlock_timeout' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['deadlock_timeout'])
+            .to eq('5s')
+
+          expect(chef_run).to render_file(runtime_conf)
+            .with_content(/deadlock_timeout = '5s'/)
+        end
+
+        it 'sets track_io_timing' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['track_io_timing'])
+            .to eq('off')
+
+          expect(chef_run).to render_file(runtime_conf)
+            .with_content(/track_io_timing = 'off'/)
+        end
+      end
+    end
+
+    context 'with user specified settings' do
+      before do
+        stub_gitlab_rb(geo_postgresql: {
+                         enable: true,
+                         dynamic_shared_memory_type: 'none',
+                         custom_pg_hba_entries: {
+                           foo: [
+                             type: 'host',
+                             database: 'foo',
+                             user: 'bar',
+                             cidr: '127.0.0.1/32',
+                             method: 'trust'
+                           ]
+                         },
+                         sql_user_password: 'fakepasswordhash',
+                         shared_preload_libraries: 'pg_stat_statements',
+                         log_line_prefix: '%a',
+                         max_standby_archive_delay: '60s',
+                         max_standby_streaming_delay: '120s',
+                         archive_mode: 'on',
+                         archive_command: 'command',
+                         archive_timeout: '120',
+                       },
+                       postgresql: {
+                         username: 'foo',
+                         group: 'bar'
+                       }
+                      )
+      end
+
+      it 'notifies geo-postgresql reload' do
+        runtime_resource = chef_run.template(runtime_conf)
+        expect(runtime_resource).to notify('execute[reload geo-postgresql]').to(:run).immediately
+      end
+
+      it 'creates the gitlab_geo role in the geo-postgresql database with the specified password' do
+        expect(chef_run).to create_postgresql_user('gitlab_geo').with(password: 'md5fakepasswordhash')
+      end
+
+      context 'when rendering postgresql.conf' do
+        it 'sets the dynamic_shared_memory_type' do
+          expect(chef_run).to render_file(
+            postgresql_conf
+          ).with_content(/^dynamic_shared_memory_type = none/)
+        end
+
+        it 'correctly sets the shared_preload_libraries setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['shared_preload_libraries']).to eql('pg_stat_statements')
+
+          expect(chef_run).to render_file(postgresql_conf)
+            .with_content(/shared_preload_libraries = 'pg_stat_statements'/)
+        end
+
+        it 'sets archive settings' do
+          expect(chef_run).to render_file(
+            postgresql_conf
+          ).with_content(/archive_mode = on/)
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/archive_command = 'command'/)
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/archive_timeout = 120/)
+        end
+      end
+
+      context 'when rendering runtime.conf' do
+        it 'correctly sets the log_line_prefix setting' do
+          expect(chef_run.node['gitlab']['geo-postgresql']['log_line_prefix']).to eql('%a')
+
+          expect(chef_run).to render_file(runtime_conf)
+            .with_content(/log_line_prefix = '%a'/)
+        end
+
+        it 'sets max_standby settings' do
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/max_standby_archive_delay = 60s/)
+          expect(chef_run).to render_file(
+            runtime_conf
+          ).with_content(/max_standby_streaming_delay = 120s/)
+        end
+
+        context 'when rendering pg_hba.conf' do
+          it 'creates a standard pg_hba.conf' do
+            expect(chef_run).to render_file(pg_hba_conf)
+              .with_content('local   all         all                               peer map=gitlab')
+          end
+
+          it 'adds users custom entries to pg_hba.conf' do
+            expect(chef_run).to render_file(pg_hba_conf)
+              .with_content('host foo bar 127.0.0.1/32 trust')
+          end
+        end
+      end
+    end
+
+    context 'when geo postgres is disabled' do
+      before do
+        stub_gitlab_rb(geo_postgresql: { enable: false })
+      end
+
+      it_behaves_like 'disabled runit service', 'geo-postgresql'
+    end
+  end
+end
+
+RSpec.describe 'geo postgresql when version mismatches occur' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
+  let(:postgresql_conf) { '/var/opt/gitlab/geo-postgresql/data/postgresql.conf' }
+  let(:runtime_conf) { '/var/opt/gitlab/geo-postgresql/data/runtime.conf' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    stub_gitlab_rb(geo_postgresql: { enable: true })
+  end
+
+  context 'running version differs from installed version' do
+    before do
+      allow_any_instance_of(GeoPgHelper).to receive(:version).and_return(PGVersion.new('expectation'))
+      allow_any_instance_of(GeoPgHelper).to receive(:running_version).and_return(PGVersion.new('reality'))
+    end
+
+    it 'warns the user that a restart is needed' do
+      allow_any_instance_of(GeoPgHelper).to receive(:is_running?).and_return(true)
+      expect(chef_run).to run_ruby_block('warn pending geo-postgresql restart')
+    end
+
+    it 'does not warns the user that a restart is needed when geo-postgres is stopped' do
+      expect(chef_run).not_to run_ruby_block('warn pending geo-postgresql restart')
+    end
+  end
+
+  context 'running version differs from data version' do
+    before do
+      allow_any_instance_of(GeoPgHelper).to receive(:version).and_return(PGVersion.new('expectation'))
+      allow_any_instance_of(GeoPgHelper).to receive(:running_version).and_return(PGVersion.new('expectation'))
+      allow_any_instance_of(GeoPgHelper).to receive(:database_version).and_return(PGVersion.new('reality'))
+    end
+
+    it 'does not warn the user that a restart is needed' do
+      allow_any_instance_of(GeoPgHelper).to receive(:is_running?).and_return(true)
+      expect(chef_run).not_to run_ruby_block('warn pending geo-postgresql restart')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/recipes/geo-secondary_disable_spec.rb b/spec/chef/cookbooks/gitlab-ee/recipes/geo-secondary_disable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvcmVjaXBlcy9nZW8tc2Vjb25kYXJ5X2Rpc2FibGVfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/recipes/geo-secondary_disable_spec.rb
@@ -0,0 +1,19 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab-ee::geo-secondary_disable' do
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe 'when geo_secondary_role is disabled' do
+    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+    before { stub_gitlab_rb(geo_secondary_role: { enable: false }) }
+
+    it 'removes database_geo.yml symlink' do
+      expect(chef_run).to delete_templatesymlink('Removes database_geo.yml symlink')
+                            .with(link_to: '/var/opt/gitlab/gitlab-rails/etc/database_geo.yml',
+                                  link_from: '/opt/gitlab/embedded/service/gitlab-rails/config/database_geo.yml')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/recipes/geo-secondary_spec.rb b/spec/chef/cookbooks/gitlab-ee/recipes/geo-secondary_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvcmVjaXBlcy9nZW8tc2Vjb25kYXJ5X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/recipes/geo-secondary_spec.rb
@@ -0,0 +1,388 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab-ee::geo-secondary' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab-ee::default') }
+  let(:database_geo_yml) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/database_geo.yml') }
+  let(:database_geo_yml_content) { ChefSpec::Renderer.new(chef_run, database_geo_yml).content }
+  let(:generated_yml_content) { YAML.safe_load(database_geo_yml_content, [], [], true, symbolize_names: true) }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe 'when geo_secondary_role is disabled' do
+    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+    before { stub_gitlab_rb(geo_secondary_role: { enable: false }) }
+
+    it 'does not render the geo-secondary files' do
+      expect(chef_run).not_to render_file('/opt/gitlab/embedded/service/gitlab-rails/config/database_geo.yml')
+      expect(chef_run).not_to render_file('/var/opt/gitlab/gitlab-rails/etc/database_geo.yml')
+    end
+  end
+
+  describe 'when geo_secondary_role is disabled but geo-postgresql enabled' do
+    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+    before do
+      stub_gitlab_rb(geo_secondary_role: { enable: false },
+                     geo_postgresql: { enable: true })
+    end
+
+    it 'does not render the geo-secondary files' do
+      expect(chef_run).not_to render_file('/opt/gitlab/embedded/service/gitlab-rails/config/database_geo.yml')
+      expect(chef_run).not_to render_file('/var/opt/gitlab/gitlab-rails/etc/database_geo.yml')
+    end
+  end
+
+  describe 'when gitlab_rails is disabled, but geo_secondary_role is enabled' do
+    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+    before do
+      stub_gitlab_rb(geo_secondary_role: { enable: true },
+                     gitlab_rails: { enable: false })
+    end
+
+    it 'does not render the geo-secondary files' do
+      expect(chef_run).not_to create_templatesymlink('Create a database_geo.yml and create a symlink to Rails root')
+    end
+  end
+
+  describe 'when gitlab_rails is enabled' do
+    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+    before do
+      stub_gitlab_rb(geo_secondary_role: { enable: true },
+                     geo_postgresql: { enable: true },
+                     puma: { enable: false },
+                     sidekiq: { enable: false },
+                     geo_logcursor: { enable: false },
+                     gitlab_rails: { enable: true })
+    end
+
+    it 'allows gitlab_rails to be overriden' do
+      expect(chef_run.node['gitlab']['gitlab-rails']['enable']).to be true
+    end
+  end
+
+  context 'when gitaly is enabled' do
+    describe 'when gitlab_rails enable is not set' do
+      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+      before do
+        stub_gitlab_rb(geo_secondary_role: { enable: true },
+                       geo_secondary: { enable: true },
+                       geo_postgresql: { enable: false },
+                       puma: { enable: false },
+                       sidekiq: { enable: false },
+                       geo_logcursor: { enable: false },
+                       gitaly: { enable: true })
+      end
+
+      it 'ensures gitlab_rails is enabled' do
+        chef_run
+        expect(Gitlab['gitlab_rails']['enable']).to be true
+      end
+    end
+
+    describe 'when gitlab_rails enable is provided' do
+      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+      before do
+        stub_gitlab_rb(geo_secondary_role: { enable: true },
+                       geo_secondary: { enable: true },
+                       geo_postgresql: { enable: false },
+                       puma: { enable: false },
+                       sidekiq: { enable: false },
+                       geo_logcursor: { enable: false },
+                       gitaly: { enable: true },
+                       gitlab_rails: { enable: false })
+      end
+
+      it 'does not override gitlab_rails enable' do
+        chef_run
+        expect(Gitlab['gitlab_rails']['enable']).to be false
+      end
+    end
+  end
+
+  context 'when geo_secondary_role is enabled but geo-postgresql is disabled' do
+    before do
+      stub_gitlab_rb(geo_secondary_role: { enable: true },
+                     geo_postgresql: { enable: false },
+                     geo_secondary: {
+                       db_host: '1.1.1.1',
+                       db_password: 'password',
+                       db_port: '5431'
+                     })
+    end
+
+    describe 'migrations' do
+      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+      it 'includes the database migration recipe' do
+        expect(chef_run).to include_recipe('gitlab-ee::geo_database_migrations')
+      end
+    end
+
+    describe 'database.yml' do
+      it 'creates the database_geo.yml template not using many database structure' do
+        expect(generated_yml_content).to eq(
+          production: {
+            adapter: 'postgresql',
+            application_name: nil,
+            collation: nil,
+            connect_timeout: nil,
+            database: 'gitlabhq_geo_production',
+            encoding: 'unicode',
+            host: '1.1.1.1',
+            keepalives: nil,
+            keepalives_count: nil,
+            keepalives_idle: nil,
+            keepalives_interval: nil,
+            load_balancing: {
+              hosts: []
+            },
+            password: 'password',
+            port: 5431,
+            prepared_statements: false,
+            socket: nil,
+            sslca: nil,
+            sslcompression: 0,
+            sslmode: nil,
+            sslrootcert: nil,
+            statement_limit: nil,
+            tcp_user_timeout: nil,
+            username: 'gitlab_geo',
+            variables: {
+              statement_timeout: nil,
+            }
+          }
+        )
+      end
+
+      context 'when SSL compression is enabled' do
+        before do
+          stub_gitlab_rb(geo_secondary: { db_sslcompression: 1 })
+        end
+
+        it 'uses provided value in database.yml' do
+          expect(generated_yml_content[:production][:sslcompression]).to eq(1)
+        end
+      end
+    end
+  end
+
+  context 'when geo_secondary_role is enabled' do
+    before do
+      stub_gitlab_rb(geo_secondary_role: { enable: true })
+
+      # Make sure other calls to `File.symlink?` are allowed.
+      allow(File).to receive(:symlink?).and_call_original
+      %w(
+        alertmanager
+        gitlab-exporter
+        gitlab-workhorse
+        logrotate
+        nginx
+        node-exporter
+        postgres-exporter
+        postgresql
+        prometheus
+        redis
+        redis-exporter
+        sidekiq
+        puma
+        gitaly
+        geo-postgresql
+        gitlab-pages
+        geo-logcursor
+        patroni
+      ).map { |svc| stub_should_notify?(svc, true) }
+    end
+
+    describe 'database.yml' do
+      let(:templatesymlink) { chef_run.templatesymlink('Create a database_geo.yml and create a symlink to Rails root') }
+
+      it 'creates the database_geo.yml template not using many database structure' do
+        expect(generated_yml_content).to eq(
+          production: {
+            adapter: 'postgresql',
+            application_name: nil,
+            collation: nil,
+            connect_timeout: nil,
+            database: 'gitlabhq_geo_production',
+            encoding: 'unicode',
+            host: '/var/opt/gitlab/geo-postgresql',
+            keepalives: nil,
+            keepalives_count: nil,
+            keepalives_idle: nil,
+            keepalives_interval: nil,
+            load_balancing: {
+              hosts: []
+            },
+            password: nil,
+            port: 5431,
+            prepared_statements: false,
+            socket: nil,
+            sslca: nil,
+            sslcompression: 0,
+            sslmode: nil,
+            sslrootcert: nil,
+            statement_limit: nil,
+            tcp_user_timeout: nil,
+            username: 'gitlab_geo',
+            variables: {
+              statement_timeout: nil,
+            }
+          }
+        )
+      end
+
+      it 'template triggers dependent services notifications' do
+        expect(templatesymlink).to notify('ruby_block[Restart geo-secondary dependent services]').to(:run).delayed
+      end
+    end
+
+    describe 'PostgreSQL gitlab-geo.conf' do
+      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+      let(:geo_conf) { '/var/opt/gitlab/postgresql/data/gitlab-geo.conf' }
+      let(:postgresql_conf) { '/var/opt/gitlab/postgresql/data/postgresql.conf' }
+
+      context 'when postgresql enabled on the node' do
+        it 'renders gitlab-geo.conf' do
+          expect(chef_run).to render_file(geo_conf)
+        end
+      end
+
+      context 'when postgresql disabled on the node' do
+        before { stub_gitlab_rb(postgresql: { enable: false }) }
+
+        it 'does not render gitlab-geo.conf' do
+          expect(chef_run).not_to render_file(geo_conf)
+        end
+      end
+    end
+
+    describe 'Restart geo-secondary dependent services' do
+      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+      let(:ruby_block) { chef_run.ruby_block('Restart geo-secondary dependent services') }
+
+      it 'does not run' do
+        expect(chef_run).not_to run_ruby_block('Restart geo-secondary dependent services')
+        expect(ruby_block).to do_nothing
+      end
+
+      it 'ruby_block triggers dependent services notifications' do
+        allow(ruby_block).to receive(:notifies)
+        ruby_block.block.call
+
+        %w(
+          puma
+          geo-logcursor
+        ).each do |svc|
+          expect(ruby_block).to have_received(:notifies).with(:restart, "runit_service[#{svc}]")
+        end
+        expect(ruby_block).to have_received(:notifies).with(:restart, "sidekiq_service[sidekiq]")
+      end
+    end
+
+    describe 'include_recipe' do
+      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+      it 'includes the Geo tracking DB recipe' do
+        expect(chef_run).to include_recipe('gitlab-ee::geo-postgresql')
+      end
+
+      it 'includes the Geo secondary recipes for Rails' do
+        expect(chef_run).to include_recipe('gitlab-ee::geo-secondary')
+        expect(chef_run).to include_recipe('gitlab-ee::geo_database_migrations')
+      end
+
+      it 'does not include the Geo database migrations recipe if Rails not needed' do
+        stub_gitlab_rb(geo_secondary_role: { enable: true },
+                       nginx: { enable: false },
+                       puma: { enable: false },
+                       sidekiq: { enable: false },
+                       gitaly: { enable: false },
+                       postgresql: { enable: false },
+                       geo_logcursor: { enable: false },
+                       redis: { enable: true })
+
+        expect(chef_run).not_to include_recipe('gitlab-ee::geo_database_migrations')
+      end
+    end
+
+    describe 'migrations' do
+      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+      it 'runs the migrations' do
+        expect(chef_run).to run_rails_migration('gitlab-geo tracking')
+      end
+    end
+
+    describe 'rails_needed?' do
+      let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab-ee::default') }
+
+      context 'manually enabled services' do
+        before do
+          stub_gitlab_rb(
+            # Everything but puma is disabled
+            puma: { enable: true },
+            sidekiq: { enable: false },
+            geo_logcursor: { enable: false },
+            gitaly: { enable: false }
+          )
+        end
+
+        it 'should need rails' do
+          expect(chef_run).to include_recipe('gitlab::gitlab-rails')
+        end
+      end
+
+      context 'manually disabled services' do
+        before do
+          stub_gitlab_rb(
+            gitaly: { enable: false },
+            puma: { enable: false },
+            sidekiq: { enable: false },
+            geo_logcursor: { enable: false }
+          )
+        end
+
+        it 'should not need rails' do
+          expect(chef_run).not_to include_recipe('gitlab::gitlab-rails')
+        end
+      end
+    end
+  end
+
+  context 'puma worker_processes' do
+    let(:chef_run) do
+      ChefSpec::SoloRunner.new do |node|
+        node.automatic['cpu']['total'] = 16
+        node.automatic['memory']['total'] = '8388608KB' # 8GB
+      end.converge('gitlab-ee::default')
+    end
+
+    it 'reduces the number of puma workers on secondary node' do
+      stub_gitlab_rb(geo_secondary_role: { enable: true })
+
+      expect(chef_run.node['gitlab']['puma']['worker_processes']).to eq 5
+    end
+
+    it 'uses the specified number of puma workers' do
+      stub_gitlab_rb(geo_secondary_role: { enable: true },
+                     puma: { worker_processes: 1 })
+
+      expect(chef_run.node['gitlab']['puma']['worker_processes']).to eq 1
+    end
+
+    it 'does not reduce the number of puma workers on primary node' do
+      stub_gitlab_rb(geo_primary_role: { enable: true })
+
+      expect(chef_run.node['gitlab']['puma']['worker_processes']).to eq 6
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/recipes/geo_database_migrations_spec.rb b/spec/chef/cookbooks/gitlab-ee/recipes/geo_database_migrations_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvcmVjaXBlcy9nZW9fZGF0YWJhc2VfbWlncmF0aW9uc19zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/recipes/geo_database_migrations_spec.rb
@@ -0,0 +1,71 @@
+require 'chef_helper'
+
+# NOTE: These specs do not verify whether the code actually ran
+# Nor whether the resource inside of the recipe was notified correctly.
+# At this moment they only verify whether the expected commands are passed
+# to the bash block.
+#
+
+RSpec.describe 'gitlab-ee::geo-database-migrations' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when migration should run' do
+    before do
+      stub_default_not_listening?(false)
+      stub_gitlab_rb(geo_secondary_role: { enable: true })
+
+      # Make sure other calls to `File.symlink?` are allowed.
+      allow(File).to receive(:symlink?).and_call_original
+      %w(
+        alertmanager
+        gitlab-exporter
+        gitlab-workhorse
+        logrotate
+        nginx
+        node-exporter
+        postgres-exporter
+        postgresql
+        prometheus
+        redis
+        redis-exporter
+        sidekiq
+        puma
+        gitaly
+        geo-postgresql
+        gitlab-pages
+        geo-logcursor
+      ).map { |svc| stub_should_notify?(svc, true) }
+    end
+
+    it 'runs the migrations with expected attributes' do
+      expect(chef_run).to run_rails_migration('gitlab-geo tracking') do |resource|
+        expect(resource.dependent_services).to include_array(%w(runit_service[puma] sidekiq_service[sidekiq]))
+        expect(resource.rake_task).to eq('geo:db:migrate')
+        expect(resource.logfile_prefix).to eq('gitlab-geo-db-migrate')
+        expect(resource.helper).to be_a(GitlabGeoHelper)
+      end
+    end
+
+    it 'starts geo-postgresql if its not running' do
+      stub_not_listening?('geo-postgresql', true)
+
+      expect(chef_run.rails_migration('gitlab-geo tracking')).to notify('execute[start geo-postgresql]').to(:run)
+    end
+  end
+
+  context 'with auto_migrate off' do
+    it 'skips running the migrations' do
+      stub_gitlab_rb({
+                       geo_secondary_role: { enable: true },
+                       geo_secondary: { auto_migrate: false }
+                     })
+
+      expect(chef_run).to include_recipe('gitlab-ee::geo_database_migrations')
+      expect(chef_run).not_to run_rails_migration('gitlab-geo tracking')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/recipes/geo_spec.rb b/spec/chef/cookbooks/gitlab-ee/recipes/geo_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvcmVjaXBlcy9nZW9fc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/recipes/geo_spec.rb
@@ -0,0 +1,166 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab-ee::geo' do
+  let(:node) { chef_run.node }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  shared_examples 'default services' do
+    it 'remain enabled', :aggregate_failures do
+      chef_run
+
+      default_services = Services.find_by_group(Services::DEFAULT_GROUP)
+      omnibus_helper = OmnibusHelper.new(node)
+
+      expect(default_services.count).to be > 0
+      default_services.each do |service|
+        expect(omnibus_helper.service_enabled?(service.tr('_', '-'))).to be(true), "#{service} was not enabled"
+      end
+    end
+  end
+
+  context 'when geo_primary_role enabled' do
+    cached(:chef_run) do
+      RSpec::Mocks.with_temporary_scope do
+        stub_gitlab_rb(geo_primary_role: { enable: true })
+      end
+      ChefSpec::SoloRunner.converge('gitlab-ee::default')
+    end
+
+    it_behaves_like 'default services'
+
+    context 'in geo_logcursor settings' do
+      it 'is not enabled' do
+        expect(node['gitlab']['geo-logcursor']['enable']).to eq(nil)
+      end
+    end
+
+    context 'in postgres settings' do
+      let(:config_attrs) { node['postgresql'] }
+
+      it 'defines sql_replication_user' do
+        expect(config_attrs['sql_replication_user']).to eq('gitlab_replicator')
+      end
+
+      it 'defines wal_level' do
+        expect(config_attrs['wal_level']).to eq('hot_standby')
+      end
+
+      it 'defines wal_log_hints' do
+        expect(config_attrs['wal_log_hints']).to eq('off')
+      end
+
+      it 'defines max_wal_senders' do
+        expect(config_attrs['max_wal_senders']).to eq(10)
+      end
+
+      it 'defines wal_keep_segments' do
+        expect(config_attrs['wal_keep_segments']).to eq(50)
+      end
+
+      it 'defines max_replication_slots' do
+        expect(config_attrs['max_replication_slots']).to eq(1)
+      end
+
+      it 'defines hot_standby' do
+        expect(config_attrs['hot_standby']).to eq('on')
+      end
+    end
+  end
+
+  context 'geo_secondary_role enabled' do
+    cached(:chef_run) do
+      RSpec::Mocks.with_temporary_scope do
+        stub_gitlab_rb(geo_secondary_role: { enable: true })
+      end
+      ChefSpec::SoloRunner.converge('gitlab-ee::default')
+    end
+
+    it_behaves_like 'default services'
+
+    context 'in geo_postgres settings' do
+      it 'is enabled' do
+        expect(node['gitlab']['geo-postgresql']['enable']).to eq(true)
+      end
+    end
+
+    context 'in geo_logcursor settings' do
+      it 'is enabled' do
+        expect(node['gitlab']['geo-logcursor']['enable']).to eq(true)
+      end
+    end
+
+    context 'in postgres settings' do
+      let(:config_attrs) { node['postgresql'] }
+
+      it 'defines wal_level' do
+        expect(config_attrs['wal_level']).to eq('hot_standby')
+      end
+
+      it 'defines wal_log_hints' do
+        expect(config_attrs['wal_log_hints']).to eq('off')
+      end
+
+      it 'defines max_wal_senders' do
+        expect(config_attrs['max_wal_senders']).to eq(10)
+      end
+
+      it 'defines wal_keep_segments' do
+        expect(config_attrs['wal_keep_segments']).to eq(10)
+      end
+
+      it 'defines max_replication_slots' do
+        expect(config_attrs['max_replication_slots']).to eq(0)
+      end
+
+      it 'defines hot_standby' do
+        expect(config_attrs['hot_standby']).to eq('on')
+      end
+
+      it 'defines standby settings' do
+        expect(config_attrs['max_standby_archive_delay']).to eq('60s')
+        expect(config_attrs['max_standby_streaming_delay']).to eq('60s')
+      end
+    end
+
+    context 'postgresql 13' do
+      let(:runtime_conf) { '/var/opt/gitlab/geo-postgresql/data/runtime.conf' }
+
+      before do
+        allow_any_instance_of(GeoPgHelper).to receive(:version).and_return(PGVersion.new('13.0'))
+        allow_any_instance_of(GeoPgHelper).to receive(:database_version).and_return(PGVersion.new('13.0'))
+      end
+
+      it 'configures wal_keep_size instead of wal_keep_segments' do
+        expect(chef_run).to render_file(runtime_conf).with_content { |content|
+          expect(content).to include("wal_keep_size")
+          expect(content).not_to include("wal_keep_segments")
+        }
+      end
+    end
+
+    context 'postgresql 12' do
+      let(:runtime_conf) { '/var/opt/gitlab/geo-postgresql/data/runtime.conf' }
+
+      before do
+        allow_any_instance_of(GeoPgHelper).to receive(:version).and_return(PGVersion.new('12.0'))
+        allow_any_instance_of(GeoPgHelper).to receive(:database_version).and_return(PGVersion.new('12.0'))
+      end
+
+      it 'configures wal_keep_segments instead of wal_keep_size' do
+        expect(chef_run).to render_file(runtime_conf).with_content { |content|
+          expect(content).to include("wal_keep_segments")
+          expect(content).to_not include("wal_keep_size")
+        }
+      end
+    end
+
+    context 'in gitlab-rails' do
+      it 'disables auto_migrate' do
+        expect(node['gitlab']['gitlab-rails']['auto_migrate']).to eq(false)
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/recipes/sentinel_spec.rb b/spec/chef/cookbooks/gitlab-ee/recipes/sentinel_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvcmVjaXBlcy9zZW50aW5lbF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/recipes/sentinel_spec.rb
@@ -0,0 +1,186 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::redis' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(sentinel_service runit_service)).converge('gitlab-ee::default') }
+  let(:redis_master_ip) { '1.1.1.1' }
+  let(:redis_announce_ip) { '10.10.10.10' }
+  let(:redis_master_password) { 'blahblahblah' }
+  let(:sentinel_conf) { '/var/opt/gitlab/sentinel/sentinel.conf' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe 'When sentinel is disabled' do
+    before do
+      stub_gitlab_rb(
+        redis: {
+          master_ip: redis_master_ip,
+          announce_ip: redis_announce_ip,
+          master_password: redis_master_password
+        },
+        redis_sentinel_role: {
+          enable: false,
+        }
+      )
+    end
+
+    it_behaves_like 'disabled runit service', 'sentinel', 'root', 'root'
+  end
+
+  describe 'When sentinel is enabled' do
+    context 'default values' do
+      before do
+        stub_gitlab_rb(
+          redis: {
+            master_ip: redis_master_ip,
+            announce_ip: redis_announce_ip,
+            master_password: redis_master_password
+          },
+          redis_sentinel_role: {
+            enable: true,
+          }
+        )
+      end
+      it 'creates redis user and group' do
+        expect(chef_run).to create_account('user and group for sentinel').with(username: 'gitlab-redis', groupname: 'gitlab-redis')
+      end
+
+      it 'renders sentinel config file with default values' do
+        expect(chef_run).to render_file('/var/opt/gitlab/sentinel/sentinel.conf')
+          .with_content { |content|
+            expect(content).to match(%r{bind 0.0.0.0})
+            expect(content).to match(%r{port 26379})
+            expect(content).to match(%r{sentinel announce-ip 10.10.10.10})
+            expect(content).to match(%r{sentinel monitor gitlab-redis 1.1.1.1 6379 1})
+            expect(content).to match(%r{sentinel down-after-milliseconds gitlab-redis 10000})
+            expect(content).to match(%r{sentinel failover-timeout gitlab-redis 60000})
+            expect(content).to match(%r{sentinel auth-pass gitlab-redis blahblahblah})
+            expect(content).not_to match(%r{^tls})
+            expect(content).to match(%r{SENTINEL resolve-hostnames no})
+            expect(content).to match(%r{SENTINEL announce-hostnames no})
+          }
+      end
+
+      it_behaves_like 'enabled runit service', 'sentinel', 'root', 'root'
+
+      context 'user overrides sentinel_use_hostnames' do
+        before do
+          stub_gitlab_rb(
+            sentinel: {
+              use_hostnames: true
+            }
+          )
+        end
+
+        it 'uses hostnames' do
+          expect(chef_run).to render_file(sentinel_conf).with_content { |content|
+            expect(content).to match(%r{SENTINEL resolve-hostnames yes})
+            expect(content).to match(%r{SENTINEL announce-hostnames yes})
+          }
+        end
+      end
+    end
+
+    context 'user specified values' do
+      before do
+        stub_gitlab_rb(
+          redis_sentinel_role: {
+            enable: true,
+          },
+          redis: {
+            username: 'foo',
+            group: 'bar',
+            master_ip: redis_master_ip,
+            announce_ip: 'fake.hostname.local',
+            master_password: redis_master_password
+          }
+        )
+      end
+      it 'creates redis user and group' do
+        expect(chef_run).to create_account('user and group for sentinel').with(username: 'foo', groupname: 'bar')
+      end
+
+      it_behaves_like 'enabled runit service', 'sentinel', 'root', 'root'
+
+      it 'uses hostnames' do
+        expect(chef_run).to render_file(sentinel_conf).with_content { |content|
+          expect(content).to match(%r{SENTINEL resolve-hostnames yes})
+          expect(content).to match(%r{SENTINEL announce-hostnames yes})
+        }
+      end
+
+      context 'user overrides sentinel_use_hostnames' do
+        before do
+          stub_gitlab_rb(
+            sentinel: {
+              use_hostnames: false
+            }
+          )
+        end
+
+        it 'does not use hostnames' do
+          expect(chef_run).to render_file(sentinel_conf).with_content { |content|
+            expect(content).to match(%r{SENTINEL resolve-hostnames no})
+            expect(content).to match(%r{SENTINEL announce-hostnames no})
+          }
+        end
+      end
+    end
+
+    context 'with tls settings specified' do
+      before do
+        stub_gitlab_rb(
+          redis: {
+            master_ip: redis_master_ip,
+            announce_ip: redis_announce_ip,
+            master_password: redis_master_password,
+          },
+          redis_sentinel_role: {
+            enable: true
+          },
+          sentinel: {
+            tls_port: 6380,
+            tls_cert_file: '/etc/gitlab/ssl/redis.crt',
+            tls_key_file: '/etc/gitlab/ssl/redis.key',
+            tls_dh_params_file: '/etc/gitlab/ssl/redis-dhparams',
+            tls_ca_cert_file: '/etc/gitlab/ssl/redis-ca.crt',
+            tls_ca_cert_dir: '/opt/gitlab/embedded/ssl/certs',
+            tls_auth_clients: 'no',
+            tls_replication: 'yes',
+            tls_cluster: 'yes',
+            tls_protocols: 'TLSv1.2 TLSv1.3',
+            tls_ciphers: 'DEFAULT:!MEDIUM',
+            tls_ciphersuites: 'TLS_CHACHA20_POLY1305_SHA256',
+            tls_prefer_server_ciphers: 'yes',
+            tls_session_caching: 'no',
+            tls_session_cache_size: 10000,
+            tls_session_cache_timeout: 120
+          }
+        )
+      end
+
+      it 'renders sentinel config file with specified tls values' do
+        expect(chef_run).to render_file('/var/opt/gitlab/sentinel/sentinel.conf')
+          .with_content { |content|
+            expect(content).to match(%r{^tls-port 6380$})
+            expect(content).to match(%r{^tls-cert-file /etc/gitlab/ssl/redis.crt$})
+            expect(content).to match(%r{^tls-key-file /etc/gitlab/ssl/redis.key$})
+            expect(content).to match(%r{^tls-dh-params-file /etc/gitlab/ssl/redis-dhparams$})
+            expect(content).to match(%r{^tls-ca-cert-file /etc/gitlab/ssl/redis-ca.crt$})
+            expect(content).to match(%r{^tls-ca-cert-dir /opt/gitlab/embedded/ssl/certs$})
+            expect(content).to match(%r{^tls-auth-clients no$})
+            expect(content).to match(%r{^tls-replication yes$})
+            expect(content).to match(%r{^tls-cluster yes$})
+            expect(content).to match(%r{^tls-protocols "TLSv1.2 TLSv1.3"$})
+            expect(content).to match(%r{^tls-ciphers DEFAULT:!MEDIUM$})
+            expect(content).to match(%r{^tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256$})
+            expect(content).to match(%r{^tls-prefer-server-ciphers yes$})
+            expect(content).to match(%r{^tls-session-caching no$})
+            expect(content).to match(%r{^tls-session-cache-size 10000$})
+            expect(content).to match(%r{^tls-session-cache-timeout 120$})
+          }
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-ee/resources/pgbouncer_user_spec.rb b/spec/chef/cookbooks/gitlab-ee/resources/pgbouncer_user_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItZWUvcmVzb3VyY2VzL3BnYm91bmNlcl91c2VyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-ee/resources/pgbouncer_user_spec.rb
@@ -0,0 +1,48 @@
+require 'chef_helper'
+RSpec.describe 'pgbouncer_user' do
+  let(:runner) { ChefSpec::SoloRunner.new(step_into: ['pgbouncer_user']) }
+
+  context 'create geo' do
+    let(:chef_run) { runner.converge('test_gitlab_ee::pgbouncer_user_create_geo') }
+    it 'should create the pgbouncer user' do
+      expect(chef_run).to create_postgresql_user('pgbouncer-geo').with(
+        helper: an_instance_of(GeoPgHelper),
+        username: 'pgbouncer-geo',
+        password: 'md5fakepassword-geo'
+      )
+    end
+
+    it 'should create the pg_shadow_lookup function' do
+      postgresql_user = chef_run.postgresql_user('pgbouncer-geo')
+      expect(postgresql_user).to notify('execute[Add pgbouncer auth function]')
+      resource = chef_run.execute('Add pgbouncer auth function')
+      expect(resource.command).to match(%r{^/opt/gitlab/bin/gitlab-geo-psql -d fakedb-geo})
+    end
+
+    it 'should not try and recreate the function' do
+      expect(chef_run).not_to run_execute('Add pgbouncer auth function')
+    end
+  end
+
+  context 'create rails' do
+    let(:chef_run) { runner.converge('test_gitlab_ee::pgbouncer_user_create_rails') }
+    it 'should create the pgbouncer user' do
+      expect(chef_run).to create_postgresql_user('pgbouncer-rails').with(
+        helper: an_instance_of(PgHelper),
+        username: 'pgbouncer-rails',
+        password: 'md5fakepassword-rails'
+      )
+    end
+
+    it 'should create the pg_shadow_lookup function' do
+      postgresql_user = chef_run.postgresql_user('pgbouncer-rails')
+      expect(postgresql_user).to notify('execute[Add pgbouncer auth function]')
+      resource = chef_run.execute('Add pgbouncer auth function')
+      expect(resource.command).to match(%r{^/opt/gitlab/bin/gitlab-psql -d fakedb-rails})
+    end
+
+    it 'should not try and recreate the function' do
+      expect(chef_run).not_to run_execute('Add pgbouncer auth function')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-kas/recipes/gitlab-kas_spec.rb b/spec/chef/cookbooks/gitlab-kas/recipes/gitlab-kas_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIta2FzL3JlY2lwZXMvZ2l0bGFiLWthc19zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-kas/recipes/gitlab-kas_spec.rb
@@ -0,0 +1,508 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab-kas' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service env_dir templatesymlink)).converge('gitlab::default') }
+  let(:gitlab_kas_config_yml) { chef_run_load_yaml_template(chef_run, '/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when application_role is configured' do
+    before do
+      stub_gitlab_rb(roles: %w(application_role))
+    end
+
+    it 'should be enabled' do
+      expect(chef_run).to include_recipe('gitlab-kas::enable')
+    end
+  end
+
+  context 'with defaults' do
+    before do
+      stub_gitlab_rb(external_url: 'https://gitlab.example.com')
+    end
+
+    it 'creates a default VERSION file and restarts service' do
+      expect(chef_run).to create_version_file('Create version file for Gitlab KAS').with(
+        version_file_path: '/var/opt/gitlab/gitlab-kas/VERSION',
+        version_check_cmd: '/opt/gitlab/embedded/bin/gitlab-kas --version'
+      )
+
+      expect(chef_run.version_file('Create version file for Gitlab KAS')).to notify('runit_service[gitlab-kas]').to(:restart)
+    end
+
+    it 'correctly renders the KAS service run file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-kas/run").with_content(%r{--configuration-file /var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml})
+    end
+
+    it 'correctly renders the KAS log run file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-kas/log/run").with_content(%r{exec svlogd -tt /var/log/gitlab/gitlab-kas})
+    end
+
+    it 'correctly renders the KAS config file' do
+      expect(gitlab_kas_config_yml).to(
+        include(
+          agent: hash_including(
+            listen: {
+              network: 'tcp',
+              address: 'localhost:8150',
+              websocket: true,
+            },
+            kubernetes_api: {
+              listen: {
+                address: 'localhost:8154',
+              },
+              url_path_prefix: '/'
+            }
+          ),
+          observability: {
+            logging: {
+              level: 'info',
+            },
+            usage_reporting_period: '60s'
+          },
+          private_api: {
+            listen: {
+              address: "localhost:8155",
+              authentication_secret_file: "/var/opt/gitlab/gitlab-kas/private_api_authentication_secret_file",
+              network: "tcp"
+            },
+          }
+        )
+      )
+    end
+
+    it 'correctly renders the KAS authentication secret files' do
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-kas/authentication_secret_file").with_content { |content| Base64.strict_decode64(content).size == 32 }
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-kas/private_api_authentication_secret_file").with_content { |content| Base64.strict_decode64(content).size == 32 }
+    end
+
+    it 'sets OWN_PRIVATE_API_URL and SSL_CERT_DIR' do
+      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-kas/env/OWN_PRIVATE_API_URL').with_content('grpc://localhost:8155')
+      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-kas/env/SSL_CERT_DIR').with_content('/opt/gitlab/embedded/ssl/certs/')
+    end
+  end
+
+  context 'with user settings' do
+    let(:api_secret_key) { Base64.strict_encode64('1' * 32) }
+    let(:private_api_secret_key) { Base64.strict_encode64('2' * 32) }
+
+    before do
+      stub_gitlab_rb(
+        external_url: 'https://gitlab.example.com',
+        gitlab_kas: {
+          api_secret_key: api_secret_key,
+          private_api_secret_key: private_api_secret_key,
+          listen_address: 'localhost:5006',
+          listen_websocket: false,
+          metrics_usage_reporting_period: '120',
+          sentry_dsn: 'https://my_key:my_secret@sentry.io/test_project',
+          sentry_environment: 'production',
+          log_level: 'debug',
+        }
+      )
+    end
+
+    it 'correctly renders the KAS service run file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-kas/run").with_content(%r{--configuration-file /var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml})
+    end
+
+    it 'correctly renders the KAS log run file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-kas/log/run").with_content(%r{exec svlogd -tt /var/log/gitlab/gitlab-kas})
+    end
+
+    it 'correctly renders the KAS config file' do
+      expect(gitlab_kas_config_yml).to(
+        include(
+          agent: hash_including(
+            listen: {
+              network: 'tcp',
+              address: 'localhost:5006',
+              websocket: false
+            }
+          ),
+          observability: {
+            logging: {
+              level: 'debug'
+            },
+            usage_reporting_period: '120s',
+            sentry: {
+              dsn: 'https://my_key:my_secret@sentry.io/test_project',
+              environment: 'production'
+            }
+          }
+        )
+      )
+    end
+
+    it 'correctly renders the KAS authentication secret files' do
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-kas/authentication_secret_file").with_content(api_secret_key)
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-kas/private_api_authentication_secret_file").with_content(private_api_secret_key)
+    end
+  end
+
+  describe 'gitlab.yml configuration' do
+    let(:gitlab_yml) { chef_run_load_yaml_template(chef_run, '/var/opt/gitlab/gitlab-rails/etc/gitlab.yml') }
+
+    context 'with defaults' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com'
+        )
+      end
+
+      it 'renders gitlab_kas enabled with default URLs in config/gitlab.yml' do
+        expect(gitlab_yml[:production][:gitlab_kas]).to include(
+          enabled: true,
+          external_url: 'wss://gitlab.example.com/-/kubernetes-agent/',
+          internal_url: 'grpc://localhost:8153',
+          external_k8s_proxy_url: 'https://gitlab.example.com/-/kubernetes-agent/k8s-proxy/'
+        )
+      end
+    end
+
+    context 'when not https' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'http://gitlab.example.com'
+        )
+      end
+
+      it 'has exernal URL with scheme `ws` instead of `wss`' do
+        expect(gitlab_yml[:production][:gitlab_kas]).to include(
+          external_url: 'ws://gitlab.example.com/-/kubernetes-agent/'
+        )
+      end
+    end
+
+    context 'with custom listen addresses' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          gitlab_kas: {
+            enable: true,
+            listen_address: 'custom-address:1234',
+            internal_api_listen_address: 'custom-api-address:9999'
+          }
+        )
+      end
+
+      it 'derives the external URLs from the top level external URL, and the internal URL from the listen address' do
+        expect(gitlab_yml[:production][:gitlab_kas]).to include(
+          enabled: true,
+          external_url: 'wss://gitlab.example.com/-/kubernetes-agent/',
+          internal_url: 'grpc://custom-api-address:9999',
+          external_k8s_proxy_url: 'https://gitlab.example.com/-/kubernetes-agent/k8s-proxy/'
+        )
+      end
+    end
+
+    context 'with explicitly configured URLs' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          gitlab_rails: {
+            gitlab_kas_external_url: 'wss://kas.example.com',
+            gitlab_kas_internal_url: 'grpc://kas.internal',
+            gitlab_kas_external_k8s_proxy_url: 'https://kas.example.com/k8s-proxy'
+          }
+        )
+      end
+
+      it 'uses the explicitly configured URL' do
+        expect(gitlab_yml[:production][:gitlab_kas]).to include(
+          external_url: 'wss://kas.example.com',
+          internal_url: 'grpc://kas.internal',
+          external_k8s_proxy_url: 'https://kas.example.com/k8s-proxy'
+        )
+      end
+    end
+  end
+
+  describe 'logrotate settings' do
+    context 'default values' do
+      it_behaves_like 'configured logrotate service', 'gitlab-kas', 'git', 'git'
+    end
+
+    context 'specified username and group' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          user: {
+            username: 'foo',
+            group: 'bar'
+          }
+        )
+      end
+
+      it_behaves_like 'configured logrotate service', 'gitlab-kas', 'foo', 'bar'
+    end
+  end
+
+  describe 'redis config' do
+    context 'when there is a password' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          gitlab_kas: { enable: true },
+          gitlab_rails: { redis_password: 'the-password' }
+        )
+      end
+
+      it 'writes password_file into the kas config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
+          kas_redis_cfg = YAML.safe_load(content)['redis']
+          expect(kas_redis_cfg).to(
+            include(
+              'password_file' => '/var/opt/gitlab/gitlab-kas/redis_password_file'
+            )
+          )
+        }
+      end
+      it 'renders the password file' do
+        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/redis_password_file').with_content('the-password')
+      end
+    end
+
+    context 'when there is no password' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          gitlab_kas: { enable: true }
+        )
+      end
+
+      it 'does not write password_file into the config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
+          kas_cfg = YAML.safe_load(content)
+          expect(kas_cfg['redis']).not_to include('password_file')
+        }
+      end
+
+      it 'renders no password file' do
+        expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-kas/redis_password_file")
+      end
+    end
+
+    context 'without sentinel host only' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          gitlab_kas: {
+            enable: true
+          },
+          gitlab_rails: {
+            redis_host: 'the-host'
+          }
+        )
+      end
+
+      it 'renders a single server configuration in to the kas config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
+          kas_redis_cfg = YAML.safe_load(content)['redis']
+          expect(kas_redis_cfg).to(
+            include(
+              'network' => 'tcp',
+              'server' => {
+                'address' => 'the-host:6379'
+              }
+            )
+          )
+          expect(kas_redis_cfg).not_to(include('sentinel'))
+        }
+      end
+    end
+
+    context 'without sentinel host and port' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          gitlab_rails: {
+            redis_host: 'the-host',
+            redis_port: 12345,
+          }
+        )
+      end
+
+      it 'renders a single server configuration in to the kas config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
+          kas_redis_cfg = YAML.safe_load(content)['redis']
+          expect(kas_redis_cfg).to(
+            include(
+              'network' => 'tcp',
+              'server' => {
+                'address' => 'the-host:12345'
+              }
+            )
+          )
+          expect(kas_redis_cfg).not_to(include('sentinel'))
+        }
+      end
+    end
+
+    context 'without sentinel but with tls enabled' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          gitlab_rails: {
+            redis_host: 'the-host',
+            redis_port: 12345,
+            redis_ssl: true,
+          }
+        )
+      end
+
+      it 'renders a configuration with tls enabled in to the kas config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
+          kas_redis_cfg = YAML.safe_load(content)['redis']
+          expect(kas_redis_cfg).to(
+            include(
+              'network' => 'tcp',
+              'tls' => {
+                'enabled' => true
+              },
+              'server' => {
+                'address' => 'the-host:12345'
+              }
+            )
+          )
+        }
+      end
+    end
+
+    context 'with sentinel' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          gitlab_rails: {
+            redis_sentinels: [
+              { host: 'a', port: 1 },
+              { host: 'b', port: 2 },
+              { host: 'c' }
+            ]
+          },
+          redis: {
+            master_name: 'example-redis'
+          }
+        )
+      end
+
+      it 'renders a single server configuration in to the kas config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
+          kas_redis_cfg = YAML.safe_load(content)['redis']
+          expect(kas_redis_cfg).to(
+            include(
+              'network' => 'tcp',
+              'sentinel' => {
+                'master_name' => 'example-redis',
+                'addresses' => [
+                  'a:1',
+                  'b:2',
+                  'c:6379'
+                ]
+              }
+            )
+          )
+          expect(kas_redis_cfg).not_to(include('server'))
+        }
+      end
+    end
+  end
+
+  describe 'TLS listen config' do
+    context 'when all certificates and keys are defined' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          gitlab_kas: {
+            enable: true,
+            listen_websocket: false,
+            certificate_file: '/path/to/cert.pem',
+            key_file: '/path/to/key.pem',
+            internal_api_certificate_file: '/path/to/internal-api-cert.pem',
+            internal_api_key_file: '/path/to/internal-api-key.pem',
+            kubernetes_api_certificate_file: '/path/to/kubernetes-api-cert.pem',
+            kubernetes_api_key_file: '/path/to/kubernetes-api-key.pem',
+            private_api_certificate_file: '/path/to/private-api-cert.pem',
+            private_api_key_file: '/path/to/private-api-key.pem'
+          }
+        )
+      end
+
+      it 'correctly renders the KAS config file' do
+        expect(gitlab_kas_config_yml).to(
+          include(
+            agent: hash_including(
+              listen: hash_including(
+                certificate_file: '/path/to/cert.pem',
+                key_file: '/path/to/key.pem'
+              ),
+              kubernetes_api: hash_including(
+                listen: hash_including(
+                  certificate_file: '/path/to/kubernetes-api-cert.pem',
+                  key_file: '/path/to/kubernetes-api-key.pem'
+                )
+              )
+            ),
+            api: {
+              listen: hash_including(
+                certificate_file: '/path/to/internal-api-cert.pem',
+                key_file: '/path/to/internal-api-key.pem'
+              ),
+            },
+            private_api: {
+              listen: hash_including(
+                certificate_file: '/path/to/private-api-cert.pem',
+                key_file: '/path/to/private-api-key.pem'
+              )
+            }
+          )
+        )
+      end
+    end
+
+    context 'when certificate/key bundles are not correctly defined' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          gitlab_kas: {
+            enable: true,
+            certificate_file: '/path/to/file.pem',
+            internal_api_certificate_file: '/path/to/file.pem',
+            kubernetes_api_key_file: '/path/to/file.pem',
+            private_api_key_file: '/path/to/file.pem'
+          }
+        )
+      end
+
+      it 'renders no certificate or key configuration' do
+        expect(gitlab_kas_config_yml).not_to(include('/path/to/file.pem'))
+      end
+    end
+
+    context 'when the certificate/key bundle is defined and websocket tunneling is enabled' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          gitlab_kas: {
+            enable: true,
+            listen_websocket: true,
+            certificate_file: '/path/to/cert.pem',
+            key_file: '/path/to/key.pem',
+          }
+        )
+      end
+
+      it 'logs a warning' do
+        expect(chef_run).to run_ruby_block('websocket TLS termination')
+      end
+    end
+  end
+
+  def chef_run_load_yaml_template(chef_run, path)
+    template = chef_run.template(path)
+    file_content = ChefSpec::Renderer.new(chef_run, template).content
+    YAML.safe_load(file_content, [], [], true, symbolize_names: true)
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab-pages/recipes/gitlab-pages_spec.rb b/spec/chef/cookbooks/gitlab-pages/recipes/gitlab-pages_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWItcGFnZXMvcmVjaXBlcy9naXRsYWItcGFnZXNfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab-pages/recipes/gitlab-pages_spec.rb
@@ -0,0 +1,333 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-pages' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service env_dir)).converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow(Etc).to receive(:getpwnam).with('git').and_return(spy('getpwnam spy', uid: 1000, gid: 1000))
+  end
+
+  context 'with default values' do
+    it 'does not include Pages recipe' do
+      expect(chef_run).not_to include_recipe('gitlab-pages::enable')
+      expect(chef_run).to include_recipe('gitlab-pages::disable')
+    end
+  end
+
+  context 'with Pages enabled' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'https://gitlab.example.com',
+        pages_external_url: 'https://pages.example.com'
+      )
+    end
+
+    it 'includes Pages recipe' do
+      expect(chef_run).to include_recipe('gitlab-pages::enable')
+    end
+
+    it 'creates a VERSION file and restarts the service' do
+      expect(chef_run).to create_version_file('Create version file for Gitlab Pages').with(
+        version_file_path: '/var/opt/gitlab/gitlab-pages/VERSION',
+        version_check_cmd: '/opt/gitlab/embedded/bin/gitlab-pages --version'
+      )
+
+      expect(chef_run.version_file('Create version file for Gitlab Pages')).to notify('runit_service[gitlab-pages]').to(:restart)
+    end
+
+    it 'renders the env dir files' do
+      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-pages/env/SSL_CERT_DIR')
+        .with_content('/opt/gitlab/embedded/ssl/certs')
+    end
+
+    it 'renders the pages service run file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-pages/run").with_content(%r{-config="/var/opt/gitlab/gitlab-pages/gitlab-pages-config"})
+    end
+
+    it 'renders the pages log run file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-pages/log/run").with_content(%r{exec svlogd /var/log/gitlab/gitlab-pages})
+    end
+
+    it 'deletes old admin.secret file' do
+      expect(chef_run).to delete_file("/var/opt/gitlab/gitlab-pages/admin.secret")
+    end
+
+    it 'renders pages config file' do
+      default_content = <<~EOS
+       pages-domain=pages.example.com
+       pages-root=/var/opt/gitlab/gitlab-rails/shared/pages
+       api-secret-key=/var/opt/gitlab/gitlab-pages/.gitlab_pages_secret
+       listen-proxy=localhost:8090
+       log-format=json
+       use-http2=true
+       artifacts-server=https://gitlab.example.com/api/v4
+       artifacts-server-timeout=10
+       gitlab-server=https://gitlab.example.com
+      EOS
+
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-pages/gitlab-pages-config").with_content(default_content)
+    end
+
+    it 'skips rendering the auth settings when access control is disabled' do
+      stub_gitlab_rb(
+        external_url: 'https://gitlab.example.com',
+        pages_external_url: 'https://pages.example.com',
+        gitlab_pages: {
+          access_control: false,
+          auth_secret: 'auth_secret'
+        }
+      )
+
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-pages/gitlab-pages-config").with_content { |content|
+        expect(content).not_to match(%r{auth-secret=auth_secret})
+      }
+    end
+
+    context 'when access control is enabled' do
+      context 'when access control secrets are not specified' do
+        before do
+          stub_gitlab_rb(
+            external_url: 'https://gitlab.example.com',
+            pages_external_url: 'https://pages.example.com',
+            gitlab_pages: {
+              access_control: true,
+              auth_secret: 'auth_secret'
+            }
+          )
+
+          allow(GitlabPages).to receive(:authorize_with_gitlab) {
+            Gitlab['gitlab_pages']['gitlab_secret'] = 'app_secret'
+            Gitlab['gitlab_pages']['gitlab_id'] = 'app_id'
+          }
+        end
+
+        it 'authorizes Pages with GitLab' do
+          expect(chef_run).to run_ruby_block('authorize pages with gitlab')
+            .at_converge_time
+          expect(chef_run).to run_ruby_block('re-populate GitLab Pages configuration options')
+            .at_converge_time
+          expect(GitlabPages).to receive(:authorize_with_gitlab)
+
+          chef_run.ruby_block('authorize pages with gitlab').block.call
+          chef_run.ruby_block('re-populate GitLab Pages configuration options').block.call
+
+          expect(chef_run).to render_file("/var/opt/gitlab/gitlab-pages/gitlab-pages-config").with_content { |content|
+            expect(content).to match(%r{auth-client-id=app_id})
+            expect(content).to match(%r{auth-client-secret=app_secret})
+            expect(content).to match(%r{auth-redirect-uri=https://projects.pages.example.com/auth})
+            expect(content).to match(%r{auth-secret=auth_secret})
+          }
+        end
+      end
+
+      context 'when access control secrets are specified' do
+        before do
+          stub_gitlab_rb(
+            external_url: 'https://gitlab.example.com',
+            pages_external_url: 'https://pages.example.com',
+            gitlab_pages: {
+              access_control: true,
+              gitlab_id: 'app_id',
+              gitlab_secret: 'app_secret',
+              auth_secret: 'auth_secret',
+              auth_redirect_uri: 'https://projects.pages.example.com/auth',
+              auth_scope: 'read_api'
+            }
+          )
+        end
+
+        it 'does not attempt to authorize with GitLab' do
+          expect(chef_run).not_to run_ruby_block('authorize pages with gitlab')
+        end
+
+        it 'renders pages config file' do
+          expect(chef_run).to render_file("/var/opt/gitlab/gitlab-pages/gitlab-pages-config").with_content { |content|
+            expect(content).to match(%r{auth-client-id=app_id})
+            expect(content).to match(%r{auth-client-secret=app_secret})
+            expect(content).to match(%r{auth-redirect-uri=https://projects.pages.example.com/auth})
+            expect(content).to match(%r{auth-secret=auth_secret})
+            expect(content).to match(%r{auth-scope=read_api})
+          }
+        end
+      end
+    end
+
+    context 'with custom port' do
+      before do
+        stub_gitlab_rb(
+          pages_external_url: 'https://pages.example.com:8443',
+          gitlab_pages: {
+            access_control: true
+          }
+        )
+      end
+
+      it 'sets the correct port number' do
+        expect(chef_run).to render_file("/var/opt/gitlab/gitlab-pages/gitlab-pages-config").with_content { |content|
+          expect(content).to match(%r{auth-redirect-uri=https://projects.pages.example.com:8443/auth})
+        }
+      end
+    end
+
+    context 'with custom values' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://gitlab.example.com',
+          pages_external_url: 'https://pages.example.com',
+          gitlab_pages: {
+            external_http: ['external_pages.example.com', 'localhost:9000'],
+            external_https: ['external_pages.example.com', 'localhost:9001'],
+            external_https_proxyv2: ['external_pages.example.com', 'localhost:9002'],
+            metrics_address: 'localhost:1234',
+            redirect_http: true,
+            dir: '/var/opt/gitlab/pages',
+            cert: '/etc/gitlab/pages.crt',
+            artifacts_server_url: "https://gitlab.elsewhere.com/api/v5",
+            artifacts_server_timeout: 60,
+            status_uri: '/@status',
+            max_connections: 7500,
+            max_uri_length: 2048,
+            propagate_correlation_id: true,
+            log_format: 'text',
+            log_verbose: true,
+            gitlab_id: 'app_id',
+            gitlab_secret: 'app_secret',
+            auth_secret: 'auth_secret',
+            auth_redirect_uri: 'https://projects.pages.example.com/auth',
+            auth_scope: 'read_api',
+            access_control: true,
+            insecure_ciphers: true,
+            tls_min_version: "tls1.0",
+            tls_max_version: "tls1.2",
+            sentry_enabled: true,
+            sentry_dsn: 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40',
+            sentry_environment: 'production',
+            headers: ['X-XSS-Protection: 1; mode=block', 'X-Content-Type-Options: nosniff', 'Test: Header'],
+            server_shutdown_timeout: "30s",
+            gitlab_client_http_timeout: "10s",
+            gitlab_client_jwt_expiry: "30s",
+            zip_cache_expiration: "120s",
+            zip_cache_cleanup: "1m",
+            zip_cache_refresh: "60s",
+            zip_open_timeout: "45s",
+            zip_http_client_timeout: "30m",
+            internal_gitlab_server: "https://int.gitlab.example.com",
+            gitlab_cache_expiry: "1m",
+            gitlab_cache_refresh: "500ms",
+            gitlab_cache_cleanup: "100ms",
+            gitlab_retrieval_timeout: "3s",
+            gitlab_retrieval_interval: "500ms",
+            gitlab_retrieval_retries: 5,
+            rate_limit_source_ip: 100,
+            rate_limit_source_ip_burst: 50,
+            rate_limit_domain: 1000,
+            rate_limit_domain_burst: 10000,
+            rate_limit_tls_source_ip: 101,
+            rate_limit_tls_source_ip_burst: 51,
+            rate_limit_tls_domain: 1001,
+            rate_limit_tls_domain_burst: 10001,
+            enable_disk: true,
+            env: {
+              GITLAB_CONTINUOUS_PROFILING: "stackdriver?service=gitlab-pages",
+            },
+          }
+        )
+      end
+
+      it 'renders pages config file in the specified directory' do
+        expected_content = <<~EOS
+            pages-domain=pages.example.com
+            pages-root=/var/opt/gitlab/gitlab-rails/shared/pages
+            api-secret-key=/var/opt/gitlab/pages/.gitlab_pages_secret
+            auth-client-id=app_id
+            auth-redirect-uri=https://projects.pages.example.com/auth
+            auth-client-secret=app_secret
+            auth-secret=auth_secret
+            auth-scope=read_api
+            zip-cache-expiration=120s
+            zip-cache-cleanup=1m
+            zip-cache-refresh=60s
+            zip-open-timeout=45s
+            zip-http-client-timeout=30m
+            listen-proxy=localhost:8090
+            metrics-address=localhost:1234
+            pages-status=/@status
+            max-conns=7500
+            max-uri-length=2048
+            propagate-correlation-id=true
+            log-format=text
+            log-verbose
+            sentry-dsn=https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40
+            sentry-environment=production
+            redirect-http=true
+            use-http2=true
+            artifacts-server=https://gitlab.elsewhere.com/api/v5
+            artifacts-server-timeout=60
+            gitlab-server=https://gitlab.example.com
+            internal-gitlab-server=https://int.gitlab.example.com
+            insecure-ciphers
+            tls-min-version=tls1.0
+            tls-max-version=tls1.2
+            server-shutdown-timeout=30s
+            gitlab-client-http-timeout=10s
+            gitlab-client-jwt-expiry=30s
+            listen-http=external_pages.example.com,localhost:9000
+            listen-https=external_pages.example.com,localhost:9001
+            listen-https-proxyv2=external_pages.example.com,localhost:9002
+            root-cert=/etc/gitlab/pages.crt
+            root-key=/etc/gitlab/ssl/pages.example.com.key
+            gitlab-cache-expiry=1m
+            gitlab-cache-refresh=500ms
+            gitlab-cache-cleanup=100ms
+            gitlab-retrieval-timeout=3s
+            gitlab-retrieval-timeout=500ms
+            gitlab-retrieval-retries=5
+            enable-disk=true
+            rate-limit-source-ip=100
+            rate-limit-source-ip-burst=50
+            rate-limit-domain=1000
+            rate-limit-domain-burst=10000
+            rate-limit-tls-source-ip=101
+            rate-limit-tls-source-ip-burst=51
+            rate-limit-tls-domain=1001
+            rate-limit-tls-domain-burst=10001
+        EOS
+
+        expect(chef_run).to render_file("/var/opt/gitlab/pages/gitlab-pages-config").with_content(expected_content)
+      end
+
+      it 'specifies headers as arguments in the run file' do
+        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-pages/run").with_content(%r{-header="X-XSS-Protection: 1; mode=block"})
+        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-pages/run").with_content(%r{-header="X-Content-Type-Options: nosniff"})
+        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-pages/run").with_content(%r{-header="Test: Header"})
+      end
+
+      it 'renders the env dir files' do
+        expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-pages/env/GITLAB_CONTINUOUS_PROFILING")
+          .with_content('stackdriver?service=gitlab-pages')
+        expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-pages/env/SSL_CERT_DIR")
+          .with_content('/opt/gitlab/embedded/ssl/certs')
+      end
+    end
+
+    describe 'logrotate settings' do
+      context 'default values' do
+        it_behaves_like 'configured logrotate service', 'gitlab-pages', 'git', 'git'
+      end
+
+      context 'specified username and group' do
+        before do
+          stub_gitlab_rb(
+            user: {
+              username: 'foo',
+              group: 'bar'
+            }
+          )
+        end
+
+        it_behaves_like 'configured logrotate service', 'gitlab-pages', 'foo', 'bar'
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/account_helper_spec.rb b/spec/chef/cookbooks/gitlab/libraries/account_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL2FjY291bnRfaGVscGVyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/account_helper_spec.rb
@@ -0,0 +1,17 @@
+require 'chef_helper'
+
+RSpec.describe AccountHelper do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+  it 'returns a list of users' do
+    expect(AccountHelper.new(chef_run.node).users).to eq(
+      %w(git gitlab-www gitlab-redis gitlab-psql mattermost registry gitlab-prometheus gitlab-consul)
+    )
+  end
+
+  it 'returns a list of groups' do
+    expect(AccountHelper.new(chef_run.node).groups).to eq(
+      %w(git gitlab-www gitlab-redis gitlab-psql mattermost registry gitlab-consul gitlab-prometheus)
+    )
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/helpers/base_pg_helper_spec.rb b/spec/chef/cookbooks/gitlab/libraries/helpers/base_pg_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL2hlbHBlcnMvYmFzZV9wZ19oZWxwZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/helpers/base_pg_helper_spec.rb
@@ -0,0 +1,336 @@
+require 'chef_helper'
+
+RSpec.describe BasePgHelper do
+  cached(:chef_run) { converge_config }
+  let(:node) { chef_run.node }
+  subject { described_class.new(node) }
+
+  before do
+    allow(subject).to receive(:service_name) { 'postgresql' }
+    allow(subject).to receive(:service_cmd) { 'gitlab-psql' }
+  end
+
+  context 'when handling ssl configuration' do
+    after do
+      chef_run.node.rm_normal('postgresql', 'dir')
+      chef_run.node.rm_normal('postgresql', 'ssl_cert_file')
+    end
+
+    describe 'ssl_cert_file' do
+      it 'is configured with an absolute path' do
+        absolute_path = "/my/absolute/path"
+        config_path = "MrBoots"
+        chef_run.node.normal['postgresql']['ssl_cert_file'] = absolute_path
+        chef_run.node.normal['postgresql']['dir'] = config_path
+        expect(subject.ssl_cert_file).to eq(absolute_path)
+      end
+
+      it 'is configured with a relative path' do
+        relative_path = "my/relative/path"
+        config_path = "/MrBoots"
+        chef_run.node.normal['postgresql']['ssl_cert_file'] = relative_path
+        chef_run.node.normal['postgresql']['dir'] = config_path
+        expect(subject.ssl_cert_file).to eq(File.join(config_path, 'data', relative_path))
+      end
+    end
+
+    describe 'ssl_key_file' do
+      it 'is configured with an absolute path' do
+        absolute_path = "/my/absolute/path"
+        config_path = "MrBoots"
+        chef_run.node.normal['postgresql']['ssl_key_file'] = absolute_path
+        chef_run.node.normal['postgresql']['dir'] = config_path
+        expect(subject.ssl_key_file).to eq(absolute_path)
+      end
+
+      it 'is configured with a relative path' do
+        relative_path = "my/relative/path"
+        config_path = "/MrBoots"
+        chef_run.node.normal['postgresql']['ssl_key_file'] = relative_path
+        chef_run.node.normal['postgresql']['dir'] = config_path
+        expect(subject.ssl_key_file).to eq(File.join(config_path, 'data', relative_path))
+      end
+    end
+  end
+
+  describe '#database_exists?' do
+    it 'calls out to psql_cmd' do
+      expect(subject).to receive(:psql_cmd).with(
+        [
+          "-d 'template1'",
+          "-c 'select datname from pg_database' -A",
+          '| grep -x example'
+        ])
+      subject.database_exists?('example')
+    end
+
+    context 'database present' do
+      it 'truthy' do
+        allow(subject).to receive(:psql_cmd) { true }
+        expect(subject.database_exists?('example')).to be_truthy
+      end
+
+      it 'falsey' do
+        allow(subject).to receive(:psql_cmd) { false }
+        expect(subject.database_exists?('example')).to be_falsey
+      end
+    end
+  end
+
+  describe '#extension_enabled?' do
+    it 'will check with psql_cmd when database present' do
+      expect(subject).to receive(:psql_cmd).with(
+        [
+          "-d 'database'",
+          "-c 'select extname from pg_extension' -A",
+          '| grep -x extension'
+        ])
+      subject.extension_enabled?('extension', 'database')
+    end
+
+    context 'extension present' do
+      it 'truthy' do
+        allow(subject).to receive(:psql_cmd) { true }
+        expect(subject.extension_enabled?('extension', 'database')).to be_truthy
+      end
+
+      it 'falsey' do
+        allow(subject).to receive(:psql_cmd) { false }
+        expect(subject.extension_enabled?('extension', 'database')).to be_falsey
+      end
+    end
+  end
+
+  describe '#extension_can_be_enabled?' do
+    before do
+      allow(subject).to receive(:is_running?).and_return(true)
+      allow(subject).to receive(:is_standby?).and_return(false)
+      allow(subject).to receive(:extension_exists?).and_return(true)
+      allow(subject).to receive(:database_exists?).and_return(true)
+      allow(subject).to receive(:extension_enabled?).and_return(false)
+    end
+
+    it 'can be enabled' do
+      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_truthy
+    end
+
+    it 'needs to be running' do
+      allow(subject).to receive(:is_running?).and_return(false)
+      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_falsey
+    end
+
+    it 'cannot be done on a slave' do
+      allow(subject).to receive(:is_standby?).and_return(true)
+      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_falsey
+    end
+
+    it 'needs to have the extension available' do
+      allow(subject).to receive(:extension_exists?).and_return(false)
+      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_falsey
+    end
+
+    it 'needs the database to load the extension into' do
+      allow(subject).to receive(:database_exists?).and_return(false)
+      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_falsey
+    end
+
+    it 'should not enable twice' do
+      allow(subject).to receive(:extension_enabled?).and_return(true)
+      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_falsey
+    end
+  end
+
+  describe '#user_options' do
+    before do
+      result = spy('shellout')
+      allow(result).to receive(:stdout).and_return("f|f|t|f\n")
+      allow(subject).to receive(:do_shell_out).and_return(result)
+    end
+
+    it 'returns hash from query' do
+      expect(subject.user_options('')).to eq(
+        {
+          'SUPERUSER' => false,
+          'CREATEDB' => false,
+          'REPLICATION' => true,
+          'BYPASSRLS' => false
+        }
+      )
+    end
+  end
+
+  describe '#user_options_set?' do
+    let(:default_options) do
+      {
+        'SUPERUSER' => false,
+        'CREATEDB' => false,
+        'REPLICATION' => true,
+        'BYPASSRLS' => false
+      }
+    end
+
+    context 'default user options' do
+      before do
+        allow(subject).to receive(:user_options).and_return(default_options)
+      end
+
+      it 'returns true when no options are asked about' do
+        expect(subject.user_options_set?('', [])).to be_truthy
+      end
+
+      it 'returns true when options are set to their defaults' do
+        expect(subject.user_options_set?('', ['NOSUPERUSER'])).to be_truthy
+      end
+
+      it 'returns false when options are set away from their defaults' do
+        expect(subject.user_options_set?('', ['SUPERUSER'])).to be_falsey
+      end
+    end
+
+    context 'modified user' do
+      before do
+        allow(subject).to receive(:user_options).and_return(default_options.merge({ 'SUPERUSER' => true }))
+      end
+
+      it 'returns false when options is not what we expect' do
+        expect(subject.user_options_set?('', ['NOSUPERUSER'])).to be_falsey
+      end
+    end
+  end
+
+  describe '#user_password_match?' do
+    before do
+      # user: gitlab pass: test123
+      allow(subject).to receive(:user_hashed_password) { 'md5b56573ef0d94cff111898c63ec259f3f' }
+    end
+
+    it 'returns true when same password is in plain-text' do
+      expect(subject.user_password_match?('gitlab', 'test123')).to be_truthy
+    end
+
+    it 'returns true when same password is in MD5 format' do
+      expect(subject.user_password_match?('gitlab', 'md5b56573ef0d94cff111898c63ec259f3f')).to be_truthy
+    end
+
+    it 'returns false when wrong password is in plain-text' do
+      expect(subject.user_password_match?('gitlab', 'wrong')).to be_falsey
+    end
+
+    it 'returns false when wrong password is in MD5 format' do
+      expect(subject.user_password_match?('gitlab', 'md5b599de4332636c03a60fca13be1edb5f')).to be_falsey
+    end
+
+    it 'returns false when password is not supplied' do
+      expect(subject.user_password_match?('gitlab', nil)).to be_falsey
+    end
+
+    context 'nil password' do
+      before do
+        # user: gitlab pass: unset
+        allow(subject).to receive(:user_hashed_password) { '' }
+      end
+
+      it 'returns true when the password is nil' do
+        expect(subject.user_password_match?('gitlab', nil)).to be_truthy
+      end
+    end
+  end
+
+  describe '#parse_pghash' do
+    let(:payload) { '{host=127.0.0.1,dbname=gitlabhq_production,port=5432,user=gitlab,"password=foo}bar\"zoo\\cat"}' }
+
+    it 'returns a hash' do
+      expect(subject.parse_pghash(payload)).to be_a(Hash)
+    end
+
+    it 'when content is empty still return a hash' do
+      expect(subject.parse_pghash('')).to be_a(Hash)
+      expect(subject.parse_pghash('{}')).to be_a(Hash)
+    end
+
+    it 'returns hash with expected keys' do
+      hash = subject.parse_pghash(payload)
+
+      expect(hash.keys).to contain_exactly(:host, :dbname, :port, :user, :password)
+    end
+
+    it 'returns hash with expected values' do
+      hash = subject.parse_pghash(payload)
+
+      expect(hash.values).to contain_exactly('127.0.0.1', 'gitlabhq_production', '5432', 'gitlab', 'foo}bar"zoo\cat')
+    end
+  end
+
+  describe '#is_running?' do
+    it 'returns true when postgres is running' do
+      stub_service_success_status('postgresql', true)
+
+      expect(subject.is_running?).to be_truthy
+    end
+
+    it 'returns false when postgres is not running' do
+      stub_service_success_status('postgresql', false)
+
+      expect(subject.is_running?).to be_falsey
+    end
+  end
+
+  describe '#is_managed_and_offline?' do
+    it 'returns true when conditions are met' do
+      chef_run.node.normal['postgresql']['enable'] = true
+      stub_service_failure_status('postgresql', true)
+
+      expect(subject.is_managed_and_offline?).to be_truthy
+    end
+
+    it 'returns false when conditions are not met' do
+      chef_run.node.normal['postgresql']['enable'] = true
+      stub_service_failure_status('postgresql', false)
+
+      expect(subject.is_managed_and_offline?).to be_falsey
+
+      chef_run.node.normal['postgresql']['enable'] = false
+      stub_service_failure_status('postgresql', false)
+
+      expect(subject.is_managed_and_offline?).to be_falsey
+
+      stub_service_failure_status('postgresql', true)
+      expect(subject.is_managed_and_offline?).to be_falsey
+    end
+  end
+
+  describe '#is_standby?' do
+    let(:recovery_files) { %w(recovery.conf recovery.signal standby.signal) }
+
+    it 'returns true for a standby instance' do
+      recovery_files.each do |f|
+        allow(File).to receive(:exist?)
+          .with("/var/opt/gitlab/postgresql/data/#{f}").and_return(true)
+      end
+
+      expect(subject.is_standby?).to be true
+    end
+
+    it 'returns false for a primary instance' do
+      recovery_files.each do |f|
+        allow(File).to receive(:exist?)
+          .with("/var/opt/gitlab/postgresql/data/#{f}").and_return(false)
+      end
+      expect(subject.is_standby?).to be false
+    end
+  end
+
+  context 'when handling connection information' do
+    describe '#connection_info' do
+      it 'raises a not implemented warning' do
+        expect { subject.connection_info }.to raise_error(NotImplementedError)
+      end
+    end
+
+    describe '#build_connection_info' do
+      it 'returns a ConnectionInfo object' do
+        expect(subject.build_connection_info('dbname', 'dbhost', 'port', 'pguser')).to be_a(described_class::ConnectionInfo)
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/helpers/gitlab_workhorse_helper_spec.rb b/spec/chef/cookbooks/gitlab/libraries/helpers/gitlab_workhorse_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL2hlbHBlcnMvZ2l0bGFiX3dvcmtob3JzZV9oZWxwZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/helpers/gitlab_workhorse_helper_spec.rb
@@ -0,0 +1,47 @@
+require 'chef_helper'
+
+RSpec.describe GitlabWorkhorseHelper do
+  let(:node) { chef_run.node }
+  subject { described_class.new(node) }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'workhorse is listening on a tcp socket' do
+    cached(:chef_run) { converge_config }
+    let(:tcp_address) { '1.9.8.4' }
+
+    before do
+      stub_gitlab_rb(
+        gitlab_workhorse: {
+          listen_network: 'http',
+          listen_addr: tcp_address
+        }
+      )
+    end
+
+    describe '#unix_socket?' do
+      it 'returns false' do
+        expect(subject.unix_socket?).to be false
+      end
+    end
+  end
+
+  context 'workhorse is listening on a unix socket' do
+    cached(:chef_run) { converge_config }
+    before do
+      stub_gitlab_rb(
+        gitlab_workhorse: {
+          listen_network: 'unix'
+        }
+      )
+    end
+
+    describe '#unix_socket?' do
+      it 'returns true' do
+        expect(subject.unix_socket?).to be true
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/helpers/helper_spec.rb b/spec/chef/cookbooks/gitlab/libraries/helpers/helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL2hlbHBlcnMvaGVscGVyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/helpers/helper_spec.rb
@@ -0,0 +1,254 @@
+require 'chef_helper'
+
+RSpec.shared_examples 'Postgres helpers' do |service_name, service_cmd, edition|
+  let(:chef_run) do
+    ChefSpec::SoloRunner.new do |node|
+      node.normal['gitlab'][service_name]['dir'] = '/fakedir'
+      node.normal['package']['install-dir'] = '/fake/install/dir'
+    end.converge("#{edition}::config")
+  end
+
+  let(:node) { chef_run.node }
+
+  let!(:helper) do
+    described_class.new(node)
+  end
+
+  before do
+    allow(VersionHelper).to receive(:version).with('/opt/gitlab/embedded/bin/psql --version') { 'YYYYYYYY XXXXXXX' }
+  end
+
+  it 'is associated with a valid service' do
+    # this is a validation to make sure we are passing a valid/existing service_name to the shared example
+    expect(node['gitlab'][service_name].to_h).not_to be_empty
+  end
+
+  describe '#version' do
+    it 'returns a valid version' do
+      expect(helper.version).to eq('XXXXXXX')
+    end
+  end
+
+  describe '#database_version' do
+    it 'returns a valid database_version' do
+      allow(File).to receive(:exist?).and_call_original
+      allow(File).to receive(:exist?).with('/fakedir/data/PG_VERSION') { true }
+      allow(File).to receive(:read).and_call_original
+      allow(File).to receive(:read).with('/fakedir/data/PG_VERSION') { '111.222' }
+      allow(Dir).to receive(:glob).with('/fake/install/dir/embedded/postgresql/*') { %w(111.222.18 222.333.11) }
+
+      # We mock this in chef_helper.rb. Override the mock to call the original
+      allow_any_instance_of(described_class).to receive(:database_version).and_call_original
+      expect(helper.database_version).to eq('111.222')
+    end
+  end
+
+  describe '#extension_exists?' do
+    it 'returns whether an extension exists' do
+      expect(helper).to receive(:success?).with("/opt/gitlab/bin/#{service_cmd} -d 'template1' -c 'select name from pg_available_extensions' -A | grep -x myextension")
+      helper.extension_exists?('myextension')
+    end
+  end
+
+  describe '#extension_enabled?' do
+    it 'returns whether an extension exists' do
+      expect(helper).to receive(:success?).with("/opt/gitlab/bin/#{service_cmd} -d 'mydatabase' -c 'select extname from pg_extension' -A | grep -x myextension")
+      helper.extension_enabled?('myextension', 'mydatabase')
+    end
+  end
+
+  describe '#database_exists?' do
+    it 'returns whether a database exists' do
+      expect(helper).to receive(:success?).with("/opt/gitlab/bin/#{service_cmd} -d 'template1' -c 'select datname from pg_database' -A | grep -x mydatabase")
+      helper.database_exists?('mydatabase')
+    end
+  end
+end
+
+RSpec.describe PgHelper do
+  include_examples 'Postgres helpers', 'postgresql', 'gitlab-psql', 'gitlab'
+end
+
+RSpec.describe GeoPgHelper do
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    stub_gitlab_rb(
+      'geo_postgresql' => {
+        'dir' => '/fakedir'
+      }
+    )
+  end
+
+  include_examples 'Postgres helpers', 'geo-postgresql', 'gitlab-geo-psql', 'gitlab-ee'
+end
+
+RSpec.describe OmnibusHelper do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+  let(:node) { chef_run.node }
+  let(:services) do
+    %w(
+      puma
+      sidekiq
+      gitlab-workhorse
+      postgresql
+      redis
+      nginx
+      logrotate
+      prometheus
+      alertmanager
+      grafana
+      node-exporter
+      redis-exporter
+      postgres-exporter
+      gitlab-exporter
+      gitlab-pages
+      gitaly
+    ).freeze
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow(File).to receive(:symlink?).with(any_args).and_call_original
+  end
+
+  context 'service is currently enabled, bootstrapped and is running' do
+    before do
+      services.each do |service|
+        stub_should_notify?(service, true)
+      end
+      stub_gitlab_rb(nginx: { enable: true })
+    end
+
+    it 'notifies the service' do
+      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).to notify('runit_service[nginx]').to(:restart).delayed
+    end
+  end
+
+  context 'disabling a service that was bootstrapped and is currently running' do
+    before do
+      services.each do |service|
+        stub_should_notify?(service, true)
+      end
+      stub_gitlab_rb(nginx: { enable: false })
+    end
+
+    it 'does not notify the service' do
+      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
+    end
+  end
+
+  context 'enabling a service that was bootstrapped but not currently running' do
+    before do
+      services.each do |service|
+        allow(File).to receive(:symlink?).with("/opt/gitlab/service/#{service}").and_return(true)
+        allow_any_instance_of(OmnibusHelper).to receive(:success?).with("/opt/gitlab/init/#{service} status").and_return(false)
+      end
+      stub_gitlab_rb(nginx: { enable: true })
+    end
+
+    it 'does not notify the service' do
+      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
+    end
+  end
+
+  context 'disabling a service that was bootstrapped but not currently running' do
+    before do
+      services.each do |service|
+        allow(File).to receive(:symlink?).with("/opt/gitlab/service/#{service}").and_return(true)
+        allow_any_instance_of(OmnibusHelper).to receive(:success?).with("/opt/gitlab/init/#{service} status").and_return(false)
+      end
+      stub_gitlab_rb(nginx: { enable: false })
+    end
+
+    it 'does not notify the service' do
+      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
+    end
+  end
+
+  context 'enabling a service that was disabled but currently running' do
+    before do
+      services.each do |service|
+        allow(File).to receive(:symlink?).with("/opt/gitlab/service/#{service}").and_return(false)
+        allow_any_instance_of(OmnibusHelper).to receive(:success?).with("/opt/gitlab/init/#{service} status").and_return(true)
+      end
+      stub_gitlab_rb(nginx: { enable: true })
+    end
+
+    it 'does not notify the service' do
+      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
+    end
+  end
+
+  context 'disabling a service that was disabled but currently running' do
+    before do
+      services.each do |service|
+        allow(File).to receive(:symlink?).with("/opt/gitlab/service/#{service}").and_return(false)
+        allow_any_instance_of(OmnibusHelper).to receive(:success?).with("/opt/gitlab/init/#{service} status").and_return(true)
+      end
+      stub_gitlab_rb(nginx: { enable: false })
+    end
+
+    it 'does not notify the service' do
+      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
+    end
+  end
+
+  context 'enabling a service that was disabled and not currently running' do
+    before do
+      services.each do |service|
+        stub_should_notify?(service, false)
+      end
+      stub_gitlab_rb(nginx: { enable: true })
+    end
+
+    it 'does not notify the service' do
+      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
+    end
+  end
+
+  context 'disabling a service that was disabled and not currently running' do
+    before do
+      services.each do |service|
+        stub_should_notify?(service, false)
+      end
+      stub_gitlab_rb(nginx: { enable: false })
+    end
+
+    it 'does not notify the service' do
+      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
+    end
+  end
+
+  context 'expected_owner?' do
+    let(:oh) { OmnibusHelper.new(chef_run.node) }
+    before do
+      allow_any_instance_of(OmnibusHelper).to receive(:expected_owner?)
+        .and_call_original
+      allow_any_instance_of(OmnibusHelper).to receive(
+        :expected_user?).and_return(false)
+      allow_any_instance_of(OmnibusHelper).to receive(
+        :expected_user?
+      ).with('/tmp/fakefile', 'fakeuser').and_return(true)
+      allow_any_instance_of(OmnibusHelper).to receive(
+        :expected_group?).and_return(false)
+      allow_any_instance_of(OmnibusHelper).to receive(
+        :expected_group?
+      ).with('/tmp/fakefile', 'fakegroup').and_return(true)
+    end
+
+    it 'should return false if the group is wrong' do
+      expect(oh.expected_owner?('/tmp/fakefile', 'fakeuser', 'wronggroup'))
+        .to be false
+    end
+
+    it 'should return false if the user is wrong' do
+      expect(oh.expected_owner?('/tmp/fakefile', 'wronguser', 'fakegroup'))
+        .to be false
+    end
+
+    it 'should return true if user and group is correct' do
+      expect(oh.expected_owner?('/tmp/fakefile', 'fakeuser', 'fakegroup'))
+        .to be true
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/helpers/pg_status_helper_spec.rb b/spec/chef/cookbooks/gitlab/libraries/helpers/pg_status_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL2hlbHBlcnMvcGdfc3RhdHVzX2hlbHBlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/helpers/pg_status_helper_spec.rb
@@ -0,0 +1,133 @@
+require 'chef_helper'
+
+RSpec.describe PgStatusHelper do
+  cached(:chef_run) { converge_config }
+  let!(:node) { chef_run.node }
+  let!(:helper) { PgHelper.new(node) }
+  let(:connection_info) { helper.build_connection_info('dbname', 'dbhost', 'port', 'pguser') }
+
+  context 'when checking status' do
+    subject { described_class.new(connection_info, node) }
+
+    describe '#service_checks_exhausted?' do
+      it 'returns true when there are zero remaining service checks' do
+        allow(subject).to receive(:remaining_service_checks).and_return(0)
+        expect(subject.service_checks_exhausted?).to be true
+      end
+
+      it 'returns false when more service checks are allowed' do
+        allow(subject).to receive(:remaining_service_checks).and_return(1)
+        expect(subject.service_checks_exhausted?).to be false
+      end
+    end
+
+    describe '#ready?' do
+      it 'raises no warnings when Postgres is accepting connections' do
+        allow(subject).to receive(:accepting_connections?).and_return(true)
+        expect(subject.ready?).to be true
+      end
+
+      it 'raises a warning when Postgres is not responding' do
+        allow(subject).to receive(:not_responding?).and_return(true)
+        allow(subject).to receive(:remaining_service_checks).and_return(0)
+        expect { subject.ready? }.to raise_error(RuntimeError, 'PostgreSQL did not respond before service checks were exhausted')
+      end
+
+      it 'raises a warning when Postgres gets invalid connection parameters' do
+        allow(subject).to receive(:invalid_connection_parameters?).and_return(true)
+        expect { subject.ready? }.to raise_error(RuntimeError, 'PostgreSQL is not receiving the correct connection parameters')
+      end
+
+      it 'raises a warning when all service checks are exhausted' do
+        allow(subject).to receive(:service_checks_exhausted?).and_return(true)
+        expect { subject.ready? }.to raise_error(RuntimeError, 'Exhausted service checks and database is still not available')
+      end
+    end
+
+    context 'when invoking pg_isready' do
+      using RSpec::Parameterized::TableSyntax
+
+      where(:exit_code, :accepting, :rejecting, :not_responding, :invalid_parameters) do
+        0 | true | false | false | false
+        1 | false | true | false | false
+        2 | false | false | true | false
+        3 | false | false | false | true
+      end
+
+      with_them do
+        before do
+          result = spy('shellout')
+          allow(subject).to receive(:do_shell_out).and_return(result)
+          allow(result).to receive(:exitstatus).and_return(exit_code)
+        end
+
+        describe '#service_state' do
+          it 'returns the expected exit code' do
+            expect(subject.service_state).to eq(exit_code)
+          end
+        end
+
+        describe '#accepting_connections?' do
+          it 'returns the expected boolean' do
+            expect(subject.accepting_connections?).to be accepting
+          end
+        end
+
+        describe '#rejecting_connections?' do
+          it 'returns the expected boolean' do
+            expect(subject.rejecting_connections?).to be rejecting
+          end
+        end
+
+        describe '#not_responding?' do
+          it 'returns the expected boolean' do
+            expect(subject.not_responding?).to be not_responding
+          end
+        end
+
+        describe '#invalid_connection_parameters?' do
+          it 'returns the expected boolean' do
+            expect(subject.invalid_connection_parameters?).to be invalid_parameters
+          end
+        end
+      end
+    end
+  end
+
+  context 'when all checks use configured defaults' do
+    subject { described_class.new(connection_info, node) }
+
+    describe '#maximum_service_checks' do
+      it 'will check 20 times' do
+        expect(subject.maximum_service_checks).to eq(20)
+      end
+    end
+
+    describe '#service_check_interval' do
+      it 'will check every 5 seconds' do
+        expect(subject.service_check_interval).to eq(5)
+      end
+    end
+  end
+
+  context 'when checks are are customized' do
+    let(:the_answer) { 424242 }
+    before do
+      chef_run.node.normal['postgresql']['max_service_checks'] = the_answer
+      chef_run.node.normal['postgresql']['service_check_interval'] = the_answer
+      @subject = described_class.new(connection_info, node)
+    end
+
+    describe '#maximum_service_checks' do
+      it 'will check the configured number of times' do
+        expect(@subject.maximum_service_checks).to eq(the_answer)
+      end
+    end
+
+    describe '#service_check_interval' do
+      it 'will check at a configured interval' do
+        expect(@subject.service_check_interval).to eq(the_answer)
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/pg_version_spec.rb b/spec/chef/cookbooks/gitlab/libraries/pg_version_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL3BnX3ZlcnNpb25fc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/pg_version_spec.rb
@@ -0,0 +1,83 @@
+require 'chef_helper'
+
+RSpec.describe PGVersion do
+  context '.parse' do
+    it 'returns a PGVersion class when provided a value' do
+      expect(PGVersion.parse('string')).to be_a(described_class)
+    end
+
+    it 'returns nil when not passed a value' do
+      expect(PGVersion.parse(nil)).to be_nil
+    end
+  end
+
+  context 'postgres 9.6' do
+    it '.valid? validates version strings' do
+      expect(PGVersion.new('9.6.0').valid?).to be true
+      expect(PGVersion.new('9.6').valid?).to be true
+      expect(PGVersion.new('9').valid?).to be false
+      expect(PGVersion.new('9.6.0.0').valid?).to be false
+      expect(PGVersion.new('i9').valid?).to be false
+      expect(PGVersion.new('i9.6').valid?).to be false
+      expect(PGVersion.new('9.6.0i').valid?).to be false
+      expect(PGVersion.new('i9.6.0').valid?).to be false
+    end
+
+    it 'parses the correct version parts from a MAJOR only version' do
+      version = PGVersion.new('9.6')
+
+      expect(version.major).to eq '9.6'
+      expect(version.minor).to be_nil
+    end
+
+    it 'parses the correct major version from a full version' do
+      version = PGVersion.new('9.6.10')
+
+      expect(version.major).to eq '9.6'
+      expect(version.minor).to eq '10'
+    end
+  end
+
+  context 'postgres 10+' do
+    it '.valid? validates version strings' do
+      expect(PGVersion.new('10.0.0').valid?).to be true
+      expect(PGVersion.new('11.0').valid?).to be true
+      expect(PGVersion.new('10').valid?).to be true
+      expect(PGVersion.new('10.0.0.0').valid?).to be false
+      expect(PGVersion.new('i10').valid?).to be false
+      expect(PGVersion.new('i11.0').valid?).to be false
+      expect(PGVersion.new('10.0.0i').valid?).to be false
+      expect(PGVersion.new('i10.0.0').valid?).to be false
+    end
+
+    it 'parses the correct version parts from a MAJOR only version' do
+      version = PGVersion.new('10')
+      version_new = PGVersion.new('12')
+
+      expect(version.major).to eq '10'
+      expect(version.minor).to be_nil
+      expect(version_new.major).to eq '12'
+      expect(version_new.minor).to be_nil
+    end
+
+    it 'parses the correct major version from a full version' do
+      version = PGVersion.new('10.5')
+      version_new = PGVersion.new('12.122')
+
+      expect(version.major).to eq '10'
+      expect(version.minor).to eq '5'
+      expect(version_new.major).to eq '12'
+      expect(version_new.minor).to eq '122'
+    end
+
+    it 'ignores patch version' do
+      version = PGVersion.new('10.5.3')
+      version_new = PGVersion.new('12.122.3')
+
+      expect(version.major).to eq '10'
+      expect(version.minor).to eq '5'
+      expect(version_new.major).to eq '12'
+      expect(version_new.minor).to eq '122'
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/rails_migration_helper_spec.rb b/spec/chef/cookbooks/gitlab/libraries/rails_migration_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL3JhaWxzX21pZ3JhdGlvbl9oZWxwZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/rails_migration_helper_spec.rb
@@ -0,0 +1,39 @@
+require 'chef_helper'
+
+RSpec.describe RailsMigrationHelper do
+  cached(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+  subject(:helper) { described_class.new(chef_run.node) }
+
+  context '#migrated?' do
+    it 'returns true if status file exists and content is "0"' do
+      allow(helper).to receive(:db_migrate_status_file) { File.join(fixture_path, 'migration/successful-migration-status-file') }
+
+      expect(helper.migrated?).to be_truthy
+    end
+
+    it 'returns false when status file doesnt exist' do
+      allow(helper).to receive(:db_migrate_status_file) { File.join(fixture_path, 'migration/non-existent-migration') }
+
+      expect(helper.migrated?).to be_falsey
+    end
+
+    it 'returns false when content in status file is different from "0"' do
+      allow(helper).to receive(:db_migrate_status_file) { File.join(fixture_path, 'migration/failed-migration-status-file') }
+
+      expect(helper.migrated?).to be_falsey
+    end
+  end
+
+  context '#db_migrate_status_file' do
+    it 'returns a path including status_file_prefix connection_digest and revision' do
+      fake_connection_digest = '63467bb60aa187d2a6830aa8f1b5cbe0'
+      fake_revision = 'f9631484e7f'
+      expected_status_file = "/var/opt/gitlab/gitlab-rails/upgrade-status/db-migrate-#{fake_connection_digest}-#{fake_revision}"
+
+      allow(helper).to receive(:connection_digest) { fake_connection_digest }
+      allow(helper).to receive(:revision) { fake_revision }
+
+      expect(helper.db_migrate_status_file).to eq(expected_status_file)
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/redis_helper_spec.rb b/spec/chef/cookbooks/gitlab/libraries/redis_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL3JlZGlzX2hlbHBlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/redis_helper_spec.rb
@@ -0,0 +1,251 @@
+require 'chef_helper'
+
+RSpec.describe RedisHelper do
+  let(:chef_run) { converge_config }
+  subject { described_class.new(chef_run.node) }
+
+  context '#redis_params' do
+    context 'without sentinels' do
+      before { allow(Gitlab).to receive(:[]).and_call_original }
+
+      it 'returns correct parameters' do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            redis_host: 'redis.example.com',
+            redis_port: 8888,
+            redis_password: 'mypass'
+          }
+        )
+        expect(subject.redis_params).to eq(['redis.example.com', 8888, 'mypass'])
+        expect(subject.redis_params(support_sentinel_groupname: false)).to eq(['redis.example.com', 8888, 'mypass'])
+      end
+    end
+
+    context 'with sentinels' do
+      before { allow(Gitlab).to receive(:[]).and_call_original }
+
+      it 'returns correct parameters' do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            redis_host: 'redis.example.com',
+            redis_port: 8888,
+            redis_password: 'mypass',
+            redis_sentinels: [
+              { 'host' => 'sentinel1.example.com', 'port' => '12345' },
+              { 'host' => 'sentinel2.example.com', 'port' => '12345' }
+            ]
+          },
+          redis: {
+            master_name: 'master_from.redis.master_name',
+            master_password: 'password_from.redis.master_password'
+          }
+        )
+        expect(subject.redis_params).to eq(['master_from.redis.master_name', 6379, 'password_from.redis.master_password'])
+        expect(subject.redis_params(support_sentinel_groupname: false)).to eq(['redis.example.com', 8888, 'mypass'])
+      end
+    end
+  end
+
+  context '#redis_url' do
+    context 'with default configuration' do
+      it 'returns a unix socket' do
+        expect(subject.redis_url.to_s).to eq('unix:/var/opt/gitlab/redis/redis.socket')
+      end
+    end
+
+    context 'with custom configuration' do
+      before { allow(Gitlab).to receive(:[]).and_call_original }
+
+      it 'returns a Redis URL when redis_host is defined' do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            redis_host: 'redis.example.com'
+          }
+        )
+
+        expect(subject.redis_url.to_s).to eq('redis://redis.example.com/')
+      end
+
+      it 'returns a Redis URL with port when a non default port is defined' do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            redis_host: 'redis.example.com',
+            redis_port: 8888
+          }
+        )
+
+        expect(subject.redis_url.to_s).to eq('redis://redis.example.com:8888/')
+      end
+
+      it 'returns a Redis URL with database when specified' do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            redis_host: 'redis.example.com',
+            redis_database: 0
+          }
+        )
+
+        expect(subject.redis_url.to_s).to eq('redis://redis.example.com/0')
+      end
+
+      it 'returns a Redis URL with password when specified' do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            redis_host: 'redis.example.com',
+            redis_password: 'mypass'
+          }
+        )
+
+        expect(subject.redis_url.to_s).to eq('redis://:mypass@redis.example.com/')
+      end
+
+      it 'returns a Redis URL with an encoded password' do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            redis_host: 'redis.example.com',
+            redis_password: '#223'
+          }
+        )
+
+        expect(subject.redis_url.to_s).to eq('redis://:%23223@redis.example.com/')
+      end
+
+      it 'returns a Redis URL with password, port and database when all specified' do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            redis_host: 'redis.example.com',
+            redis_password: 'mypass',
+            redis_database: 0,
+            redis_port: 8888
+          }
+        )
+
+        expect(subject.redis_url.to_s).to eq('redis://:mypass@redis.example.com:8888/0')
+      end
+
+      it 'returns an SSL Redis URL with password, port and database when all specified' do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            redis_host: 'redis.example.com',
+            redis_password: 'mypass',
+            redis_database: 0,
+            redis_port: 8888,
+            redis_ssl: true
+          }
+        )
+
+        expect(subject.redis_url.to_s).to eq('rediss://:mypass@redis.example.com:8888/0')
+      end
+    end
+  end
+
+  describe '#running_version' do
+    let(:redis_cli_output) do
+      <<~MSG
+        # Server
+        redis_version:3.2.12
+        redis_git_sha1:00000000
+        redis_git_dirty:0
+        redis_build_id:e16da30f4a0a7845
+        redis_mode:standalone
+        os:Linux 4.15.0-58-generic x86_64
+      MSG
+    end
+
+    before do
+      # Un-doing the stub added in chef_helper
+      allow_any_instance_of(described_class).to receive(:running_version).and_call_original
+      allow(Gitlab).to receive(:[]).and_call_original
+      allow(VersionHelper).to receive(:version).with(/redis-cli.*INFO/).and_return(redis_cli_output)
+    end
+
+    context 'when redis is not running' do
+      it 'returns nil' do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('redis').and_return(false)
+
+        expect(subject.running_version).to be_nil
+      end
+    end
+
+    context 'when redis is running' do
+      before do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('redis').and_return(true)
+      end
+      context 'over socket' do
+        it 'calls VersionHelper.version with correct arguments' do
+          expect(VersionHelper).to receive(:version).with('/opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket INFO')
+
+          subject.running_version
+        end
+      end
+
+      context 'over TCP' do
+        before do
+          stub_gitlab_rb(
+            redis: {
+              bind: '0.0.0.0',
+              port: 6379
+            }
+          )
+        end
+
+        it 'calls VersionHelper.version with correct arguments' do
+          expect(VersionHelper).to receive(:version).with('/opt/gitlab/embedded/bin/redis-cli -h 0.0.0.0 -p 6379 INFO')
+
+          subject.running_version
+        end
+
+        context 'with a Redis password specified' do
+          before do
+            stub_gitlab_rb(
+              redis: {
+                bind: '0.0.0.0',
+                port: 6379,
+                password: 'toomanysecrets'
+              }
+            )
+          end
+
+          it 'it passes password to the command' do
+            expect(VersionHelper).to receive(:version).with("/opt/gitlab/embedded/bin/redis-cli -h 0.0.0.0 -p 6379 -a 'toomanysecrets' INFO")
+
+            subject.running_version
+          end
+        end
+      end
+
+      it 'parses version from redis-cli output properly' do
+        expect(subject.running_version).to eq('3.2.12')
+      end
+    end
+  end
+
+  describe '#installed_version' do
+    let(:redis_server_output) { 'Redis server v=3.2.12 sha=00000000:0 malloc=jemalloc-4.0.3 bits=64 build=e16da30f4a0a7845' }
+
+    before do
+      # Un-doing the stub added in chef_helper
+      allow_any_instance_of(described_class).to receive(:installed_version).and_call_original
+      allow(Gitlab).to receive(:[]).and_call_original
+      allow(VersionHelper).to receive(:version).with(/redis-server --version/).and_return(redis_server_output)
+    end
+
+    context 'when redis is not running' do
+      it 'returns nil' do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('redis').and_return(false)
+
+        expect(subject.installed_version).to be_nil
+      end
+    end
+
+    context 'when redis is running' do
+      before do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('redis').and_return(true)
+      end
+
+      it 'parses redis-server output properly' do
+        expect(subject.installed_version).to eq('3.2.12')
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/redis_spec.rb b/spec/chef/cookbooks/gitlab/libraries/redis_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL3JlZGlzX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/redis_spec.rb
@@ -0,0 +1,210 @@
+# This spec is to test the Redis helper and whether the values parsed
+# are the ones we expect
+require 'chef_helper'
+
+RSpec.describe 'Redis' do
+  let(:chef_run) { converge_config(is_ee: true) }
+  let(:node) { chef_run.node }
+  subject { ::Redis }
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context '.parse_variables' do
+    it 'delegates to parse_redis_settings' do
+      expect(subject).to receive(:parse_redis_settings)
+
+      subject.parse_variables
+    end
+  end
+
+  context '.parse_redis_settings' do
+    context 'when no customization is made' do
+      it 'keeps unixsocket' do
+        expect(node['gitlab']['gitlab-rails']['unixsocket']).not_to eq false
+
+        subject.parse_redis_settings
+      end
+    end
+
+    context 'within redis host and port synchronization with gitlab_rails' do
+      let(:redis_host) { '1.2.3.4' }
+      let(:redis_port) { 6370 }
+
+      context 'when not using sentinels' do
+        before do
+          stub_gitlab_rb(
+            redis: {
+              bind: redis_host,
+              port: redis_port
+            }
+          )
+        end
+
+        it 'disables unix socket when redis tcp params are defined' do
+          expect(node['redis']['unixsocket']).to eq false
+
+          subject.parse_redis_settings
+        end
+
+        it 'expects redis_host to match bind value from redis' do
+          expect(node['gitlab']['gitlab-rails']['redis_host']).to eq redis_host
+
+          subject.parse_redis_settings
+        end
+
+        it 'expects redis_port to match port value from redis' do
+          expect(node['gitlab']['gitlab-rails']['redis_port']).to eq redis_port
+
+          subject.parse_redis_settings
+        end
+      end
+
+      context 'when using sentinels' do
+        let(:master_name) { 'gitlabredis' }
+        let(:master_pass) { 'PASSWORD' }
+
+        before do
+          stub_gitlab_rb(
+            redis: {
+              bind: redis_host,
+              port: redis_port,
+              master_name: master_name,
+              master_password: master_pass
+            },
+            gitlab_rails: {
+              redis_sentinels: [
+                { host: '1.2.3.4', port: '26379' }
+              ]
+            }
+          )
+        end
+
+        it 'disables unix socket when sentinel params are defined' do
+          expect(node['redis']['unixsocket']).to eq false
+
+          subject.parse_redis_settings
+        end
+      end
+
+      context 'when with redis_replica_role enabled' do
+        before do
+          stub_gitlab_rb(
+            redis_replica_role: {
+              enable: true
+            },
+            redis: {
+              master_ip: '10.0.0.0',
+              master_port: 6379,
+              master_password: 'PASSWORD'
+            }
+          )
+        end
+
+        it 'defined redis master as false' do
+          expect(node['redis']['master']).to eq false
+        end
+      end
+    end
+
+    context 'within redis password and master_password' do
+      let(:redis_password) { 'PASSWORD' }
+
+      context 'when master_role is enabled' do
+        before do
+          stub_gitlab_rb(
+            redis_master_role: {
+              enable: true
+            },
+            redis: {
+              password: redis_password,
+              master_ip: '10.0.0.0'
+            }
+          )
+        end
+
+        it 'master_password is autofilled based on redis current password' do
+          expect(node['redis']['master_password']).to eq redis_password
+        end
+      end
+
+      context 'when redis is a replica' do
+        before do
+          stub_gitlab_rb(
+            redis: {
+              master: false,
+              password: redis_password,
+              master_ip: '10.0.0.0'
+            }
+          )
+        end
+
+        it 'master_password is autofilled based on redis current password' do
+          expect(node['redis']['master_password']).to eq redis_password
+        end
+      end
+
+      context 'when sentinel is enabled' do
+        before do
+          stub_gitlab_rb(
+            sentinel: {
+              enable: true
+            },
+            redis: {
+              password: redis_password,
+              master_ip: '10.0.0.0'
+            }
+          )
+        end
+
+        it 'master_password is autofilled based on redis current password' do
+          expect(node['redis']['master_password']).to eq redis_password
+        end
+
+        context 'announce_ip is defined' do
+          let(:redis_port) { 6379 }
+          let(:redis_announce_ip) { '10.10.10.10' }
+          before do
+            stub_gitlab_rb(
+              redis_sentinel_role: {
+                enable: true,
+              },
+              redis: {
+                master_password: redis_password,
+                master_ip: '10.0.0.0',
+                port: redis_port,
+                announce_ip: redis_announce_ip
+              }
+            )
+          end
+
+          it 'Redis announce_port is autofilled based on redis current port' do
+            expect(node['redis']['announce_port']).to eq redis_port
+
+            subject.parse_redis_settings
+          end
+        end
+      end
+
+      context 'when both password and master_password are present' do
+        let(:master_password) { 'anotherPASSWORD' }
+        before do
+          stub_gitlab_rb(
+            redis_replica_role: {
+              enable: true
+            },
+            redis: {
+              password: redis_password,
+              master_ip: '10.0.0.0',
+              master_password: master_password
+            }
+          )
+        end
+
+        it 'keeps user specified master_password' do
+          expect(node['redis']['master_password']).to eq master_password
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/redis_uri_spec.rb b/spec/chef/cookbooks/gitlab/libraries/redis_uri_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL3JlZGlzX3VyaV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/redis_uri_spec.rb
@@ -0,0 +1,76 @@
+# This spec is to test the Redis helper and whether the values parsed
+# are the ones we expect
+
+require 'spec_helper'
+
+RSpec.describe URI::Redis do
+  subject { URI('redis://localhost') }
+
+  it { is_expected.to be_a(described_class) }
+
+  context '.parse' do
+    it 'delegates to URI.parse' do
+      expect(URI).to receive(:parse).with('redis://localhost')
+
+      described_class.parse('redis://localhost')
+    end
+  end
+
+  context 'port' do
+    it 'has a default port' do
+      expect(subject.default_port).to eq 6379
+    end
+
+    it 'outputs default port when none is defined' do
+      expect(subject.port).to eq subject.default_port
+    end
+
+    it 'does not include port when it is default port' do
+      expect(subject.to_s).to eq 'redis://localhost'
+    end
+
+    it 'includes port when it is different than default' do
+      subject.port = 6378
+      expect(subject.to_s).to eq 'redis://localhost:6378'
+    end
+  end
+
+  context 'with password' do
+    before { subject.password = 'password' }
+
+    it 'allows password to be defined' do
+      expect(subject.password).to eq 'password'
+    end
+
+    it 'renders url with password' do
+      expect(subject.to_s).to eq 'redis://:password@localhost'
+    end
+  end
+
+  context 'with non-alphanumeric password' do
+    let(:password) { "&onBsidv6#XeKFd}=BDDyRrv" }
+    let(:escaped) { CGI.escape(password) }
+
+    it 'rejects unencoded passwords' do
+      expect { subject.password = password }.to raise_error(URI::InvalidComponentError)
+    end
+
+    it 'allows encoded passwords' do
+      subject.password = escaped
+
+      expect(subject.password).to eq(escaped)
+    end
+
+    it 'renders url with escaped password' do
+      subject.password = escaped
+
+      expect(subject.to_s).to eq "redis://:#{escaped}@localhost"
+    end
+  end
+
+  context 'without password' do
+    it 'renders url without authentication division characters' do
+      expect(subject.to_s).to eq 'redis://localhost'
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/registry_spec.rb b/spec/chef/cookbooks/gitlab/libraries/registry_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL3JlZ2lzdHJ5X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/registry_spec.rb
@@ -0,0 +1,59 @@
+require 'chef_helper'
+
+RSpec.describe Registry do
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'registry is disabled' do
+    before do
+      stub_gitlab_rb(
+        registry: {
+          enabled: false
+        }
+      )
+    end
+
+    it 'should return false' do
+      expect(described_class.auto_enable).to be_falsey
+    end
+  end
+
+  context 'registry_external_url is set' do
+    before do
+      stub_gitlab_rb(
+        registry_external_url: 'https://registry.example.com'
+      )
+    end
+
+    it 'should return false' do
+      expect(described_class.auto_enable).to be_falsey
+    end
+  end
+
+  context 'lets encrypt is not enabled' do
+    before do
+      stub_gitlab_rb(
+        letsencrypt: {
+          enable: false
+        }
+      )
+    end
+
+    it 'should return false' do
+      expect(described_class.auto_enable).to be_falsey
+    end
+  end
+
+  context 'external_url is a relative url' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'https://registry.example.com/path'
+      )
+    end
+
+    it 'should return false' do
+      expect(described_class.auto_enable).to be_falsey
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/libraries/workhorse_spec.rb b/spec/chef/cookbooks/gitlab/libraries/workhorse_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL3dvcmtob3JzZV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/libraries/workhorse_spec.rb
@@ -0,0 +1,125 @@
+# This spec is to test the Workhorse library and whether the values parsed
+# are the ones we expect
+require 'chef_helper'
+
+RSpec.describe 'GitlabWorkhorse' do
+  let(:node) { chef_run.node }
+  let(:user_socket) { '/where/is/my/ten/mm/socket_now' }
+  let(:user_sockets_directory) { '/where/is/my/ten/mm/sockets' }
+  let(:default_sockets_directory) { '/var/opt/gitlab/gitlab-workhorse/sockets' }
+  let(:default_socket) { '/var/opt/gitlab/gitlab-workhorse/sockets/socket' }
+  let(:tcp_listen_address) { '1.9.8.4' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context '.parse_variables' do
+    context 'listening on a tcp socket' do
+      let(:chef_run) { converge_config }
+
+      before do
+        stub_gitlab_rb(
+          gitlab_workhorse: {
+            listen_network: 'http',
+            listen_addr: tcp_listen_address
+          }
+        )
+      end
+
+      it 'uses the user configured TCP listen address' do
+        expect(node['gitlab']['gitlab-workhorse']['listen_addr']).to eq(tcp_listen_address)
+      end
+
+      it 'keeps the sockets_directory as nil' do
+        expect(node['gitlab']['gitlab-workhorse']['sockets_directory']).to eq(nil)
+      end
+    end
+
+    context 'listening on a unix socket' do
+      context 'using default configuration' do
+        let(:chef_run) { converge_config }
+
+        before do
+          stub_gitlab_rb(
+            gitlab_workhorse: {
+              listen_network: 'unix'
+            }
+          )
+        end
+
+        it 'uses the default sockets directory' do
+          expect(node['gitlab']['gitlab-workhorse']['sockets_directory']).to eq(default_sockets_directory)
+        end
+
+        it 'uses the default socket file path' do
+          expect(node['gitlab']['gitlab-workhorse']['listen_addr']).to eq(default_socket)
+        end
+      end
+
+      context 'only listen_addr is set' do
+        let(:chef_run) { converge_config }
+
+        before do
+          stub_gitlab_rb(
+            gitlab_workhorse: {
+              listen_network: 'unix',
+              listen_addr: user_socket
+            }
+          )
+        end
+
+        it 'uses the user configured listen address' do
+          expect(node['gitlab']['gitlab-workhorse']['listen_addr']).to eq(user_socket)
+        end
+
+        it 'keeps the sockets_directory as nil' do
+          expect(node['gitlab']['gitlab-workhorse']['sockets_directory']).to eq(nil)
+        end
+      end
+
+      context 'only sockets_directory is set' do
+        let(:chef_run) { converge_config }
+
+        before do
+          stub_gitlab_rb(
+            gitlab_workhorse: {
+              listen_network: 'unix',
+              sockets_directory: user_sockets_directory
+            }
+          )
+        end
+
+        it 'uses the user configured sockets directory' do
+          expect(node['gitlab']['gitlab-workhorse']['sockets_directory']).to eq(user_sockets_directory)
+        end
+
+        it 'creates a socket named socket in the user configured sockets directory' do
+          expect(node['gitlab']['gitlab-workhorse']['listen_addr']).to eq("#{user_sockets_directory}/socket")
+        end
+      end
+
+      context 'listen_addr and sockets_directory are both set' do
+        let(:chef_run) { converge_config }
+
+        before do
+          stub_gitlab_rb(
+            gitlab_workhorse: {
+              listen_network: 'unix',
+              listen_addr: user_socket,
+              sockets_directory: user_sockets_directory
+            }
+          )
+        end
+
+        it 'uses the user configured sockets directory' do
+          expect(node['gitlab']['gitlab-workhorse']['sockets_directory']).to eq(user_sockets_directory)
+        end
+
+        it 'creates a socket matching the configured listen_addr' do
+          expect(node['gitlab']['gitlab-workhorse']['listen_addr']).to eq(user_socket)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/add_trusted_certs_spec.rb b/spec/chef/cookbooks/gitlab/recipes/add_trusted_certs_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9hZGRfdHJ1c3RlZF9jZXJ0c19zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/add_trusted_certs_spec.rb
@@ -0,0 +1,44 @@
+require 'chef_helper'
+
+RSpec.describe 'add_trusted_certs recipe' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+  let(:cert_helper) { CertificateHelper.new('/etc/gitlab/trusted-certs', '/opt/gitlab/embedded/ssl/certs', '/var/opt/gitlab') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  it 'creates the certificate directories' do
+    expect(chef_run).to create_directory('/opt/gitlab/embedded/ssl/certs').with(mode: '0755')
+    expect(chef_run).to create_directory('/etc/gitlab/trusted-certs').with(mode: '0755')
+  end
+
+  it 'creates a readme file for the managed certificate directory' do
+    expect(chef_run).to create_file('/opt/gitlab/embedded/ssl/certs/README').with(
+      mode: '0644',
+      content: /This directory is managed by omnibus\-gitlab/
+    )
+  end
+
+  context 'when new trusted certificates have been added' do
+    before do
+      allow(cert_helper).to receive(:new_certificate_added?).and_return(true)
+      allow(CertificateHelper).to receive(:new).and_return(cert_helper)
+    end
+
+    it 'attempts to link trusted certificates into our certificate chain' do
+      expect(chef_run).to run_ruby_block('Move existing certs and link to /opt/gitlab/embedded/ssl/certs')
+    end
+  end
+
+  context 'when there have been no changes to the trusted certificates directory' do
+    before do
+      allow(cert_helper).to receive(:new_certificate_added?).and_return(false)
+      allow(CertificateHelper).to receive(:new).and_return(cert_helper)
+    end
+
+    it 'does not attempt to link trusted certificates into our certificate chain' do
+      expect(chef_run).not_to run_ruby_block('Move existing certs and link to /opt/gitlab/embedded/ssl/certs')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/config_spec.rb b/spec/chef/cookbooks/gitlab/recipes/config_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9jb25maWdfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/config_spec.rb
@@ -0,0 +1,226 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::config' do
+  cached(:chef_run) { converge_config }
+  let(:node) { chef_run.node }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  shared_examples 'regular services are disabled' do
+    it 'disables regular services' do
+      expect(node['gitlab']['puma']['enable']).to eq false
+      expect(node['gitlab']['sidekiq']['enable']).to eq false
+      expect(node['gitlab']['gitlab-workhorse']['enable']).to eq false
+      expect(node['gitaly']['enable']).to eq false
+      expect(node['gitlab']['nginx']['enable']).to eq false
+      expect(node['postgresql']['enable']).to eq false
+      expect(node['gitlab']['mailroom']['enable']).to eq false
+      expect(node['monitoring']['gitlab-exporter']['enable']).to eq false
+      expect(node['monitoring']['postgres-exporter']['enable']).to eq false
+      expect(node['monitoring']['prometheus']['enable']).to eq false
+    end
+  end
+
+  it 'ignores unsupported top level variables' do
+    allow(IO).to receive(:read).and_call_original
+    allow(IO).to receive(:read).with('config-test.rb').and_return('abc="top-level"')
+    Gitlab.from_file('config-test.rb')
+
+    expect(node['gitlab']['abc']).to be_nil
+  end
+
+  it 'errors on unsupported top level methods' do
+    allow(IO).to receive(:read).and_call_original
+    allow(IO).to receive(:read).with('config-test.rb').and_return('abc "top-level"')
+    expect do
+      Gitlab.from_file('config-test.rb')
+    end.to raise_error(Mixlib::Config::UnknownConfigOptionError)
+  end
+
+  it 'errors on unsupported nested variables' do
+    allow(IO).to receive(:read).and_call_original
+    allow(IO).to receive(:read).with('config-test.rb').and_return('abc["def"]["hij"] = "top-level"')
+    expect do
+      Gitlab.from_file('config-test.rb')
+    end.to raise_error(Mixlib::Config::UnknownConfigOptionError)
+  end
+
+  context 'when gitlab-rails is disabled' do
+    cached(:chef_run) { converge_config }
+    before do
+      stub_gitlab_rb(
+        gitlab_rails: {
+          enable: false
+        }
+      )
+    end
+
+    it 'disables Gitlab components' do
+      expect(node['gitlab']['puma']['enable']).to eq false
+      expect(node['gitlab']['sidekiq']['enable']).to eq false
+      expect(node['gitlab']['gitlab-workhorse']['enable']).to eq false
+      expect(node['monitoring']['gitlab-exporter']['enable']).to eq false
+    end
+
+    it 'still leaves other default service enabled' do
+      expect(node['gitlab']['nginx']['enable']).to eq true
+      expect(node['postgresql']['enable']).to eq true
+      expect(node['redis']['enable']).to eq true
+      expect(node['monitoring']['prometheus']['enable']).to eq true
+      expect(node['monitoring']['alertmanager']['enable']).to eq true
+      expect(node['monitoring']['node-exporter']['enable']).to eq true
+      expect(node['monitoring']['redis-exporter']['enable']).to eq true
+      expect(node['logrotate']['enable']).to eq true
+      expect(node['monitoring']['postgres-exporter']['enable']).to eq true
+    end
+  end
+
+  context 'with roles' do
+    context 'when redis_sentinel_role is enabled' do
+      cached(:chef_run) { converge_config(is_ee: true) }
+      before do
+        stub_gitlab_rb(
+          redis_sentinel_role: {
+            enable: true
+          },
+          redis: {
+            master_password: 'PASSWORD',
+            master_ip: '10.0.0.0'
+          }
+        )
+      end
+
+      it_behaves_like 'regular services are disabled'
+
+      it 'only sentinel is enabled' do
+        expect(node['gitlab']['sentinel']['enable']).to eq true
+        expect(node['redis']['enable']).to eq false
+        expect(node['monitoring']['redis-exporter']['enable']).to eq false
+        expect(node['monitoring']['node-exporter']['enable']).to eq true
+        expect(node['logrotate']['enable']).to eq true
+      end
+
+      context 'when redis_sentinel_role is enabled with redis_master_role' do
+        cached(:chef_run) { converge_config(is_ee: true) }
+        before do
+          stub_gitlab_rb(
+            redis_sentinel_role: {
+              enable: true
+            },
+            redis_master_role: {
+              enable: true
+            }
+          )
+        end
+
+        it_behaves_like 'regular services are disabled'
+
+        it 'redis and sentinel are enabled' do
+          expect(node['gitlab']['sentinel']['enable']).to eq true
+          expect(node['redis']['enable']).to eq true
+          expect(node['monitoring']['redis-exporter']['enable']).to eq true
+          expect(node['monitoring']['node-exporter']['enable']).to eq true
+          expect(node['logrotate']['enable']).to eq true
+        end
+      end
+
+      context 'when redis_sentinel_role is enabled with redis_replica_role' do
+        cached(:chef_run) { converge_config(is_ee: true) }
+        before do
+          stub_gitlab_rb(
+            redis_sentinel_role: {
+              enable: true
+            },
+            redis_replica_role: {
+              enable: true
+            },
+            redis: {
+              master_ip: '10.0.0.0',
+              master_port: 6379,
+              master_password: 'PASSWORD'
+            }
+          )
+        end
+
+        it_behaves_like 'regular services are disabled'
+
+        it 'only redis is enabled' do
+          expect(node['gitlab']['sentinel']['enable']).to eq true
+          expect(node['redis']['enable']).to eq true
+          expect(node['monitoring']['redis-exporter']['enable']).to eq true
+          expect(node['monitoring']['node-exporter']['enable']).to eq true
+          expect(node['logrotate']['enable']).to eq true
+        end
+      end
+    end
+
+    context 'when redis_master_role is enabled' do
+      cached(:chef_run) { converge_config(is_ee: true) }
+      before do
+        stub_gitlab_rb(
+          redis_master_role: {
+            enable: true
+          }
+        )
+      end
+
+      it_behaves_like 'regular services are disabled'
+
+      it 'only redis is enabled' do
+        expect(node['redis']['enable']).to eq true
+        expect(node['gitlab']['sentinel']['enable']).to eq false
+        expect(node['monitoring']['redis-exporter']['enable']).to eq true
+        expect(node['monitoring']['node-exporter']['enable']).to eq true
+        expect(node['logrotate']['enable']).to eq true
+      end
+    end
+
+    context 'when redis_replica_role is enabled' do
+      cached(:chef_run) { converge_config(is_ee: true) }
+      before do
+        stub_gitlab_rb(
+          redis_replica_role: {
+            enable: true
+          },
+          redis: {
+            master_ip: '10.0.0.0',
+            master_port: 6379,
+            master_password: 'PASSWORD'
+          }
+        )
+      end
+
+      it_behaves_like 'regular services are disabled'
+
+      it 'only redis is enabled' do
+        expect(node['redis']['enable']).to eq true
+        expect(node['gitlab']['sentinel']['enable']).to eq false
+      end
+    end
+
+    context 'when redis_master_role and redis_replica_role are enabled' do
+      cached(:chef_run) { converge_config(is_ee: true) }
+      before do
+        stub_gitlab_rb(
+          redis_master_role: {
+            enable: true
+          },
+          redis_replica_role: {
+            enable: true
+          },
+          redis: {
+            master_ip: '10.0.0.0',
+            master_port: 6379,
+            master_password: 'PASSWORD'
+          }
+        )
+      end
+
+      it 'fails with an error' do
+        expect { chef_run }.to raise_error RuntimeError
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/database_migrations_spec.rb b/spec/chef/cookbooks/gitlab/recipes/database_migrations_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9kYXRhYmFzZV9taWdyYXRpb25zX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/database_migrations_spec.rb
@@ -0,0 +1,192 @@
+require 'chef_helper'
+
+# NOTE: These specs do not verify whether the code actually ran
+# Nor whether the resource inside of the recipe was notified correctly.
+# At this moment they only verify whether the expected commands are passed
+# to the resource block.
+
+RSpec.describe 'gitlab::database-migrations' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when migration should run' do
+    before do
+      stub_default_not_listening?(false)
+    end
+
+    let(:bash_block) { chef_run.bash('migrate gitlab-rails database') }
+    let(:migration_block) { chef_run.rails_migration('gitlab-rails') }
+
+    it 'runs the migrations with expected attributes' do
+      expect(chef_run).to run_rails_migration('gitlab-rails') do |resource|
+        expect(resource.rake_task).to eq('gitlab:db:configure')
+        expect(resource.logfile_prefix).to eq('gitlab-rails-db-migrate')
+        expect(resource.helper).to be_a(RailsMigrationHelper)
+      end
+    end
+
+    it 'skips outdated external databases warning by default' do
+      expect(migration_block).to notify('ruby_block[check remote PG version]').to(:run)
+      expect(chef_run.ruby_block('check remote PG version').should_skip?(:run)).to be_truthy
+    end
+
+    context 'using external db' do
+      before { stub_gitlab_rb(postgresql: { enable: false }) }
+
+      it 'warns about outdated databases' do
+        allow(GitlabRailsEnvHelper).to receive(:db_version).and_return(11)
+
+        expect(migration_block).to notify('ruby_block[check remote PG version]').to(:run)
+        expect(chef_run.ruby_block('check remote PG version').should_skip?(:run)).to be_falsey
+
+        chef_run.ruby_block('check remote PG version').block.call
+
+        expect_logged_warning(/Support for PostgreSQL 11 has been removed/)
+      end
+    end
+
+    context 'initial root password' do
+      context 'set via gitlab.rb' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              initial_root_password: 'foobar'
+            }
+          )
+        end
+
+        it 'runs DB migration with GITLAB_ROOT_PASSWORD variable set to provided value' do
+          expect(chef_run).to run_rails_migration('gitlab-rails').with(
+            environment: {
+              'GITLAB_ROOT_PASSWORD' => 'foobar',
+            }
+          )
+        end
+      end
+
+      context 'set via env variable' do
+        before do
+          allow(ENV).to receive(:[]).and_call_original
+          allow(ENV).to receive(:[]).with('GITLAB_ROOT_PASSWORD').and_return('asdf1234@')
+        end
+
+        it 'runs DB migration with GITLAB_ROOT_PASSWORD variable set to provided value' do
+          expect(chef_run).to run_rails_migration('gitlab-rails').with(
+            environment: {
+              'GITLAB_ROOT_PASSWORD' => 'asdf1234@',
+            }
+          )
+        end
+      end
+
+      context 'set via both env variable and gitlab.rb' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              initial_root_password: 'foobar'
+            }
+          )
+
+          allow(ENV).to receive(:[]).and_call_original
+          allow(ENV).to receive(:[]).with('GITLAB_ROOT_PASSWORD').and_return('asdf1234@')
+        end
+
+        it 'runs DB migration with GITLAB_ROOT_PASSWORD variable set to value provided via env variable' do
+          expect(chef_run).to run_rails_migration('gitlab-rails').with(
+            environment: {
+              'GITLAB_ROOT_PASSWORD' => 'asdf1234@',
+            }
+          )
+        end
+      end
+
+      context 'not set' do
+        before do
+          allow(SecretsHelper).to receive(:generate_base64).and_return('LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk=')
+        end
+
+        it 'generates a random root password and runs DB migration with GITLAB_ROOT_PASSWORD set to it' do
+          expect(chef_run).to run_rails_migration('gitlab-rails').with(
+            environment: {
+              'GITLAB_ROOT_PASSWORD' => 'LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk='
+            }
+          )
+        end
+      end
+    end
+
+    it 'runs with the initial_root_password and initial_shared_runners_registration_token in the environment' do
+      stub_gitlab_rb(
+        gitlab_rails: { initial_root_password: '123456789', initial_shared_runners_registration_token: '987654321' }
+      )
+
+      expect(chef_run).to run_rails_migration('gitlab-rails').with(
+        environment: {
+          'GITLAB_ROOT_PASSWORD' => '123456789',
+          'GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN' => '987654321'
+        }
+      )
+    end
+
+    context 'initial license file' do
+      before do
+        allow(SecretsHelper).to receive(:generate_base64).and_return('LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk=')
+      end
+
+      it 'detects license file from /etc/gitlab' do
+        allow(Dir).to receive(:glob).and_call_original
+        allow(Dir).to receive(:glob).with('/etc/gitlab/*.gitlab-license').and_return(%w[/etc/gitlab/company.gitlab-license /etc/gitlab/company2.gitlab-license])
+
+        expect(chef_run).to run_rails_migration('gitlab-rails').with(
+          environment: {
+            'GITLAB_ROOT_PASSWORD' => 'LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk=',
+            'GITLAB_LICENSE_FILE' => '/etc/gitlab/company.gitlab-license'
+          }
+        )
+      end
+
+      it 'license file specified in gitlab.rb gets priority' do
+        allow(Dir).to receive(:glob).and_call_original
+        allow(Dir).to receive(:glob).with('/etc/gitlab/*.gitlab-license').and_return(%w[/etc/gitlab/company.gitlab-license /etc/gitlab/company2.gitlab-license])
+
+        stub_gitlab_rb(
+          gitlab_rails: { initial_license_file: '/mnt/random.gitlab-license' }
+        )
+
+        expect(chef_run).to run_rails_migration('gitlab-rails').with(
+          environment: {
+            'GITLAB_ROOT_PASSWORD' => 'LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk=',
+            'GITLAB_LICENSE_FILE' => '/mnt/random.gitlab-license'
+          }
+        )
+      end
+
+      it 'Does not fail if no license file found in /etc/gitlab' do
+        allow(Dir).to receive(:glob).and_call_original
+        allow(Dir).to receive(:glob).with('/etc/gitlab/*.gitlab-license').and_return([])
+
+        expect(chef_run).to run_rails_migration('gitlab-rails').with(
+          environment: {
+            'GITLAB_ROOT_PASSWORD' => 'LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk=',
+          }
+        )
+      end
+    end
+
+    it 'should notify rails cache clear resource' do
+      expect(migration_block).to notify(
+        'execute[clear the gitlab-rails cache]')
+    end
+  end
+
+  context 'with auto_migrate off' do
+    before { stub_gitlab_rb(gitlab_rails: { auto_migrate: false }) }
+
+    it 'skips running the migrations' do
+      expect(chef_run).not_to run_rails_migration('gitlab-rails')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/database_reindexing_disable_spec.rb b/spec/chef/cookbooks/gitlab/recipes/database_reindexing_disable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9kYXRhYmFzZV9yZWluZGV4aW5nX2Rpc2FibGVfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/database_reindexing_disable_spec.rb
@@ -0,0 +1,13 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::database-reindexing' do
+  let(:chef_run) { converge_config('gitlab::database_reindexing_disable') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  it 'removes the database-reindexing cronjob' do
+    expect(chef_run).to delete_crond_job('database-reindexing')
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/database_reindexing_enable_spec.rb b/spec/chef/cookbooks/gitlab/recipes/database_reindexing_enable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9kYXRhYmFzZV9yZWluZGV4aW5nX2VuYWJsZV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/database_reindexing_enable_spec.rb
@@ -0,0 +1,53 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::database-reindexing' do
+  let(:chef_run) { converge_config('gitlab::database_reindexing_enable') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'with defaults' do
+    before do
+      stub_gitlab_rb(gitlab_rails: { database_reindexing: { enable: true } })
+    end
+
+    it 'adds a crond_job with default schedule' do
+      expect(chef_run).to create_crond_job('database-reindexing').with(
+        user: "root",
+        hour: '*',
+        minute: 0,
+        month: '*',
+        day_of_month: '*',
+        day_of_week: '0,6',
+        command: "/opt/gitlab/bin/gitlab-rake gitlab:db:reindex"
+      )
+    end
+  end
+
+  context 'with specific schedule' do
+    let(:config) do
+      {
+        enable: true,
+        hour: 10,
+        minute: 5,
+        month: 3,
+        day_of_month: 2,
+        day_of_week: 1
+      }
+    end
+
+    it 'adds a crond_job with the configured schedule' do
+      stub_gitlab_rb(gitlab_rails: { database_reindexing: config })
+
+      expect(chef_run).to create_crond_job('database-reindexing').with(
+        user: "root",
+        hour: 10,
+        minute: 5,
+        month: 3,
+        day_of_week: 1,
+        command: "/opt/gitlab/bin/gitlab-rake gitlab:db:reindex"
+      )
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/default_spec.rb b/spec/chef/cookbooks/gitlab/recipes/default_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9kZWZhdWx0X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/default_spec.rb
@@ -0,0 +1,193 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::default' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  it 'creates the user config directory' do
+    expect(chef_run).to create_directory('/etc/gitlab').with(
+      user: 'root',
+      group: 'root',
+      mode: '0775'
+    )
+  end
+
+  it 'creates the var opt data config directory' do
+    expect(chef_run).to create_directory('Create /var/opt/gitlab').with(
+      path: '/var/opt/gitlab',
+      user: 'root',
+      group: 'root',
+      mode: '0755'
+    )
+
+    gitconfig_hash = {
+      "receive" => ["fsckObjects = true", "advertisePushOptions = true"],
+      "pack" => ["threads = 1"],
+      "repack" => ["writeBitmaps = true"],
+      "transfer" => ["hideRefs=^refs/tmp/", "hideRefs=^refs/keep-around/", "hideRefs=^refs/remotes/"],
+      "core" => [
+        'alternateRefsCommand="exit 0 #"',
+        "fsyncObjectFiles = true"
+      ],
+      "fetch" => ["writeCommitGraph = true"]
+    }
+
+    expect(chef_run).to create_template('/opt/gitlab/embedded/etc/gitconfig').with(
+      variables: { gitconfig: gitconfig_hash }
+    )
+  end
+
+  it 'creates the system gitconfig directory and file' do
+    stub_gitlab_rb(omnibus_gitconfig: { system: { receive: ["fsckObjects = true", "advertisePushOptions = true"], pack: ["threads = 2"] } })
+
+    expect(chef_run).to create_directory('/opt/gitlab/embedded/etc').with(
+      user: 'root',
+      group: 'root',
+      mode: '0755'
+    )
+
+    gitconfig_hash = {
+      "receive" => ["fsckObjects = true", "advertisePushOptions = true"],
+      "pack" => ["threads = 2"],
+      "repack" => ["writeBitmaps = true"],
+      "transfer" => ["hideRefs=^refs/tmp/", "hideRefs=^refs/keep-around/", "hideRefs=^refs/remotes/"],
+      "core" => [
+        'alternateRefsCommand="exit 0 #"',
+        "fsyncObjectFiles = true"
+      ],
+      "fetch" => ["writeCommitGraph = true"]
+    }
+
+    expect(chef_run).to create_template('/opt/gitlab/embedded/etc/gitconfig').with(
+      source: 'gitconfig-system.erb',
+      variables: { gitconfig: gitconfig_hash },
+      mode: 0755
+    )
+  end
+
+  context 'with logrotate' do
+    it 'runs logrotate directory and configuration recipe by default' do
+      expect(chef_run).to include_recipe('logrotate::folders_and_configs')
+    end
+
+    it 'runs logrotate directory and configuration recipe when logrotate is disabled' do
+      stub_gitlab_rb(logrotate: { enable: false })
+
+      expect(chef_run).to include_recipe('logrotate::folders_and_configs')
+    end
+  end
+
+  context 'when manage_etc directory management is disabled' do
+    before { stub_gitlab_rb(manage_storage_directories: { enable: true, manage_etc: false }) }
+
+    it 'does not create the user config directory' do
+      expect(chef_run).not_to create_directory('/etc/gitlab')
+    end
+  end
+
+  context 'prometheus is enabled by default' do
+    it 'includes the prometheus recipe' do
+      expect(chef_run).to include_recipe('monitoring::prometheus')
+      expect(chef_run).not_to include_recipe('monitoring::prometheus_disable')
+    end
+  end
+
+  context 'with prometheus disabled' do
+    before { stub_gitlab_rb(prometheus: { enable: false }) }
+
+    it 'includes the prometheus_disable recipe' do
+      expect(chef_run).to include_recipe('monitoring::prometheus_disable')
+      expect(chef_run).not_to include_recipe('monitoring::prometheus')
+    end
+  end
+
+  context 'with database reindexing and LetsEncrypt auto-renew disabled' do
+    it 'disables crond' do
+      expect(chef_run).to include_recipe('crond::disable')
+      expect(chef_run).not_to include_recipe('crond::enable')
+    end
+  end
+
+  context 'with database reindexing enabled' do
+    before do
+      stub_gitlab_rb(gitlab_rails: { database_reindexing: { enable: true } })
+    end
+
+    it 'enables crond' do
+      expect(chef_run).to include_recipe('crond::enable')
+      expect(chef_run).not_to include_recipe('crond::disable')
+    end
+  end
+
+  context 'with LetsEncrypt auto-renew enabled' do
+    before do
+      # Registry will be auto-enabled if LetsEncrypt is enabled
+      stub_gitlab_rb(external_url: 'http://gitlab.example.com',
+                     registry: { enable: false },
+                     letsencrypt: { enable: true, auto_renew: true })
+    end
+
+    it 'enables crond' do
+      expect(chef_run).to include_recipe('crond::enable')
+      expect(chef_run).not_to include_recipe('crond::disable')
+    end
+  end
+
+  context 'with sidekiq exporter settings' do
+    context 'when Sidekiq exporter and Sidekiq health checks port are the same' do
+      before do
+        stub_gitlab_rb(
+          sidekiq:
+            {
+              metrics_enabled: true,
+              listen_address: 'localhost',
+              listen_port: 3807,
+              health_checks_enabled: true,
+              health_checks_listen_address: 'localhost',
+              health_checks_listen_port: 3807
+            }
+        )
+      end
+
+      it 'logs a warning' do
+        allow(LoggingHelper).to receive(:deprecation)
+        expect(LoggingHelper).to receive(:deprecation).with("Sidekiq exporter and health checks are set to the same address and port. This is deprecated and will result in an error in version 15.0. See https://docs.gitlab.com/ee/administration/sidekiq.html")
+
+        chef_run
+      end
+    end
+
+    context 'when Sidekiq exporter and Sidekiq health checks port are different' do
+      before do
+        stub_gitlab_rb(
+          sidekiq:
+            {
+              metrics_enabled: true,
+              listen_address: 'localhost',
+              listen_port: 3807,
+              health_checks_enabled: true,
+              health_checks_listen_address: 'localhost',
+              health_checks_listen_port: 3907
+            }
+        )
+      end
+
+      it 'does not log a warning' do
+        expect(LoggingHelper).not_to receive(:deprecation).with("Sidekiq exporter and health checks are set to the same address and port. This is deprecated and will result in an error in version 15.0. See https://docs.gitlab.com/ee/administration/sidekiq.html")
+
+        chef_run
+      end
+    end
+  end
+
+  context 'with sidekiq exporter settings not set (default settings)' do
+    it 'does not log a warning' do
+      expect(LoggingHelper).not_to receive(:deprecation).with("Sidekiq exporter and health checks are set to the same address and port. This is deprecated and will result in an error in version 15.0. See https://docs.gitlab.com/ee/administration/sidekiq.html")
+
+      chef_run
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-healthcheck_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-healthcheck_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItaGVhbHRoY2hlY2tfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-healthcheck_spec.rb
@@ -0,0 +1,65 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-healthcheck' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'nginx is enabled' do
+    before do
+      stub_gitlab_rb(nginx: { enable: true })
+    end
+
+    it 'correctly renders the healthcheck-rc file' do
+      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
+        .with_content(%r{url='http://localhost:80/help'})
+      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc").with_content(%r{flags='--insecure'})
+    end
+
+    it 'correctly renders out the healthcheck-rc file when using https' do
+      stub_gitlab_rb(external_url: 'https://gitlab.example.com')
+      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
+        .with_content(%r{url='https://localhost:443/help'})
+    end
+
+    it 'correctly renders out the healthcheck-rc file when using custom port' do
+      stub_gitlab_rb(external_url: 'http://gitlab.example.com:8080')
+      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
+        .with_content(%r{url='http://localhost:8080/help'})
+    end
+
+    it 'correctly renders out the healthcheck-rc file when using a relative url' do
+      stub_gitlab_rb(external_url: 'http://gitlab.example.com/custom')
+      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
+        .with_content(%r{url='http://localhost:80/custom/help'})
+    end
+  end
+
+  context 'nginx is disabled' do
+    before do
+      stub_gitlab_rb(nginx: { enable: false })
+    end
+
+    it 'correctly renders the healthcheck-rc file using workhorse' do
+      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
+        .with_content(%r{url='http://localhost/help'})
+      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
+        .with_content(%r{flags='--unix-socket /var/opt/gitlab/gitlab-workhorse/sockets/socket'})
+    end
+
+    it 'correctly renders healthcheck-rc file using workhorse on a port' do
+      stub_gitlab_rb(
+        gitlab_workhorse: { listen_network: 'tcp', listen_addr: 'localhost:9191' }
+      )
+      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
+        .with_content(%r{url='http://localhost:9191/help'})
+    end
+
+    it 'does not render the healthcheck-rc file when workhorse workhorse is disabled' do
+      stub_gitlab_rb(nginx: { enable: false }, gitlab_workhorse: { enable: false })
+      expect(chef_run).not_to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/database_yml_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/database_yml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZGF0YWJhc2VfeW1sX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/database_yml_spec.rb
@@ -0,0 +1,277 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  describe 'Database settings' do
+    let(:chef_run) { ChefSpec::SoloRunner.new(step_into: 'templatesymlink').converge('gitlab::default') }
+    let(:database_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/database.yml') }
+    let(:database_yml_file_content) { ChefSpec::Renderer.new(chef_run, database_yml_template).content }
+    let(:database_yml) { YAML.safe_load(database_yml_file_content, [], [], true, symbolize_names: true) }
+    let(:default_content) do
+      {
+        main: {
+          adapter: 'postgresql',
+          application_name: nil,
+          collation: nil,
+          connect_timeout: nil,
+          database: "gitlabhq_production",
+          database_tasks: true,
+          encoding: "unicode",
+          host: "/var/opt/gitlab/postgresql",
+          keepalives: nil,
+          keepalives_count: nil,
+          keepalives_idle: nil,
+          keepalives_interval: nil,
+          load_balancing: {
+            hosts: []
+          },
+          password: nil,
+          port: 5432,
+          prepared_statements: false,
+          socket: nil,
+          sslca: nil,
+          sslcompression: 0,
+          sslmode: nil,
+          sslrootcert: nil,
+          statement_limit: 1000,
+          tcp_user_timeout: nil,
+          username: "gitlab",
+          variables: {
+            statement_timeout: nil
+          }
+        }
+      }
+    end
+
+    before do
+      allow(Gitlab).to receive(:[]).and_call_original
+      allow(File).to receive(:symlink?).and_call_original
+    end
+
+    context 'with default settings' do
+      it 'renders database.yml with main database and default values' do
+        expect(database_yml[:production]).to eq(default_content)
+      end
+    end
+
+    context 'with user provided settings' do
+      context 'via top level db_* keys' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              db_database: 'foobar'
+            }
+          )
+        end
+
+        it 'renders database.yml with user specified values for main database' do
+          expect(database_yml[:production][:main][:database]).to eq('foobar')
+          expect(database_yml[:production][:main][:database_tasks]).to eq(true)
+        end
+      end
+
+      context 'via top level db_* keys and overwritten main:' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              db_database: 'foobar',
+              db_database_tasks: true,
+              databases: {
+                main: {
+                  enable: true,
+                  db_database_tasks: false
+                }
+              }
+            }
+          )
+        end
+
+        it 'renders database.yml with user specified values for main database' do
+          expect(database_yml[:production][:main][:database]).to eq('foobar')
+          expect(database_yml[:production][:main][:database_tasks]).to eq(false)
+        end
+      end
+
+      context "for main database via gitlab_rails['databases']['main'] setting" do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              databases: {
+                main: {
+                  enable: true,
+                  db_database: 'foobar'
+                }
+              }
+            }
+          )
+        end
+
+        it 'renders database.yml with user specified values for main database' do
+          expect(database_yml[:production][:main][:database]).to eq('foobar')
+        end
+      end
+
+      context 'with additional databases specified' do
+        context 'when using the same database as main:' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                databases: {
+                  ci: {
+                    enable: true
+                  }
+                }
+              }
+            )
+          end
+
+          it 'renders database.yml with main stanza first' do
+            expect(database_yml_file_content).to match("production:\n  main:")
+          end
+
+          it 'renders database.yml with both main and additional databases using default values, but disabled database_tasks' do
+            ci_content = default_content[:main].dup
+            ci_content[:database_tasks] = false
+            expected_output = default_content.merge(ci: ci_content)
+
+            expect(database_yml[:production]).to eq(expected_output)
+          end
+        end
+
+        context 'when using a different database to main:' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                databases: {
+                  ci: {
+                    enable: true,
+                    db_database: 'gitlabhq_production_ci'
+                  }
+                }
+              }
+            )
+          end
+
+          it 'renders database.yml with main stanza first' do
+            expect(database_yml_file_content).to match("production:\n  main:")
+          end
+
+          it 'renders database.yml with both main and additional databases using default values, and enabled database_tasks' do
+            ci_content = default_content[:main].dup
+            ci_content[:database] = 'gitlabhq_production_ci'
+            ci_content[:database_tasks] = true
+            expected_output = default_content.merge(ci: ci_content)
+
+            expect(database_yml[:production]).to eq(expected_output)
+          end
+        end
+
+        context 'when db_database_tasks is explicitly enabled in main, but disabled in CI' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                databases: {
+                  main: {
+                    db_connect_timeout: 30,
+                    db_database_tasks: true
+                  },
+                  ci: {
+                    enable: true,
+                    db_host: 'patroni-ci',
+                    db_connect_timeout: 50,
+                    db_database_tasks: false
+                  }
+                }
+              }
+            )
+          end
+
+          it 'renders database.yml with user specified DB settings' do
+            expect(database_yml[:production][:main][:connect_timeout]).to eq(30)
+            expect(database_yml[:production][:main][:database_tasks]).to eq(true)
+            expect(database_yml[:production][:ci][:connect_timeout]).to eq(50)
+            expect(database_yml[:production][:ci][:database_tasks]).to eq(false)
+          end
+        end
+
+        context 'with different settings for main database and additional databases' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                databases: {
+                  main: {
+                    db_connect_timeout: 30,
+                  },
+                  ci: {
+                    enable: true,
+                    db_connect_timeout: 50,
+                    db_database_tasks: false
+                  }
+                }
+              }
+            )
+          end
+
+          it 'renders database.yml with user specified DB settings' do
+            expect(database_yml[:production][:main][:connect_timeout]).to eq(30)
+            expect(database_yml[:production][:main][:database_tasks]).to eq(true)
+            expect(database_yml[:production][:ci][:connect_timeout]).to eq(50)
+            expect(database_yml[:production][:ci][:database_tasks]).to eq(false)
+          end
+        end
+
+        context 'with additional database specified but not enabled' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                databases: {
+                  main: {
+                    db_connect_timeout: 30,
+                  },
+                  ci: {
+                    db_connect_timeout: 50
+                  }
+                }
+              }
+            )
+          end
+
+          it 'renders database.yml without additional database' do
+            expect(database_yml[:production].keys).not_to include('ci')
+          end
+        end
+
+        context 'with invalid additional database specified' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                databases: {
+                  foobar: {
+                    enable: true,
+                    db_database: 'gitlabhq_foobar'
+                  },
+                  johndoe: {
+                    db_database: 'gitlabhq_johndoe'
+                  },
+                  ci: {
+                    enable: true,
+                    db_database: 'gitlabhq_ci'
+                  }
+                }
+              }
+            )
+          end
+
+          it 'raises warning about invalid database' do
+            chef_run
+            expect_logged_warning("Additional database `foobar` not supported in Rails application. It will be ignored.")
+          end
+
+          it 'does not raise warning about invalid database that is not enabled' do
+            chef_run
+            expect(LoggingHelper.messages).not_to include(kind: :warning, message: /Additional database `johndoe` not supported/)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/backup_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/backup_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9iYWNrdXBfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/backup_spec.rb
@@ -0,0 +1,105 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+  include_context 'object storage config'
+
+  describe 'Backup settings' do
+    let(:backup_settings) { gitlab_yml[:production][:backup] }
+
+    context 'with default values' do
+      it 'renders with default backup settings' do
+        expect(backup_settings).to eq(
+          archive_permissions: nil,
+          gitaly_backup_path: '/opt/gitlab/embedded/bin/gitaly-backup',
+          keep_time: nil,
+          path: '/var/opt/gitlab/backups',
+          pg_schema: nil,
+          upload: {
+            connection: nil,
+            encryption: nil,
+            encryption_key: nil,
+            multipart_chunk_size: nil,
+            remote_directory: nil,
+            storage_options: {},
+            storage_class: nil
+          }
+        )
+      end
+    end
+
+    context 'with user specified gitaly-backup path set' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            backup_gitaly_backup_path: '/some other/bin/gitaly-backup'
+          }
+        )
+      end
+
+      it 'renders with the user specified gitaly-backup path' do
+        expect(backup_settings).to include(
+          gitaly_backup_path: '/some other/bin/gitaly-backup'
+        )
+      end
+    end
+
+    context 'remote upload', :aggregate_failures do
+      let(:upload_settings) { backup_settings[:upload] }
+
+      context 'with Server-Side Encryption' do
+        context 'with Amazon S3-Managed Keys (SSE-S3)' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                'backup_upload_connection' => aws_connection_hash,
+                'backup_encryption' => 'AES256'
+              }
+            )
+          end
+
+          it 'renders encryption option' do
+            expect(upload_settings[:encryption]).to eq('AES256')
+            expect(upload_settings[:encryption_key]).to be_nil
+            expect(upload_settings[:storage_options]).to eq({})
+          end
+        end
+
+        context 'with Customer Master Keys (CMKs) Stored in AWS Key Management Service (SSE-KMS)' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                'backup_upload_connection' => aws_connection_hash,
+                'backup_upload_storage_options' => aws_storage_options_hash
+              }
+            )
+          end
+
+          it 'renders storage options' do
+            expect(upload_settings[:encryption]).to be_nil
+            expect(upload_settings[:encryption_key]).to be_nil
+            expect(upload_settings[:storage_options]).to eq(aws_storage_options)
+          end
+        end
+
+        context 'with Customer-Provided Keys (SSE-C)' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                'backup_upload_connection' => aws_connection_hash,
+                'backup_encryption' => 'AES256',
+                'backup_encryption_key' => 'A12345678'
+              }
+            )
+          end
+
+          it 'renders encryption and encryption key options' do
+            expect(upload_settings[:encryption]).to eq('AES256')
+            expect(upload_settings[:encryption_key]).to eq('A12345678')
+            expect(upload_settings[:storage_options]).to eq({})
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/consul_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/consul_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9jb25zdWxfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/consul_spec.rb
@@ -0,0 +1,123 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'Consul settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml without Consul settings' do
+        expect(gitlab_yml[:production][:consul]).to eq(
+          api_url: ""
+        )
+      end
+    end
+
+    context 'with user specified values' do
+      context 'when bundled consul is enabled' do
+        before do
+          stub_gitlab_rb(
+            consul: {
+              enable: true
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with default Consul settings' do
+          expect(gitlab_yml[:production][:consul]).to eq(
+            api_url: 'http://localhost:8500'
+          )
+        end
+      end
+
+      context 'when Consul is running on non-default location' do
+        context 'when setting client_addr and ports' do
+          before do
+            stub_gitlab_rb(
+              consul: {
+                enable: true,
+                configuration: {
+                  client_addr: '10.0.0.1',
+                  ports: {
+                    http: 1234
+                  }
+                }
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with specified Consul settings' do
+            expect(gitlab_yml[:production][:consul]).to eq(
+              api_url: 'http://10.0.0.1:1234'
+            )
+          end
+        end
+
+        context 'when setting addresses and ports' do
+          before do
+            stub_gitlab_rb(
+              consul: {
+                enable: true,
+                configuration: {
+                  addresses: {
+                    http: '10.0.1.2',
+                  },
+                  ports: {
+                    http: 1234
+                  }
+                }
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with specified Consul settings' do
+            expect(gitlab_yml[:production][:consul]).to eq(
+              api_url: 'http://10.0.1.2:1234'
+            )
+          end
+        end
+
+        context 'when http port is disabled via negative port number' do
+          before do
+            stub_gitlab_rb(
+              consul: {
+                enable: true,
+                configuration: {
+                  addresses: {
+                    https: '10.0.1.2',
+                  },
+                  ports: {
+                    http: -1,
+                    https: 1234
+                  }
+                }
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with specified Consul settings' do
+            expect(gitlab_yml[:production][:consul]).to eq(
+              api_url: 'https://10.0.1.2:1234'
+            )
+          end
+        end
+      end
+
+      context 'when tls is enabled' do
+        before do
+          stub_gitlab_rb(
+            consul: {
+              enable: true,
+              use_tls: true
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with specified Consul settings' do
+          expect(gitlab_yml[:production][:consul]).to eq(
+            api_url: 'https://localhost:8501'
+          )
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/cron_jobs_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/cron_jobs_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9jcm9uX2pvYnNfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/cron_jobs_spec.rb
@@ -0,0 +1,72 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'cron settings' do
+    context 'with default values' do
+      it "renders gitlab.yml without any cron settings" do
+        expect(gitlab_yml[:production][:cron_jobs]).to be nil
+      end
+    end
+
+    context 'with user specified values' do
+      using RSpec::Parameterized::TableSyntax
+
+      where(:gitlab_yml_setting, :gitlab_rb_setting) do
+        'admin_email_worker'                                     | 'admin_email_worker_cron'
+        'analytics_devops_adoption_create_all_snapshots_worker'  | 'analytics_devops_adoption_create_all_snapshots_worker'
+        'analytics_usage_trends_count_job_trigger_worker'        | 'analytics_usage_trends_count_job_trigger_worker_cron'
+        'ci_archive_traces_cron_worker'                          | 'ci_archive_traces_cron_worker_cron'
+        'ci_platform_metrics_update_cron_worker'                 | 'ci_platform_metrics_update_cron_worker'
+        'elastic_index_bulk_cron_worker'                         | 'elastic_index_bulk_cron'
+        'environments_auto_stop_cron_worker'                     | 'environments_auto_stop_cron_worker_cron'
+        'expire_build_artifacts_worker'                          | 'expire_build_artifacts_worker_cron'
+        'geo_file_download_dispatch_worker'                      | 'geo_file_download_dispatch_worker_cron'
+        'geo_migrated_local_files_clean_up_worker'               | 'geo_migrated_local_files_clean_up_worker_cron'
+        'geo_prune_event_log_worker'                             | 'geo_prune_event_log_worker_cron'
+        'geo_repository_sync_worker'                             | 'geo_repository_sync_worker_cron'
+        'geo_repository_verification_primary_batch_worker'       | 'geo_repository_verification_primary_batch_worker_cron'
+        'geo_repository_verification_secondary_scheduler_worker' | 'geo_repository_verification_secondary_scheduler_worker_cron'
+        'geo_secondary_registry_consistency_worker'              | 'geo_secondary_registry_consistency_worker'
+        'geo_secondary_usage_data_cron_worker'                   | 'geo_secondary_usage_data_cron_worker'
+        'historical_data_worker'                                 | 'historical_data_worker_cron'
+        'ldap_group_sync_worker'                                 | 'ldap_group_sync_worker_cron'
+        'ldap_sync_worker'                                       | 'ldap_sync_worker_cron'
+        'member_invitation_reminder_emails_worker'               | 'member_invitation_reminder_emails_worker_cron'
+        'pages_domain_removal_cron_worker'                       | 'pages_domain_removal_cron_worker'
+        'pages_domain_ssl_renewal_cron_worker'                   | 'pages_domain_ssl_renewal_cron_worker'
+        'pages_domain_verification_cron_worker'                  | 'pages_domain_verification_cron_worker'
+        'personal_access_tokens_expired_notification_worker'     | 'personal_access_tokens_expired_notification_worker_cron'
+        'personal_access_tokens_expiring_worker'                 | 'personal_access_tokens_expiring_worker_cron'
+        'pipeline_schedule_worker'                               | 'pipeline_schedule_worker_cron'
+        'pseudonymizer_worker'                                   | 'pseudonymizer_worker_cron'
+        'remove_unaccepted_member_invites_worker'                | 'remove_unaccepted_member_invites_cron_worker'
+        'repository_archive_cache_worker'                        | 'repository_archive_cache_worker_cron'
+        'repository_check_worker'                                | 'repository_check_worker_cron'
+        'schedule_migrate_external_diffs_worker'                 | 'schedule_migrate_external_diffs_worker_cron'
+        'stuck_ci_jobs_worker'                                   | 'stuck_ci_jobs_worker_cron'
+        'user_status_cleanup_batch_worker'                       | 'user_status_cleanup_batch_worker_cron'
+        'namespaces_in_product_marketing_emails_worker'          | 'namespaces_in_product_marketing_emails_worker_cron'
+        'ssh_keys_expired_notification_worker'                   | 'ssh_keys_expired_notification_worker_cron'
+        'ssh_keys_expiring_soon_notification_worker'             | 'ssh_keys_expiring_soon_notification_worker_cron'
+        'loose_foreign_keys_cleanup_worker'                      | 'loose_foreign_keys_cleanup_worker_cron'
+      end
+
+      with_them do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              gitlab_rb_setting => '1 2 3 4 5'
+            }.transform_keys(&:to_sym)
+          )
+        end
+
+        it "renders gitlab.yml with user specified cron settings" do
+          config = gitlab_yml[:production][:cron_jobs][gitlab_yml_setting.to_sym][:cron]
+          expect(config).to eq '1 2 3 4 5'
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/extra_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/extra_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9leHRyYV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/extra_spec.rb
@@ -0,0 +1,89 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'extra settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml without extra settings' do
+        expect(gitlab_yml[:production][:extra]).to be nil
+      end
+    end
+
+    context 'with user specified values' do
+      describe 'matomo settings' do
+        context 'with just matomo_url specified' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                extra_matomo_url: 'http://foobar.com'
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with default values for other matomo settings' do
+            expect(gitlab_yml[:production][:extra][:matomo_url]).to eq('http://foobar.com')
+            expect(gitlab_yml[:production][:extra][:matomo_site_id]).to be nil
+            expect(gitlab_yml[:production][:extra][:matomo_disable_cookies]).to be nil
+          end
+        end
+
+        context 'with all settings specified' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                extra_matomo_url: 'http://foobar.com',
+                extra_matomo_site_id: 'foobar',
+                extra_matomo_disable_cookies: true
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with specified matomo settings' do
+            expect(gitlab_yml[:production][:extra][:matomo_url]).to eq('http://foobar.com')
+            expect(gitlab_yml[:production][:extra][:matomo_site_id]).to eq('foobar')
+            expect(gitlab_yml[:production][:extra][:matomo_disable_cookies]).to be true
+          end
+        end
+      end
+
+      describe 'one_trust_id setting' do
+        before do
+          stub_gitlab_rb(gitlab_rails: { extra_one_trust_id: '0000-0000-test' })
+        end
+
+        it 'renders gitlab.yml with the provided value' do
+          expect(gitlab_yml[:production][:extra][:one_trust_id]).to eq('0000-0000-test')
+        end
+      end
+
+      describe 'google_tag_manager_nonce_id setting' do
+        before do
+          stub_gitlab_rb(gitlab_rails: { extra_google_tag_manager_nonce_id: '0000-0000' })
+        end
+
+        it 'renders gitlab.yml with the provided value' do
+          expect(gitlab_yml[:production][:extra][:google_tag_manager_nonce_id]).to eq('0000-0000')
+        end
+      end
+
+      context 'bizible' do
+        context 'when true' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: { bizible: true }
+            )
+          end
+
+          it 'should set bizible to true' do
+            expect(chef_run).to create_templatesymlink('Create a gitlab.yml and create a symlink to Rails root').with_variables(
+              hash_including(
+                'bizible' => true
+              )
+            )
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/forti_authenticator_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/forti_authenticator_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9mb3J0aV9hdXRoZW50aWNhdG9yX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/forti_authenticator_spec.rb
@@ -0,0 +1,40 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  using RSpec::Parameterized::TableSyntax
+  include_context 'gitlab-rails'
+
+  describe 'FortiAuthenticator settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml with FortiAuthenticator disabled' do
+        expect(gitlab_yml[:production][:forti_authenticator]).to eq(
+          enabled: false
+        )
+      end
+    end
+
+    context 'with user specified values' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            forti_authenticator_enabled: true,
+            forti_authenticator_host: 'forti_authenticator.example.com',
+            forti_authenticator_port: 444,
+            forti_authenticator_username: 'janedoe',
+            forti_authenticator_access_token: '123s3cr3t456'
+          }
+        )
+      end
+
+      it 'renders gitlab.yml with user specified values' do
+        expect(gitlab_yml[:production][:forti_authenticator]).to eq(
+          enabled: true,
+          host: 'forti_authenticator.example.com',
+          port: 444,
+          username: 'janedoe',
+          access_token: '123s3cr3t456'
+        )
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/forti_token_cloud_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/forti_token_cloud_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9mb3J0aV90b2tlbl9jbG91ZF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/forti_token_cloud_spec.rb
@@ -0,0 +1,36 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  using RSpec::Parameterized::TableSyntax
+  include_context 'gitlab-rails'
+
+  describe 'FortiToken Cloud settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml with FortiAuthenticator disabled' do
+        expect(gitlab_yml[:production][:forti_token_cloud]).to eq(
+          enabled: false
+        )
+      end
+    end
+
+    context 'with user specified values' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            forti_token_cloud_enabled: true,
+            forti_token_cloud_client_id: 'forti_token_cloud_client_id',
+            forti_token_cloud_client_secret: '123s3cr3t456'
+          }
+        )
+      end
+
+      it 'renders gitlab.yml with user specified values' do
+        expect(gitlab_yml[:production][:forti_token_cloud]).to eq(
+          enabled: true,
+          client_id: 'forti_token_cloud_client_id',
+          client_secret: '123s3cr3t456'
+        )
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/geo_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/geo_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9nZW9fc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/geo_spec.rb
@@ -0,0 +1,41 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'Geo settings' do
+    context 'with default configuration' do
+      it 'renders gitlab.yml with default values' do
+        expect(gitlab_yml[:production][:geo]).to eq(
+          node_name: nil,
+          registry_replication: {
+            enabled: nil,
+            primary_api_url: nil
+          }
+        )
+      end
+    end
+
+    context 'with user specified configuration' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            geo_node_name: 'foobar',
+            geo_registry_replication_enabled: true,
+            geo_registry_replication_primary_api_url: 'https://example.com'
+          }
+        )
+      end
+
+      it 'renders gitlab.yml with user specified values' do
+        expect(gitlab_yml[:production][:geo]).to eq(
+          node_name: 'foobar',
+          registry_replication: {
+            enabled: true,
+            primary_api_url: 'https://example.com'
+          }
+        )
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/gitaly_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/gitaly_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9naXRhbHlfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/gitaly_spec.rb
@@ -0,0 +1,33 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'Gitaly settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml without Gitaly token set' do
+        expect(gitlab_yml[:production][:gitaly]).to eq(
+          client_path: "/opt/gitlab/embedded/bin",
+          token: ""
+        )
+      end
+    end
+
+    context 'with user specified token' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            gitaly_token: 'token123456'
+          }
+        )
+      end
+
+      it 'renders gitlab.yml with user specified token' do
+        expect(gitlab_yml[:production][:gitaly]).to eq(
+          client_path: "/opt/gitlab/embedded/bin",
+          token: 'token123456'
+        )
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/gitlab_shell_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/gitlab_shell_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9naXRsYWJfc2hlbGxfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/gitlab_shell_spec.rb
@@ -0,0 +1,78 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'GitLab Shell settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml with default values' do
+        expect(gitlab_yml[:production][:gitlab_shell]).to eq(
+          authorized_keys_file: '/var/opt/gitlab/.ssh/authorized_keys',
+          git_timeout: 10800,
+          hooks_path: '/opt/gitlab/embedded/service/gitlab-shell/hooks/',
+          path: '/opt/gitlab/embedded/service/gitlab-shell/',
+          receive_pack: nil,
+          ssh_port: nil,
+          upload_pack: nil
+        )
+      end
+    end
+
+    context 'with user specified values' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            gitlab_shell_git_timeout: 99999,
+            gitlab_shell_hooks_path: '/tmp/hooks',
+            gitlab_shell_path: '/tmp/shell',
+            gitlab_shell_upload_pack: true,
+            gitlab_shell_receive_pack: true,
+            gitlab_shell_ssh_port: 123,
+          }
+        )
+      end
+
+      it 'renders gitlab.yml with user specified values' do
+        expect(gitlab_yml[:production][:gitlab_shell]).to eq(
+          authorized_keys_file: '/var/opt/gitlab/.ssh/authorized_keys',
+          git_timeout: 99999,
+          hooks_path: '/tmp/hooks',
+          path: '/tmp/shell',
+          receive_pack: true,
+          ssh_port: 123,
+          upload_pack: true
+        )
+      end
+
+      describe 'authorized_keys_file' do
+        context 'when explicitly configured' do
+          before do
+            stub_gitlab_rb(
+              gitlab_shell: {
+                auth_file: '/tmp/authorized_keys'
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with user specified path' do
+            expect(gitlab_yml[:production][:gitlab_shell][:authorized_keys_file]).to eq('/tmp/authorized_keys')
+          end
+        end
+
+        context 'when the user home directory is specified' do
+          before do
+            stub_gitlab_rb(
+              user: {
+                home: '/tmp/user'
+              }
+            )
+          end
+
+          it 'defaults to auth file within specified home directory' do
+            expect(gitlab_yml[:production][:gitlab_shell][:authorized_keys_file]).to eq('/tmp/user/.ssh/authorized_keys')
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/gitlab_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/gitlab_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9naXRsYWJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/gitlab_spec.rb
@@ -0,0 +1,207 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'GitLab Application Settings' do
+    describe 'Content Security Policy settings' do
+      context 'with default configuration' do
+        it 'renders gitlab.yml without content security policy settings' do
+          expect(gitlab_yml[:production][:gitlab][:content_security_policy]).to be nil
+        end
+      end
+
+      context 'with user specified configuration' do
+        context 'with content security policy disabled' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                content_security_policy: {
+                  enabled: false
+                }
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with content security policy disabled' do
+            expect(gitlab_yml[:production][:gitlab][:content_security_policy]).to eq(
+              enabled: false
+            )
+          end
+        end
+
+        context 'for various content security policy related settings' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                content_security_policy: {
+                  enabled: true,
+                  report_only: true,
+                  directives: {
+                    default_src: "'self'",
+                    script_src: "'self' http://recaptcha.net",
+                    worker_src: "'self'"
+                  }
+                }
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with user specified values for content security policy' do
+            expect(gitlab_yml[:production][:gitlab][:content_security_policy]).to eq(
+              enabled: true,
+              report_only: true,
+              directives: {
+                default_src: "'self'",
+                script_src: "'self' http://recaptcha.net",
+                worker_src: "'self'"
+              }
+            )
+          end
+        end
+      end
+    end
+
+    describe 'SMIME email settings' do
+      context 'with default configuration' do
+        it 'renders gitlab.yml with SMIME email settings disabled' do
+          expect(gitlab_yml[:production][:gitlab][:email_smime]).to eq(
+            enabled: false,
+            cert_file: '/etc/gitlab/ssl/gitlab_smime.crt',
+            key_file: '/etc/gitlab/ssl/gitlab_smime.key',
+            ca_certs_file: nil
+          )
+        end
+      end
+
+      context 'with user specified configuration' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              gitlab_email_smime_enabled: true,
+              gitlab_email_smime_key_file: '/etc/gitlab/ssl/custom_gitlab_smime.key',
+              gitlab_email_smime_cert_file: '/etc/gitlab/ssl/custom_gitlab_smime.crt',
+              gitlab_email_smime_ca_certs_file: '/etc/gitlab/ssl/custom_gitlab_smime_cas.crt'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with user specified values for SMIME email settings' do
+          expect(gitlab_yml[:production][:gitlab][:email_smime]).to eq(
+            enabled: true,
+            cert_file: '/etc/gitlab/ssl/custom_gitlab_smime.crt',
+            key_file: '/etc/gitlab/ssl/custom_gitlab_smime.key',
+            ca_certs_file: '/etc/gitlab/ssl/custom_gitlab_smime_cas.crt'
+          )
+        end
+      end
+    end
+
+    describe 'Allowed hosts' do
+      context 'with default configuration' do
+        it 'does not render allowed_hosts in gitlab.yml' do
+          expect(gitlab_yml[:production][:gitlab][:allowed_hosts]).to be nil
+        end
+      end
+
+      context 'with user specified configuration' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              allowed_hosts: ['example.com', 'foobar.com']
+            }
+          )
+        end
+
+        it 'renders specified allowed_hosts in gitlab.yml' do
+          expect(gitlab_yml[:production][:gitlab][:allowed_hosts]).to eq(['example.com', 'foobar.com'])
+        end
+      end
+    end
+
+    describe 'Application settings cache expiry' do
+      context 'with default configuration' do
+        it 'renders gitlab.yml without application settings cache expiry' do
+          expect(gitlab_yml[:production][:gitlab][:application_settings_cache_seconds]).to be nil
+        end
+      end
+
+      context 'with user specified configuration' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              application_settings_cache_seconds: 30
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with user specified value for application settings cache expiry' do
+          expect(gitlab_yml[:production][:gitlab][:application_settings_cache_seconds]).to eq(30)
+        end
+      end
+    end
+
+    describe 'Maximum Request Duration' do
+      context 'with default configuration' do
+        it 'renders gitlab.yml with default value for maximum request duration' do
+          expect(gitlab_yml[:production][:gitlab][:max_request_duration_seconds]).to eq(57)
+        end
+
+        context 'with default configuration and Unicorn as webserver' do
+          before do
+            stub_gitlab_rb(
+              puma: { enable: false },
+              unicorn: { enable: true }
+            )
+          end
+
+          it 'renders gitlab.yml with default value for maximum request duration' do
+            expect(gitlab_yml[:production][:gitlab][:max_request_duration_seconds]).to eq(57)
+          end
+        end
+      end
+
+      context 'with user specified configuration' do
+        context 'for worker_timeout' do
+          using RSpec::Parameterized::TableSyntax
+
+          where(:web_worker, :configured_timeout, :expected_duration) do
+            :unicorn | 30   | 29
+            :unicorn | "30" | 29
+            :puma    | 120  | 114
+          end
+
+          with_them do
+            before do
+              stub_gitlab_rb(
+                unicorn: { enable: web_worker == :unicorn, worker_timeout: configured_timeout },
+                puma: { enable: web_worker == :puma, worker_timeout: configured_timeout }
+              )
+            end
+
+            it 'renders gitlab.yml with maximum request duration computed from configured worker timeout' do
+              expect(gitlab_yml[:production][:gitlab][:max_request_duration_seconds]).to eq(expected_duration)
+            end
+          end
+        end
+
+        context 'for max_request_duration_seconds' do
+          before do
+            stub_gitlab_rb(
+              puma: {
+                worker_timeout: 120
+              },
+              gitlab_rails: {
+                max_request_duration_seconds: 100
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with user specified value for maximum request duration' do
+            expect(gitlab_yml[:production][:gitlab][:max_request_duration_seconds]).to eq(100)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/incoming_email_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/incoming_email_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9pbmNvbWluZ19lbWFpbF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/incoming_email_spec.rb
@@ -0,0 +1,60 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'incoming email settings' do
+    describe 'log file location' do
+      context 'with default values' do
+        it 'renders gitlab.yml with default value for mailroom log file path' do
+          expect(gitlab_yml[:production][:incoming_email][:log_path]).to eq '/var/log/gitlab/mailroom/mail_room_json.log'
+        end
+      end
+
+      context 'with custom log file path specified' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              incoming_email_log_file: '/my/custom/log/file'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with specified value for mailroom log file path' do
+          expect(gitlab_yml[:production][:incoming_email][:log_path]).to eq '/my/custom/log/file'
+        end
+      end
+
+      context 'with custom log directory specified' do
+        before do
+          stub_gitlab_rb(
+            mailroom: {
+              log_directory: '/my/custom/directory'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with default log file name inside specified directory' do
+          expect(gitlab_yml[:production][:incoming_email][:log_path]).to eq '/my/custom/directory/mail_room_json.log'
+        end
+      end
+
+      context 'with custom log file and directory specified' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              incoming_email_log_file: '/foobar.log'
+            },
+            mailroom: {
+              log_directory: '/my/custom/directory'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with specified log file path irrespective of specified directory' do
+          expect(gitlab_yml[:production][:incoming_email][:log_path]).to eq '/foobar.log'
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/ldap_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/ldap_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9sZGFwX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/ldap_spec.rb
@@ -0,0 +1,152 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'LDAP server configuration' do
+    context 'with default configuration' do
+      it 'renders gitlab.yml with LDAP disabled' do
+        expect(gitlab_yml[:production][:ldap]).to eq(
+          enabled: false,
+          active_directory: nil,
+          admin_group: nil,
+          allow_username_or_email_login: nil,
+          base: nil,
+          bind_dn: nil,
+          group_base: nil,
+          host: nil,
+          lowercase_usernames: nil,
+          method: nil,
+          password: nil,
+          port: nil,
+          prevent_ldap_sign_in: false,
+          sync_ssh_keys: nil,
+          sync_time: nil,
+          uid: nil,
+          user_filter: nil
+        )
+      end
+    end
+
+    context 'with user specified configuration' do
+      context 'when using new syntax for LDAP servers' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              ldap_enabled: true,
+              prevent_ldap_sign_in: false,
+              ldap_sync_time: 20,
+              ldap_servers: {
+                main: {
+                  label: 'LDAP Primary',
+                  host: 'primary.ldap',
+                  port: 389,
+                  uid: 'uid',
+                  encryption: 'plain',
+                  password: 's3cr3t',
+                  base: 'dc=example,dc=com',
+                  user_filter: ''
+                },
+                secondary: {
+                  label: 'LDAP Secondary',
+                  host: 'secondary.ldap',
+                  port: 389,
+                  uid: 'uid',
+                  encryption: 'plain',
+                  bind_dn: 'dc=example,dc=com',
+                  password: 's3cr3t',
+                  smartcard_auth: 'required',
+                  base: '',
+                  user_filter: '',
+                }
+              }
+            })
+        end
+
+        it 'renders gitlab.yml with user specified values' do
+          expected_output = {
+            enabled: true,
+            prevent_ldap_sign_in: false,
+            sync_time: 20,
+            servers: {
+              main: {
+                label: 'LDAP Primary',
+                host: 'primary.ldap',
+                port: 389,
+                uid: 'uid',
+                encryption: 'plain',
+                password: 's3cr3t',
+                base: 'dc=example,dc=com',
+                user_filter: ''
+              },
+              secondary: {
+                label: 'LDAP Secondary',
+                host: 'secondary.ldap',
+                port: 389,
+                uid: 'uid',
+                encryption: 'plain',
+                bind_dn: 'dc=example,dc=com',
+                password: 's3cr3t',
+                smartcard_auth: 'required',
+                base: '',
+                user_filter: '',
+              }
+            }
+          }
+
+          expect(gitlab_yml[:production][:ldap]).to eq(expected_output)
+        end
+      end
+
+      context 'when using old syntax with single LDAP server' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              ldap_enabled: true,
+              prevent_ldap_sign_in: false,
+              ldap_host: 'primary.ldap',
+              ldap_port: 389,
+              ldap_uid: 'uid',
+              ldap_password: 's3cr3t',
+              ldap_base: 'dc=example,dc=com',
+              ldap_user_filter: '',
+              ldap_method: 'plain',
+              ldap_bind_dn: 'foobar',
+              ldap_active_directory: 'asdf',
+              ldap_allow_username_or_email_login: false,
+              ldap_lowercase_usernames: true,
+              ldap_group_base: 'dc-example.com',
+              ldap_admin_group: 'foo',
+              ldap_sync_ssh_keys: true,
+              ldap_sync_time: 10
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with user specified values' do
+          expected_output = {
+            enabled: true,
+            prevent_ldap_sign_in: false,
+            sync_time: 10,
+            host: 'primary.ldap',
+            port: 389,
+            uid: 'uid',
+            password: 's3cr3t',
+            base: 'dc=example,dc=com',
+            user_filter: '',
+            method: 'plain',
+            bind_dn: 'foobar',
+            active_directory: 'asdf',
+            allow_username_or_email_login: false,
+            lowercase_usernames: true,
+            group_base: 'dc-example.com',
+            admin_group: 'foo',
+            sync_ssh_keys: 'true',
+          }
+
+          expect(gitlab_yml[:production][:ldap]).to eq(expected_output)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/mattermost_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/mattermost_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9tYXR0ZXJtb3N0X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/mattermost_spec.rb
@@ -0,0 +1,66 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  using RSpec::Parameterized::TableSyntax
+  include_context 'gitlab-rails'
+
+  describe 'Mattermost settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml with Mattermost disabled' do
+        expect(gitlab_yml[:production][:mattermost]).to eq(
+          enabled: false,
+          host: nil
+        )
+      end
+    end
+
+    context 'with user specified values' do
+      context 'when mattermost_external_url is specified' do
+        before do
+          stub_gitlab_rb(
+            mattermost_external_url: 'http://mattermost.example.com'
+          )
+        end
+
+        it 'renders gitlab.yml with specified Mattermost settings' do
+          expect(gitlab_yml[:production][:mattermost]).to eq(
+            enabled: true,
+            host: 'http://mattermost.example.com'
+          )
+        end
+      end
+
+      context 'when mattermost is running on a different machine' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              mattermost_host: 'http://mattermost.example.com'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with specified Mattermost settings' do
+          expect(gitlab_yml[:production][:mattermost]).to eq(
+            enabled: true,
+            host: 'http://mattermost.example.com'
+          )
+        end
+      end
+
+      context 'when both gitlab-rails.mattermost_host and mattermost_external_url is set' do
+        before do
+          stub_gitlab_rb(
+            mattermost_external_url: 'http://foobar.com',
+            gitlab_rails: {
+              mattermost_host: 'http://mattermost.example.com'
+            }
+          )
+        end
+
+        it 'mattermost_external_url is used' do
+          expect(gitlab_yml[:production][:mattermost][:host]).to eq('http://foobar.com')
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/monitoring_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/monitoring_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9tb25pdG9yaW5nX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/monitoring_spec.rb
@@ -0,0 +1,143 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'Monitoring settings' do
+    context 'with default configuration' do
+      it 'renders gitlab.yml with default values' do
+        expect(gitlab_yml[:production][:monitoring]).to eq(
+          ip_whitelist: %w[127.0.0.0/8 ::1/128],
+          sidekiq_exporter: {
+            enabled: true,
+            log_enabled: false,
+            port: 8082,
+            address: '127.0.0.1'
+          },
+          sidekiq_health_checks: {
+            enabled: true,
+            log_enabled: false,
+            port: 8082,
+            address: '127.0.0.1'
+          },
+          web_exporter: {
+            enabled: false,
+            port: 8083,
+            address: '127.0.0.1'
+          }
+        )
+      end
+    end
+
+    context 'with user specified configuration' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            monitoring_whitelist: %w[1.0.0.0 2.0.0.0],
+          }
+        )
+      end
+
+      it 'renders gitlab.rb with user specified values' do
+        expect(gitlab_yml[:production][:monitoring][:ip_whitelist]).to eq(%w[1.0.0.0 2.0.0.0])
+      end
+
+      context 'when sidekiq exports logs' do
+        before do
+          stub_gitlab_rb(
+            sidekiq: {
+              metrics_enabled: false,
+              exporter_log_enabled: true,
+              listen_address: '1.2.3.4',
+              listen_port: 1234
+            }
+          )
+        end
+
+        it 'renders gitlab.rb with user specified values' do
+          expect(gitlab_yml[:production][:monitoring][:sidekiq_exporter]).to eq(
+            enabled: false,
+            log_enabled: true,
+            port: 1234,
+            address: '1.2.3.4'
+          )
+        end
+      end
+
+      context 'when sidekiq exporter uses custom settings' do
+        let(:exporter_settings) do
+          {
+            sidekiq: {
+              metrics_enabled: false,
+              exporter_log_enabled: true,
+              listen_address: '1.2.2.2',
+              listen_port: 2222
+            }
+          }
+        end
+
+        before do
+          stub_gitlab_rb(exporter_settings.merge(health_checks_settings))
+        end
+
+        context 'when health-checks are not configured manually' do
+          let(:health_checks_settings) { {} }
+
+          it 'defaults health-checks to exporter settings' do
+            expect(gitlab_yml[:production][:monitoring][:sidekiq_health_checks]).to eq(
+              enabled: false,
+              log_enabled: true,
+              port: 2222,
+              address: '1.2.2.2'
+            )
+          end
+        end
+
+        context 'when health-checks are configured manually' do
+          let(:health_checks_settings) do
+            {
+              sidekiq: {
+                health_checks_enabled: true,
+                health_checks_log_enabled: true,
+                health_checks_listen_address: '1.2.3.5',
+                health_checks_listen_port: 1235
+              }
+            }
+          end
+
+          it 'uses custom health-checks settings' do
+            expect(gitlab_yml[:production][:monitoring][:sidekiq_health_checks]).to eq(
+              enabled: true,
+              log_enabled: true,
+              port: 1235,
+              address: '1.2.3.5'
+            )
+          end
+        end
+      end
+
+      context 'when webserver exports are enabled' do
+        context 'with Puma' do
+          before do
+            stub_gitlab_rb(
+              puma: {
+                enable: true,
+                exporter_enabled: false,
+                exporter_address: '1.2.3.4',
+                exporter_port: 1234
+              }
+            )
+          end
+
+          it 'renders gitlab.rb with user specified values' do
+            expect(gitlab_yml[:production][:monitoring][:web_exporter]).to eq(
+              enabled: false,
+              port: 1234,
+              address: '1.2.3.4'
+            )
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/object_storage_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/object_storage_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9vYmplY3Rfc3RvcmFnZV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/object_storage_spec.rb
@@ -0,0 +1,60 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'object storage settings' do
+    include_context 'object storage config'
+
+    describe 'consolidated object storage settings' do
+      context 'with default values' do
+        it 'renders gitlab.yml without consolidated object storage settings' do
+          expect(gitlab_yml[:production][:object_store]).to be_nil
+        end
+      end
+
+      context 'with user specified values' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              object_store: {
+                enabled: true,
+                connection: aws_connection_hash,
+                storage_options: aws_storage_options_hash,
+                objects: object_config,
+                proxy_download: true,
+              }
+            }
+          )
+        end
+
+        it 'generates gitlab.yml properly with specified values' do
+          expect(gitlab_yml[:production][:object_store]).to eq(
+            enabled: true,
+            connection: aws_connection_data,
+            storage_options: aws_storage_options,
+            objects: object_config,
+            proxy_download: true
+          )
+        end
+      end
+    end
+
+    describe 'individual object storage settings' do
+      # Parameters are:
+      # 1. Component name
+      # 2. Default settings deviating from general pattern
+      # 3. Whether Workhorse acceleration is in place - decides whether to
+      #    include background_upload, direct_upload, proxy_download etc.
+      include_examples 'renders object storage settings in gitlab.yml', 'artifacts'
+      include_examples 'renders object storage settings in gitlab.yml', 'uploads'
+      include_examples 'renders object storage settings in gitlab.yml', 'external_diffs', { remote_directory: 'external-diffs' }
+      include_examples 'renders object storage settings in gitlab.yml', 'lfs', { remote_directory: 'lfs-objects' }
+      include_examples 'renders object storage settings in gitlab.yml', 'packages'
+      include_examples 'renders object storage settings in gitlab.yml', 'dependency_proxy'
+      include_examples 'renders object storage settings in gitlab.yml', 'terraform_state', { remote_directory: 'terraform' }, false
+      include_examples 'renders object storage settings in gitlab.yml', 'ci_secure_files', { remote_directory: 'ci-secure-files' }, false
+      include_examples 'renders object storage settings in gitlab.yml', 'pages', {}, false
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/omniauth_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/omniauth_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9vbW5pYXV0aF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/omniauth_spec.rb
@@ -0,0 +1,101 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  using RSpec::Parameterized::TableSyntax
+  include_context 'gitlab-rails'
+
+  describe 'omniauth settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml with omniauth allow_single_sign_on set to saml' do
+        expect(gitlab_yml[:production][:omniauth][:allow_single_sign_on]).to eq(['saml'])
+      end
+
+      it 'renders gitlab.yml with other omniauth settings set to nil' do
+        expect(gitlab_yml[:production][:omniauth]).to eq(
+          allow_bypass_two_factor: nil,
+          auto_link_ldap_user: nil,
+          auto_link_saml_user: nil,
+          auto_link_user: nil,
+          allow_single_sign_on: ['saml'],
+          auto_sign_in_with_provider: nil,
+          block_auto_created_users: nil,
+          enabled: nil,
+          external_providers: nil,
+          providers: nil
+        )
+      end
+    end
+
+    context 'with user specified values' do
+      describe 'saml_message_max_byte_size' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              omniauth_saml_message_max_byte_size: 100000
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with custom value' do
+          expect(gitlab_yml[:production][:omniauth][:saml_message_max_byte_size]).to eq(100000)
+        end
+      end
+
+      describe 'settings that take in boolean values' do
+        where(:gitlab_yml_setting, :gitlab_rb_setting) do
+          'enabled'                  | 'omniauth_enabled'
+          'block_auto_created_users' | 'omniauth_block_auto_created_users'
+          'auto_link_ldap_user'      | 'omniauth_auto_link_ldap_user'
+          'auto_link_saml_user'      | 'omniauth_auto_link_saml_user'
+        end
+
+        with_them do
+          context "with #{params[:gitlab_rb_setting]} set to true" do
+            before do
+              stub_gitlab_rb(
+                gitlab_rails: {
+                  gitlab_rb_setting => true
+                }.transform_keys(&:to_sym)
+              )
+            end
+
+            it "renders gitlab.yml with #{params[:gitlab_yml_setting]} set to true" do
+              expect(gitlab_yml[:production][:omniauth][gitlab_yml_setting.to_sym]).to be true
+            end
+          end
+        end
+      end
+
+      describe 'settings that take in multiple types of values' do
+        where(:gitlab_yml_setting, :gitlab_rb_setting) do
+          'allow_single_sign_on'       | 'omniauth_allow_single_sign_on'
+          'sync_email_from_provider'   | 'omniauth_sync_email_from_provider'
+          'sync_profile_from_provider' | 'omniauth_sync_profile_from_provider'
+          'allow_bypass_two_factor'    | 'omniauth_allow_bypass_two_factor'
+          'sync_profile_attributes'    | 'omniauth_sync_profile_attributes'
+          'auto_link_user'             | 'omniauth_auto_link_user'
+        end
+
+        with_them do
+          # Testing scenarios where settings are set to boolean true, string,
+          # and array of strings.
+          [true, 'foo', ['bar', 'tar']].each do |value|
+            context "with #{params[:gitlab_rb_setting]} set to #{value}" do
+              before(:each) do
+                stub_gitlab_rb(
+                  gitlab_rails: {
+                    gitlab_rb_setting => value
+                  }.transform_keys(&:to_sym)
+                )
+              end
+
+              it "renders gitlab.yml with #{params[:gitlab_yml_setting]} set to #{value}" do
+                expect(gitlab_yml[:production][:omniauth][gitlab_yml_setting.to_sym]).to eq value
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/pages_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/pages_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9wYWdlc19zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/pages_spec.rb
@@ -0,0 +1,208 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  using RSpec::Parameterized::TableSyntax
+  include_context 'gitlab-rails'
+  include_context 'object storage config'
+
+  let(:aws_connection_data) { JSON.parse(aws_connection_hash.to_json, symbolize_names: true) }
+
+  describe 'Pages settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml with Pages disabled' do
+        expect(gitlab_yml[:production][:pages]).to eq(
+          enabled: false,
+          access_control: false,
+          artifacts_server: true,
+          external_http: false,
+          external_https: false,
+          host: nil,
+          https: false,
+          object_store: {
+            connection: {},
+            enabled: false,
+            remote_directory: "pages"
+          },
+          local_store: {
+            enabled: true,
+            path: "/var/opt/gitlab/gitlab-rails/shared/pages",
+          },
+          path: "/var/opt/gitlab/gitlab-rails/shared/pages",
+          port: nil
+        )
+      end
+    end
+
+    context 'with user specified values' do
+      context 'when Pages deployed along with GitLab' do
+        before do
+          stub_gitlab_rb(
+            external_url: 'https://gitlab.example.com',
+            pages_external_url: 'https://pages.example.com',
+            gitlab_pages: {
+              access_control: true,
+              artifacts_server: false,
+              external_http: ['1.2.3.4']
+            },
+            gitlab_rails: {
+              pages_path: '/random/path',
+              pages_object_store_enabled: true,
+              pages_object_store_remote_directory: 'foobar',
+              pages_object_store_connection: aws_connection_hash
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with user specified values' do
+          expect(gitlab_yml[:production][:pages]).to eq(
+            {
+              enabled: true,
+              access_control: true,
+              path: '/random/path',
+              host: 'pages.example.com',
+              port: 443,
+              https: true,
+              external_http: true,
+              external_https: false,
+              artifacts_server: false,
+              object_store: {
+                enabled: true,
+                remote_directory: 'foobar',
+                connection: aws_connection_data
+              },
+              local_store: {
+                enabled: true,
+                path: '/random/path'
+              }
+            }
+          )
+        end
+      end
+
+      context 'when Pages deployed external to GitLab' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              pages_enabled: true,
+              pages_path: '/random/path',
+              pages_host: 'pages.example.com',
+              pages_port: 443,
+              pages_https: true,
+              pages_object_store_enabled: true,
+              pages_object_store_remote_directory: 'foobar',
+              pages_object_store_connection: aws_connection_hash
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with user specified values' do
+          expect(gitlab_yml[:production][:pages]).to eq(
+            {
+              enabled: true,
+              access_control: false,
+              path: '/random/path',
+              host: 'pages.example.com',
+              port: 443,
+              https: true,
+              external_http: false,
+              external_https: false,
+              artifacts_server: true,
+              object_store: {
+                enabled: true,
+                remote_directory: 'foobar',
+                connection: aws_connection_data
+              },
+              local_store: {
+                enabled: true,
+                path: '/random/path'
+              }
+            }
+          )
+        end
+      end
+
+      describe 'for external HTTPS settings' do
+        context 'when external_https is used' do
+          before do
+            stub_gitlab_rb(
+              external_url: 'https://gitlab.example.com',
+              pages_external_url: 'https://pages.example.com',
+              gitlab_pages: {
+                external_https: ['1.2.3.4']
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with pages.external_https set to true' do
+            expect(gitlab_yml[:production][:pages][:external_https]).to be true
+          end
+        end
+
+        context 'when external_https_proxyv2 is used' do
+          before do
+            stub_gitlab_rb(
+              external_url: 'https://gitlab.example.com',
+              pages_external_url: 'https://pages.example.com',
+              gitlab_pages: {
+                external_https_proxyv2: ['1.2.3.4']
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with pages.external_https set to true' do
+            expect(gitlab_yml[:production][:pages][:external_https]).to be true
+          end
+        end
+      end
+
+      describe 'Pages local store settings' do
+        context 'when neither Pages path nor local store path is specified' do
+          before do
+            stub_gitlab_rb(
+              external_url: 'https://gitlab.example.com',
+              pages_external_url: 'https://pages.example.com'
+            )
+          end
+
+          it 'renders gitlab.yml with Pages local store path set to default Pages path' do
+            expect(gitlab_yml[:production][:pages][:local_store][:path]).to eq('/var/opt/gitlab/gitlab-rails/shared/pages')
+          end
+        end
+
+        context 'when Pages path is specified but not local store path' do
+          before do
+            stub_gitlab_rb(
+              external_url: 'https://gitlab.example.com',
+              pages_external_url: 'https://pages.example.com',
+              gitlab_rails: {
+                pages_local_store_enabled: true,
+                pages_path: '/tmp/test'
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with Pages local store path set to Pages path' do
+            expect(gitlab_yml[:production][:pages][:local_store][:path]).to eq('/tmp/test')
+          end
+        end
+
+        context 'when Pages local store path is different than Pages path' do
+          before do
+            stub_gitlab_rb(
+              external_url: 'https://gitlab.example.com',
+              pages_external_url: 'https://pages.example.com',
+              gitlab_rails: {
+                pages_path: '/tmp/test',
+                pages_local_store_path: '/another/path'
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with specified Pages local store path' do
+            expect(gitlab_yml[:production][:pages][:local_store][:path]).to eq('/another/path')
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/prometheus_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/prometheus_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9wcm9tZXRoZXVzX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/prometheus_spec.rb
@@ -0,0 +1,106 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'Prometheus settings' do
+    context 'with default configuration' do
+      it 'renders gitlab.yml with bundled Prometheus default values' do
+        expect(gitlab_yml[:production][:prometheus]).to eq(
+          enabled: true,
+          server_address: 'localhost:9090'
+        )
+      end
+    end
+
+    context 'with user specified configuation' do
+      context 'when bundled Prometheus is running on non-default address' do
+        before do
+          stub_gitlab_rb(
+            prometheus: {
+              listen_address: '0.0.0.0:9191'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with correct values' do
+          expect(gitlab_yml[:production][:prometheus]).to eq(
+            enabled: true,
+            server_address: '0.0.0.0:9191'
+          )
+        end
+      end
+
+      context 'when bundled Prometheus is running on non-default address with only port specified' do
+        before do
+          stub_gitlab_rb(
+            prometheus: {
+              listen_address: ':8080'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with server address rendered as a string' do
+          expect(gitlab_yml[:production][:prometheus]).to eq(
+            enabled: true,
+            server_address: ':8080'
+          )
+        end
+      end
+
+      context 'when configured with an external and internal Prometheus addresses' do
+        before do
+          stub_gitlab_rb(
+            prometheus: {
+              listen_address: '0.0.0.0:9191'
+            },
+            gitlab_rails: {
+              prometheus_address: '1.1.1.1:2222'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with the external address' do
+          expect(gitlab_yml[:production][:prometheus]).to eq(
+            enabled: true,
+            server_address: '1.1.1.1:2222'
+          )
+        end
+      end
+
+      context 'when bundled Prometheus is disabled and no external Prometheus address is specified' do
+        before do
+          stub_gitlab_rb(
+            prometheus: {
+              enable: false
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with Prometheus disabled' do
+          expect(gitlab_yml[:production][:prometheus][:enabled]).to be false
+        end
+      end
+
+      context 'when bundled Prometheus is disabled but an external Prometheus address is specified' do
+        before do
+          stub_gitlab_rb(
+            prometheus: {
+              enable: false,
+            },
+            gitlab_rails: {
+              prometheus_address: '1.2.3.4:2222'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with external Prometheus addreess' do
+          expect(gitlab_yml[:production][:prometheus]).to eq(
+            enabled: true,
+            server_address: '1.2.3.4:2222'
+          )
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/pseudonymizer_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/pseudonymizer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9wc2V1ZG9ueW1pemVyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/pseudonymizer_spec.rb
@@ -0,0 +1,47 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+  include_context 'object storage config'
+
+  describe 'pseudonymizer settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml with pseudonymizer disabled' do
+        config = gitlab_yml[:production][:pseudonymizer]
+        expect(config).to eq(
+          {
+            manifest: nil,
+            upload: {
+              connection: {},
+              remote_directory: nil
+            }
+          }
+        )
+      end
+    end
+
+    context 'with user specified values' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            pseudonymizer_manifest: 'another/path/manifest.yml',
+            pseudonymizer_upload_remote_directory: 'gitlab-pseudo',
+            pseudonymizer_upload_connection: aws_connection_hash,
+          }
+        )
+      end
+      it 'renders gitlab.yml with user specified values' do
+        config = gitlab_yml[:production][:pseudonymizer]
+        expect(config).to eq(
+          {
+            manifest: 'another/path/manifest.yml',
+            upload: {
+              connection: aws_connection_data,
+              remote_directory: 'gitlab-pseudo'
+            }
+          }
+        )
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/registry_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/registry_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9yZWdpc3RyeV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/registry_spec.rb
@@ -0,0 +1,122 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'Registry settings' do
+    context 'with default configuration' do
+      context 'when GitLab is running under http' do
+        it 'renders gitlab.yml without Registry settings' do
+          expect(gitlab_yml[:production][:registry]).to eq(
+            enabled: false,
+            api_url: nil,
+            host: nil,
+            port: nil,
+            issuer: 'omnibus-gitlab-issuer',
+            key: '/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key',
+            notification_secret: nil,
+            path: nil
+          )
+        end
+      end
+
+      context 'when GitLab is running under HTTPS using Omnibus automated TLS certificates' do
+        before do
+          stub_gitlab_rb(
+            external_url: 'https://gitlab.example.com'
+          )
+        end
+
+        it 'automatically enables Registry and renders gitlab.yml accordingly' do
+          expect(gitlab_yml[:production][:registry]).to eq(
+            enabled: true,
+            host: 'gitlab.example.com',
+            port: 5050,
+            api_url: 'http://localhost:5000',
+            issuer: 'omnibus-gitlab-issuer',
+            key: '/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key',
+            notification_secret: nil,
+            path: '/var/opt/gitlab/gitlab-rails/shared/registry'
+          )
+        end
+      end
+
+      context 'with user specified configuration' do
+        context 'when URL for bundled Registry is specified' do
+          before do
+            stub_gitlab_rb(
+              registry_external_url: 'http://registry.example.com'
+            )
+          end
+
+          it 'renders gitlab.yml with specified Registry URL and other default Registry settings' do
+            expect(gitlab_yml[:production][:registry]).to eq(
+              enabled: true,
+              host: 'registry.example.com',
+              port: nil,
+              api_url: 'http://localhost:5000',
+              issuer: 'omnibus-gitlab-issuer',
+              key: '/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key',
+              notification_secret: nil,
+              path: '/var/opt/gitlab/gitlab-rails/shared/registry'
+            )
+          end
+        end
+
+        context 'when other Registry configuration are specified' do
+          before do
+            stub_gitlab_rb(
+              registry_external_url: 'http://registry.example.com:1234',
+              registry: {
+                registry_http_addr: 'localhost:1111'
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with specified Registry settings' do
+            expect(gitlab_yml[:production][:registry]).to eq(
+              enabled: true,
+              host: 'registry.example.com',
+              port: 1234,
+              api_url: 'http://localhost:1111',
+              issuer: 'omnibus-gitlab-issuer',
+              key: '/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key',
+              notification_secret: nil,
+              path: '/var/opt/gitlab/gitlab-rails/shared/registry'
+            )
+          end
+        end
+
+        context 'with external Registry' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                registry_enabled: 'true',
+                registry_key_path: '/fake/path',
+                registry_host: 'registry.example.com',
+                registry_api_url: 'http://registry.example.com:1234',
+                registry_port: 1234,
+                registry_issuer: 'foobar',
+                registry_notification_secret: 'qwerty',
+                registry_path: '/tmp/registry'
+              }
+            )
+          end
+
+          it 'renders gitlab.yml with correct registry settings' do
+            expect(gitlab_yml[:production][:registry]).to eq(
+              enabled: true,
+              host: 'registry.example.com',
+              port: 1234,
+              api_url: 'http://registry.example.com:1234',
+              issuer: 'foobar',
+              key: '/fake/path',
+              notification_secret: 'qwerty',
+              path: '/tmp/registry'
+            )
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/repositories_storages_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/repositories_storages_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9yZXBvc2l0b3JpZXNfc3RvcmFnZXNfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/repositories_storages_spec.rb
@@ -0,0 +1,62 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  using RSpec::Parameterized::TableSyntax
+  include_context 'gitlab-rails'
+
+  describe 'repositories storages settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml with default repository storage settings' do
+        expect(gitlab_yml[:production][:repositories][:storages]).to eq(
+          default: {
+            gitaly_address: 'unix:/var/opt/gitlab/gitaly/gitaly.socket',
+            path: '/var/opt/gitlab/git-data/repositories'
+          }
+        )
+      end
+    end
+
+    context 'with user specified values' do
+      before do
+        stub_gitlab_rb(
+          git_data_dirs: {
+            second_storage: {
+              path: '/tmp/foobar',
+              gitaly_address: 'unix:/var/gitaly.socket'
+            }
+          }
+        )
+      end
+
+      it 'renders gitlab.yml with specified repository storage settings' do
+        expect(gitlab_yml[:production][:repositories][:storages]).to eq(
+          second_storage: {
+            gitaly_address: 'unix:/var/gitaly.socket',
+            path: '/tmp/foobar/repositories'
+          }
+        )
+      end
+
+      context 'when path is not provided for a storage' do
+        before do
+          stub_gitlab_rb(
+            git_data_dirs: {
+              default: {
+                gitaly_address: 'unix:/var/gitaly.socket'
+              }
+            }
+          )
+        end
+
+        it 'sets the default path' do
+          expect(gitlab_yml[:production][:repositories][:storages]).to eq(
+            default: {
+              gitaly_address: 'unix:/var/gitaly.socket',
+              path: '/var/opt/gitlab/git-data/repositories'
+            }
+          )
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/sentry_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/sentry_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9zZW50cnlfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/sentry_spec.rb
@@ -0,0 +1,44 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'sentry settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml with sentry disabled' do
+        expect(gitlab_yml[:production][:sentry]).to eq(
+          {
+            enabled: false,
+            dsn: nil,
+            clientside_dsn: nil,
+            environment: nil
+          }
+        )
+      end
+    end
+
+    context 'when the user enables sentry' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            sentry_enabled: true,
+            sentry_dsn: 'https://708cd0ca88972f04d5c836a395b8db63@example.com/76',
+            sentry_clientside_dsn: 'https://708cd0ca88972f04d5c836a395b8db63@example.com/77',
+            sentry_environment: 'testing'
+          }
+        )
+      end
+
+      it 'renders gitlab.yml with user specified values' do
+        expect(gitlab_yml[:production][:sentry]).to eq(
+          {
+            enabled: true,
+            dsn: 'https://708cd0ca88972f04d5c836a395b8db63@example.com/76',
+            clientside_dsn: 'https://708cd0ca88972f04d5c836a395b8db63@example.com/77',
+            environment: 'testing'
+          }
+        )
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/service_desk_email_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/service_desk_email_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9zZXJ2aWNlX2Rlc2tfZW1haWxfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/service_desk_email_spec.rb
@@ -0,0 +1,60 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'service desk email settings' do
+    describe 'log file location' do
+      context 'with default values' do
+        it 'renders gitlab.yml with default value for mailroom log file path' do
+          expect(gitlab_yml[:production][:service_desk_email][:log_path]).to eq '/var/log/gitlab/mailroom/mail_room_json.log'
+        end
+      end
+
+      context 'with custom log file path specified' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              service_desk_email_log_file: '/my/custom/log/file'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with specified value for mailroom log file path' do
+          expect(gitlab_yml[:production][:service_desk_email][:log_path]).to eq '/my/custom/log/file'
+        end
+      end
+
+      context 'with custom log directory specified' do
+        before do
+          stub_gitlab_rb(
+            mailroom: {
+              log_directory: '/my/custom/directory'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with default log file name inside specified directory' do
+          expect(gitlab_yml[:production][:service_desk_email][:log_path]).to eq '/my/custom/directory/mail_room_json.log'
+        end
+      end
+
+      context 'with custom log file and directory specified' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              service_desk_email_log_file: '/foobar.log'
+            },
+            mailroom: {
+              log_directory: '/my/custom/directory'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with specified log file path irrespective of specified directory' do
+          expect(gitlab_yml[:production][:service_desk_email][:log_path]).to eq '/foobar.log'
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/shutdown_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/shutdown_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9zaHV0ZG93bl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/shutdown_spec.rb
@@ -0,0 +1,27 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'shutdown settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml with default value for blackout seconds' do
+        expect(gitlab_yml[:production][:shutdown][:blackout_seconds]).to eq(10)
+      end
+    end
+
+    context 'with user specified values' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            shutdown_blackout_seconds: 30
+          }
+        )
+      end
+
+      it 'renders gitlab.yml with specified value for blackout seconds' do
+        expect(gitlab_yml[:production][:shutdown][:blackout_seconds]).to eq(30)
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/sidekiq_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/sidekiq_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9zaWRla2lxX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/sidekiq_spec.rb
@@ -0,0 +1,79 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'sidekiq settings' do
+    describe 'log_format' do
+      context 'with default values' do
+        it 'renders gitlab.yml with sidekiq log format set to json' do
+          expect(gitlab_yml[:production][:sidekiq][:log_format]).to eq('json')
+        end
+      end
+
+      context 'with user specified value' do
+        before do
+          stub_gitlab_rb(
+            sidekiq: {
+              log_format: 'text'
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with user specified value for sidekiq log format' do
+          expect(gitlab_yml[:production][:sidekiq][:log_format]).to eq('text')
+        end
+      end
+    end
+
+    describe 'routing_rules' do
+      context 'with default values' do
+        it 'renders gitlab.yml without routing_rules' do
+          expect(gitlab_yml[:production][:sidekiq]).not_to include(:routing_rules)
+        end
+      end
+
+      context 'with an empty array' do
+        before do
+          stub_gitlab_rb(
+            sidekiq: {
+              routing_rules: []
+            }
+          )
+        end
+
+        it 'renders gitlab.yml without routing_rules' do
+          expect(gitlab_yml[:production][:sidekiq]).not_to include(:routing_rules)
+        end
+      end
+
+      context 'with a valid routing rules list' do
+        before do
+          stub_gitlab_rb(
+            sidekiq: {
+              routing_rules: [
+                ["resource_boundary=cpu", "cpu_boundary"],
+                ["feature_category=pages", nil],
+                ["feature_category=search", ''],
+                ["feature_category=memory|resource_boundary=memory", ''],
+                ["*", "default"]
+              ]
+            }
+          )
+        end
+
+        it 'renders gitlab.yml with user specified value for sidekiq routing rules' do
+          expect(gitlab_yml[:production][:sidekiq][:routing_rules]).to eq(
+            [
+              ["resource_boundary=cpu", "cpu_boundary"],
+              ["feature_category=pages", nil],
+              ["feature_category=search", ""],
+              ["feature_category=memory|resource_boundary=memory", ""],
+              ["*", "default"]
+            ]
+          )
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/smartcard_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/smartcard_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC9zbWFydGNhcmRfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/smartcard_spec.rb
@@ -0,0 +1,46 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  include_context 'gitlab-rails'
+
+  describe 'Smartcard authentication settings' do
+    context 'with default configuration' do
+      it 'renders gitlab.yml with Smartcard authentication disabled' do
+        expect(gitlab_yml[:production][:smartcard]).to eq(
+          enabled: false,
+          ca_file: '/etc/gitlab/ssl/CA.pem',
+          client_certificate_required_host: nil,
+          client_certificate_required_port: 3444,
+          required_for_git_access: false,
+          san_extensions: false
+        )
+      end
+    end
+
+    context 'with user specified configuration' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            smartcard_enabled: true,
+            smartcard_ca_file: '/foobar/CA.pem',
+            smartcard_client_certificate_required_host: 'smartcard.gitlab.example.com',
+            smartcard_client_certificate_required_port: 123,
+            smartcard_required_for_git_access: true,
+            smartcard_san_extensions: true
+          }
+        )
+      end
+
+      it 'renders gitlab.yml with user specified values for Smartcard authentication' do
+        expect(gitlab_yml[:production][:smartcard]).to eq(
+          enabled: true,
+          ca_file: '/foobar/CA.pem',
+          client_certificate_required_host: 'smartcard.gitlab.example.com',
+          client_certificate_required_port: 123,
+          required_for_git_access: true,
+          san_extensions: true
+        )
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/unleash_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/unleash_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMvZ2l0bGFiX3ltbC91bmxlYXNoX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails/gitlab_yml/unleash_spec.rb
@@ -0,0 +1,38 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  using RSpec::Parameterized::TableSyntax
+  include_context 'gitlab-rails'
+
+  describe 'Unleash settings' do
+    context 'with default values' do
+      it 'renders gitlab.yml unleash disabled' do
+        expect(gitlab_yml[:production][:feature_flags][:unleash]).to eq(
+          enabled: false
+        )
+      end
+    end
+
+    context 'with user specified values' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            feature_flags_unleash_enabled: true,
+            feature_flags_unleash_url: 'foobar.com',
+            feature_flags_unleash_app_name: 'GitLab Production',
+            feature_flags_unleash_instance_id: 'foobar'
+          }
+        )
+      end
+
+      it 'renders gitlab.yml with user specified values' do
+        expect(gitlab_yml[:production][:feature_flags][:unleash]).to eq(
+          enabled: true,
+          url: 'foobar.com',
+          app_name: 'GitLab Production',
+          instance_id: 'foobar'
+        )
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHNfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails_spec.rb
@@ -0,0 +1,1147 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-rails' do
+  using RSpec::Parameterized::TableSyntax
+
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink runit_service)).converge('gitlab::default') }
+  let(:redis_instances) { %w(cache queues shared_state trace_chunks rate_limiting sessions) }
+  let(:config_dir) { '/var/opt/gitlab/gitlab-rails/etc/' }
+  let(:default_vars) do
+    {
+      'HOME' => '/var/opt/gitlab',
+      'RAILS_ENV' => 'production',
+      'SIDEKIQ_MEMORY_KILLER_MAX_RSS' => '2000000',
+      'BUNDLE_GEMFILE' => '/opt/gitlab/embedded/service/gitlab-rails/Gemfile',
+      'PATH' => '/opt/gitlab/bin:/opt/gitlab/embedded/bin:/bin:/usr/bin',
+      'ICU_DATA' => '/opt/gitlab/embedded/share/icu/current',
+      'PYTHONPATH' => '/opt/gitlab/embedded/lib/python3.9/site-packages',
+      'EXECJS_RUNTIME' => 'Disabled',
+      'TZ' => ':/etc/localtime',
+      'LD_PRELOAD' => '/opt/gitlab/embedded/lib/libjemalloc.so',
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
+      'SSL_CERT_FILE' => '/opt/gitlab/embedded/ssl/cert.pem'
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow(File).to receive(:symlink?).and_call_original
+  end
+
+  context 'with defaults' do
+    it 'creates a default VERSION file and restarts services' do
+      expect(chef_run).to create_version_file('Create version file for Rails').with(
+        version_file_path: '/var/opt/gitlab/gitlab-rails/RUBY_VERSION',
+        version_check_cmd: '/opt/gitlab/embedded/bin/ruby --version'
+      )
+
+      dependent_services = []
+      dependent_services.each do |svc|
+        expect(chef_run.version_file('Create version file for Rails')).to notify("runit_service[#{svc}]").to(:restart)
+      end
+    end
+  end
+
+  context 'when manage-storage-directories is disabled' do
+    cached(:chef_run) do
+      RSpec::Mocks.with_temporary_scope do
+        stub_gitlab_rb(gitlab_rails: { shared_path: '/tmp/shared',
+                                       uploads_directory: '/tmp/uploads',
+                                       uploads_storage_path: '/tmp/uploads_storage' },
+                       gitlab_ci: { builds_directory: '/tmp/builds' },
+                       git_data_dirs: {
+                         "some_storage" => {
+                           "path" => "/tmp/git-data"
+                         }
+                       },
+                       manage_storage_directories: { enable: false })
+      end
+
+      ChefSpec::SoloRunner.new.converge('gitlab::default')
+    end
+
+    it 'does not create the git-data directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/git-data')
+    end
+
+    it 'does not create the repositories directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/git-data/repositories')
+    end
+
+    it 'does not create the shared directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared')
+    end
+
+    it 'does not create the artifacts directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/artifacts')
+    end
+
+    it 'does not create the external-diffs directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/external-diffs')
+    end
+
+    it 'does not create the lfs storage directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/lfs-objects')
+    end
+
+    it 'does not create the packages storage directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/packages')
+    end
+
+    it 'does not create the dependency_proxy storage directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/dependency_proxy')
+    end
+
+    it 'does not create the terraform_state storage directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/terraform_state')
+    end
+
+    it 'does not create the ci_secure_files storage directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/ci_secure_files')
+    end
+
+    it 'does not create the GitLab pages directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/pages')
+    end
+
+    it 'does not create the uploads directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/uploads')
+    end
+
+    it 'does not create the ci builds directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/builds')
+    end
+
+    it 'does not create the uploads storage directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/uploads_storage')
+    end
+  end
+
+  context 'when manage-storage-directories is enabled' do
+    cached(:chef_run) do
+      RSpec::Mocks.with_temporary_scope do
+        stub_gitlab_rb(gitlab_rails: { shared_path: '/tmp/shared',
+                                       uploads_directory: '/tmp/uploads',
+                                       uploads_storage_path: '/tmp/uploads_storage' },
+                       gitlab_ci: { builds_directory: '/tmp/builds' },
+                       git_data_dirs: {
+                         "some_storage" => {
+                           "path" => "/tmp/git-data"
+                         }
+                       })
+      end
+
+      ChefSpec::SoloRunner.converge('gitlab::default')
+    end
+
+    it 'creates the git-data directory' do
+      expect(chef_run).to create_storage_directory('/tmp/git-data').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the repositories directory' do
+      expect(chef_run).to create_storage_directory('/tmp/git-data/repositories').with(owner: 'git', group: 'git', mode: '2770')
+    end
+
+    it 'creates the shared directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared').with(owner: 'git', group: 'gitlab-www', mode: '0751')
+    end
+
+    it 'creates the artifacts directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared/artifacts').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the external-diffs directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared/external-diffs').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the lfs storage directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared/lfs-objects').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the packages directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared/packages').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the dependency_proxy directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared/dependency_proxy').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the terraform_state directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared/terraform_state').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the ci_secure_files directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared/ci_secure_files').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the encrypted_settings directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared/encrypted_settings').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the GitLab pages directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared/pages').with(owner: 'git', group: 'gitlab-www', mode: '0750')
+    end
+
+    it 'creates the shared tmp directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared/tmp').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the shared cache directory' do
+      expect(chef_run).to create_storage_directory('/tmp/shared/cache').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the uploads directory' do
+      expect(chef_run).to create_storage_directory('/tmp/uploads').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the ci builds directory' do
+      expect(chef_run).to create_storage_directory('/tmp/builds').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the uploads storage directory' do
+      expect(chef_run).to create_storage_directory('/tmp/uploads_storage').with(owner: 'git', group: 'git', mode: '0700')
+    end
+  end
+
+  context 'when uploads storage directory is not specified' do
+    cached(:chef_run) do
+      ChefSpec::SoloRunner.converge('gitlab::default')
+    end
+
+    it 'does not create the uploads storage directory' do
+      expect(chef_run).not_to create_storage_directory('/opt/gitlab/embedded/service/gitlab-rails/public')
+    end
+  end
+
+  context 'with redis settings' do
+    let(:config_file) { '/var/opt/gitlab/gitlab-rails/etc/resque.yml' }
+    let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') }
+
+    context 'and default configuration' do
+      it 'creates the config file with the required redis settings' do
+        expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root').with_variables(
+          hash_including(
+            redis_url: URI('unix:/var/opt/gitlab/redis/redis.socket'),
+            redis_sentinels: [],
+            redis_enable_client: true
+          )
+        )
+
+        expect(chef_run).to render_file(config_file).with_content { |content|
+          expect(content).to match(%r(url: unix:/var/opt/gitlab/redis/redis.socket$))
+          expect(content).not_to match(/id:/)
+        }
+      end
+
+      it 'creates cable.yml with the same settings' do
+        expect(chef_run).to create_templatesymlink('Create a cable.yml and create a symlink to Rails root').with_variables(
+          hash_including(
+            redis_url: URI('unix:/var/opt/gitlab/redis/redis.socket'),
+            redis_sentinels: [],
+            redis_enable_client: true
+          )
+        )
+
+        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/cable.yml').with_content { |content|
+          expect(content).to match(%r(url: unix:/var/opt/gitlab/redis/redis.socket$))
+        }
+      end
+
+      it 'does not render the separate instance configurations' do
+        redis_instances.each do |instance|
+          expect(chef_run).not_to render_file("#{config_dir}redis.#{instance}.yml")
+        end
+      end
+
+      it 'deletes the separate instance config files' do
+        redis_instances.each do |instance|
+          expect(chef_run).to delete_file("/opt/gitlab/embedded/service/gitlab-rails/config/redis.#{instance}.yml")
+          expect(chef_run).to delete_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml")
+        end
+      end
+    end
+
+    context 'and custom configuration' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            redis_host: 'redis.example.com',
+            redis_port: 8888,
+            redis_database: 2,
+            redis_password: 'mypass',
+            redis_enable_client: false
+          }
+        )
+      end
+
+      it 'creates the config file with custom host, port, password and database' do
+        expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root').with_variables(
+          hash_including(
+            redis_url: URI('redis://:mypass@redis.example.com:8888/2'),
+            redis_sentinels: [],
+            redis_enable_client: false
+          )
+        )
+
+        expect(chef_run).to render_file(config_file).with_content { |content|
+          expect(content).to match(%r(url: redis://:mypass@redis.example.com:8888/2))
+          expect(content).to match(/id:$/)
+        }
+      end
+
+      it 'creates cable.yml with custom host, port, password and database' do
+        expect(chef_run).to create_templatesymlink('Create a cable.yml and create a symlink to Rails root').with_variables(
+          hash_including(
+            redis_url: URI('redis://:mypass@redis.example.com:8888/2'),
+            redis_sentinels: [],
+            redis_enable_client: false
+          )
+        )
+
+        expect(chef_run).to render_file(config_file).with_content { |content|
+          expect(content).to match(%r(url: redis://:mypass@redis.example.com:8888/2))
+          expect(content).to match(/id:$/)
+        }
+      end
+    end
+
+    context 'with multiple instances' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            redis_enable_client: false,
+            redis_cache_instance: "redis://:fakepass@fake.redis.cache.com:8888/2",
+            redis_cache_sentinels: [
+              { host: 'cache', port: '1234' },
+              { host: 'cache', port: '3456' }
+            ],
+            redis_queues_instance: "redis://:fakepass@fake.redis.queues.com:8888/2",
+            redis_queues_sentinels: [
+              { host: 'queues', port: '1234' },
+              { host: 'queues', port: '3456' }
+            ],
+            redis_shared_state_instance: "redis://:fakepass@fake.redis.shared_state.com:8888/2",
+            redis_shared_state_sentinels: [
+              { host: 'shared_state', port: '1234' },
+              { host: 'shared_state', port: '3456' }
+            ],
+            redis_trace_chunks_instance: "redis://:fakepass@fake.redis.trace_chunks.com:8888/2",
+            redis_trace_chunks_sentinels: [
+              { host: 'trace_chunks', port: '1234' },
+              { host: 'trace_chunks', port: '3456' }
+            ],
+            redis_rate_limiting_instance: "redis://:fakepass@fake.redis.rate_limiting.com:8888/2",
+            redis_rate_limiting_sentinels: [
+              { host: 'rate_limiting', port: '1234' },
+              { host: 'rate_limiting', port: '3456' }
+            ],
+            redis_sessions_instance: "redis://:fakepass@fake.redis.sessions.com:8888/2",
+            redis_sessions_sentinels: [
+              { host: 'sessions', port: '1234' },
+              { host: 'sessions', port: '3456' }
+            ],
+            redis_actioncable_instance: "redis://:fakepass@fake.redis.actioncable.com:8888/2",
+            redis_actioncable_sentinels: [
+              { host: 'actioncable', port: '1234' },
+              { host: 'actioncable', port: '3456' }
+            ]
+          }
+        )
+      end
+
+      it 'render separate config files' do
+        redis_instances.each do |instance|
+          expect(chef_run).to create_templatesymlink("Create a redis.#{instance}.yml and create a symlink to Rails root").with_variables(
+            redis_url: "redis://:fakepass@fake.redis.#{instance}.com:8888/2",
+            redis_sentinels: [{ "host" => instance, "port" => "1234" }, { "host" => instance, "port" => "3456" }],
+            redis_enable_client: false
+          )
+          expect(chef_run).not_to delete_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml")
+        end
+      end
+
+      it 'still renders the default configuration file' do
+        expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root')
+      end
+
+      it 'creates cable.yml with custom settings' do
+        expect(chef_run).to create_templatesymlink('Create a cable.yml and create a symlink to Rails root').with_variables(
+          hash_including(
+            redis_url: "redis://:fakepass@fake.redis.actioncable.com:8888/2",
+            redis_sentinels: [{ 'host' => 'actioncable', 'port' => '1234' }, { 'host' => 'actioncable', 'port' => '3456' }],
+            redis_enable_client: false
+          )
+        )
+      end
+    end
+  end
+
+  describe 'gitlab.yml' do
+    gitlab_yml_path = '/var/opt/gitlab/gitlab-rails/etc/gitlab.yml'
+    let(:gitlab_yml) { chef_run.template(gitlab_yml_path) }
+    let(:gitlab_yml_templatesymlink) { chef_run.templatesymlink('Create a gitlab.yml and create a symlink to Rails root') }
+
+    # NOTE: Test if we pass proper notifications to other resources
+    describe 'rails cache management' do
+      before do
+        stub_default_not_listening?(false)
+      end
+
+      context 'with default values' do
+        it 'should notify rails cache clear resource' do
+          expect(gitlab_yml_templatesymlink).to notify('execute[clear the gitlab-rails cache]')
+        end
+      end
+
+      context 'with rake_cache_clear set to false' do
+        before do
+          stub_gitlab_rb(gitlab_rails: { rake_cache_clear: false })
+        end
+
+        it 'should notify rails cache clear resource' do
+          expect(gitlab_yml_templatesymlink).to notify(
+            'execute[clear the gitlab-rails cache]')
+        end
+
+        it 'should not run cache clear' do
+          expect(chef_run).not_to run_execute(
+            'clear the gitlab-rails cache')
+        end
+      end
+    end
+  end
+
+  context 'with environment variables' do
+    context 'by default' do
+      it 'creates necessary env variable files' do
+        expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables(default_vars)
+      end
+
+      context 'when a custom env variable is specified' do
+        before do
+          stub_gitlab_rb(gitlab_rails: { env: { 'IAM' => 'CUSTOMVAR' } })
+        end
+
+        it 'creates necessary env variable files' do
+          expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables(
+            default_vars.merge(
+              {
+                'IAM' => 'CUSTOMVAR'
+              }
+            )
+          )
+        end
+      end
+    end
+
+    context 'when relative URL is enabled' do
+      before do
+        stub_gitlab_rb(gitlab_rails: { gitlab_relative_url: '/gitlab' })
+      end
+
+      it 'creates necessary env variable files' do
+        expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables(
+          default_vars.merge(
+            {
+              'RAILS_RELATIVE_URL_ROOT' => '/gitlab'
+            }
+          )
+        )
+      end
+    end
+
+    context 'when relative URL is specified in external_url' do
+      before do
+        stub_gitlab_rb(external_url: 'http://localhost/gitlab')
+      end
+
+      it 'creates necessary env variable files' do
+        expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables(
+          default_vars.merge(
+            {
+              'RAILS_RELATIVE_URL_ROOT' => '/gitlab'
+            }
+          )
+        )
+      end
+    end
+
+    context 'when jemalloc is disabled' do
+      before do
+        stub_gitlab_rb(gitlab_rails: { enable_jemalloc: false })
+      end
+
+      it 'creates necessary env variable files' do
+        vars = default_vars.dup
+        vars.delete("LD_PRELOAD")
+        expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables(vars)
+      end
+    end
+  end
+
+  describe "with symlinked templates" do
+    let(:chef_run) { ChefSpec::SoloRunner.new.converge('gitlab::default') }
+
+    before do
+      %w(
+        alertmanager
+        gitlab-exporter
+        gitlab-pages
+        gitlab-kas
+        gitlab-workhorse
+        logrotate
+        nginx
+        node-exporter
+        postgres-exporter
+        postgresql
+        prometheus
+        redis
+        redis-exporter
+        sidekiq
+        puma
+        gitaly
+      ).map { |svc| stub_should_notify?(svc, true) }
+    end
+
+    describe 'database.yml' do
+      database_yml_path = '/var/opt/gitlab/gitlab-rails/etc/database.yml'
+      let(:database_yml) { chef_run.template(database_yml_path) }
+      let(:database_yml_content) { ChefSpec::Renderer.new(chef_run, database_yml).content }
+      let(:generated_yml_content) { YAML.safe_load(database_yml_content) }
+
+      let(:config_file) { database_yml_path }
+      let(:templatesymlink) { chef_run.templatesymlink('Create a database.yml and create a symlink to Rails root') }
+
+      context 'by default' do
+        cached(:chef_run) do
+          ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default')
+        end
+
+        it 'creates the template' do
+          expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+            hash_including(
+              'db_host' => '/var/opt/gitlab/postgresql',
+              'db_database' => 'gitlabhq_production',
+              'db_load_balancing' => { 'hosts' => [] },
+              'db_prepared_statements' => false,
+              'db_sslcompression' => 0,
+              'db_sslcert' => nil,
+              'db_sslkey' => nil,
+              'db_application_name' => nil
+            )
+          )
+        end
+
+        it 'template triggers notifications' do
+          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
+          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
+          expect(templatesymlink).not_to notify('runit_service[gitlab-workhorse]').to(:restart).delayed
+          expect(templatesymlink).not_to notify('runit_service[nginx]').to(:restart).delayed
+        end
+
+        it 'renders expected YAML' do
+          expect(generated_yml_content.dig('production', 'main', 'adapter')).to eq('postgresql')
+          expect(generated_yml_content.dig('production', 'main', 'host')).to eq('/var/opt/gitlab/postgresql')
+          expect(generated_yml_content.dig('production', 'main', 'port')).to eq(5432)
+          expect(generated_yml_content.dig('production', 'main', 'application_name')).to eq(nil)
+        end
+      end
+
+      context 'with specific database settings' do
+        context 'with an application name set' do
+          where(:appname, :expected) do
+            ''     | ''
+            'test' | 'test'
+          end
+
+          with_them do
+            cached(:chef_run) do
+              ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default')
+            end
+
+            before do
+              stub_gitlab_rb(
+                'gitlab_rails' => { 'db_application_name' => appname }
+              )
+            end
+
+            it 'renders expected YAML' do
+              expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+                hash_including(
+                  'db_application_name' => appname
+                )
+              )
+
+              expect(generated_yml_content.dig('production', 'main').keys).to include(*%w(adapter host port application_name))
+              expect(generated_yml_content.dig('production', 'main', 'application_name')).to eq(expected)
+            end
+          end
+        end
+
+        context 'when multiple postgresql listen_address is used' do
+          before do
+            stub_gitlab_rb(postgresql: { listen_address: "127.0.0.1,1.1.1.1" })
+          end
+
+          it 'creates the postgres configuration file with multi listen_address and database.yml file with one host' do
+            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+              hash_including(
+                'db_host' => '127.0.0.1'
+              )
+            )
+          end
+        end
+
+        context 'when no postgresql listen_address is used' do
+          it 'creates the postgres configuration file with empty listen_address and database.yml file with default one' do
+            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+              hash_including(
+                'db_host' => '/var/opt/gitlab/postgresql'
+              )
+            )
+          end
+        end
+
+        context 'when one postgresql listen_address is used' do
+          cached(:chef_run) do
+            RSpec::Mocks.with_temporary_scope do
+              stub_gitlab_rb(postgresql: { listen_address: "127.0.0.1" })
+            end
+
+            ChefSpec::SoloRunner.new.converge('gitlab::default')
+          end
+
+          it 'creates the postgres configuration file with one listen_address and database.yml file with one host' do
+            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+              hash_including(
+                'db_host' => '127.0.0.1'
+              )
+            )
+          end
+
+          it 'template triggers notifications' do
+            expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
+            expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
+            expect(templatesymlink).not_to notify('runit_service[gitlab-workhorse]').to(:restart).delayed
+            expect(templatesymlink).not_to notify('runit_service[nginx]').to(:restart).delayed
+          end
+        end
+
+        context 'when load balancers are specified' do
+          before do
+            stub_gitlab_rb(gitlab_rails: { db_load_balancing: { 'hosts' => ['primary.example.com', 'secondary.example.com'] } })
+          end
+
+          it 'uses provided value in database.yml' do
+            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+              hash_including(
+                'db_load_balancing' => { 'hosts' => ['primary.example.com', 'secondary.example.com'] }
+              )
+            )
+          end
+        end
+
+        context 'when prepared_statements are disabled' do
+          before do
+            stub_gitlab_rb(gitlab_rails: { db_prepared_statements: false })
+          end
+
+          it 'uses provided value in database.yml' do
+            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+              hash_including(
+                'db_prepared_statements' => false,
+                'db_statements_limit' => 1000
+              )
+            )
+          end
+        end
+
+        context 'when limit for prepared_statements are specified' do
+          before do
+            stub_gitlab_rb(gitlab_rails: { db_statements_limit: 12345 })
+          end
+
+          it 'uses provided value in database.yml' do
+            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+              hash_including(
+                'db_prepared_statements' => false,
+                'db_statements_limit' => 12345
+              )
+            )
+          end
+        end
+
+        context 'when SSL compression is enabled' do
+          before do
+            stub_gitlab_rb(gitlab_rails: { db_sslcompression: 1 })
+          end
+
+          it 'uses provided value in database.yml' do
+            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+              hash_including(
+                'db_sslcompression' => 1
+              )
+            )
+          end
+        end
+
+        context 'when SSL certificate and key for DB is specified' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                db_sslcert: '/etc/certs/db.cer',
+                db_sslkey: '/etc/certs/db.key'
+              }
+            )
+          end
+
+          it 'uses specified value in database.yml' do
+            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+              hash_including(
+                'db_sslcert' => '/etc/certs/db.cer',
+                'db_sslkey' => '/etc/certs/db.key'
+              )
+            )
+          end
+        end
+      end
+
+      describe 'client side statement_timeout' do
+        let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') }
+
+        context 'default values' do
+          it 'does not set a default value' do
+            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+              hash_including(
+                'db_statement_timeout' => nil
+              )
+            )
+
+            expect(chef_run).to render_file(config_file).with_content { |content|
+              expect(content).to match(%r(statement_timeout: $))
+            }
+          end
+        end
+
+        context 'custom value' do
+          before do
+            stub_gitlab_rb(
+              'postgresql' => { 'statement_timeout' => '65000' },
+              'gitlab_rails' => { 'db_statement_timeout' => '70000' }
+            )
+          end
+
+          it 'uses specified client side statement_timeout value' do
+            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+              hash_including(
+                'db_statement_timeout' => '70000'
+              )
+            )
+
+            expect(chef_run).to render_file(config_file).with_content { |content|
+              expect(content).to match(%r(statement_timeout: 70000$))
+            }
+          end
+        end
+      end
+
+      context 'adjusting database adapter connection parameters' do
+        let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') }
+
+        using RSpec::Parameterized::TableSyntax
+
+        where(:rb_param, :yaml_param, :default_value, :custom_value) do
+          'db_connect_timeout' | 'connect_timeout' | nil | 5
+          'db_keepalives' | 'keepalives' | nil | 1
+          'db_keepalives_idle' | 'keepalives_idle' | nil | 5
+          'db_keepalives_interval' | 'keepalives_interval' | nil | 3
+          'db_keepalives_count' | 'keepalives_count' | nil | 3
+          'db_tcp_user_timeout' | 'tcp_user_timeout' | nil | 13000
+        end
+
+        with_them do
+          context 'default values' do
+            it 'does not set a default value' do
+              expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+                hash_including(
+                  rb_param => default_value
+                )
+              )
+
+              expect(chef_run).to render_file(config_file).with_content { |content|
+                expect(content).to match(%r(#{yaml_param}: $))
+              }
+            end
+          end
+
+          context 'custom connection parameter value' do
+            before do
+              stub_gitlab_rb(
+                'gitlab_rails' => { rb_param => custom_value }
+              )
+            end
+
+            it 'uses specified connection parameter value' do
+              expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
+                hash_including(
+                  rb_param => custom_value
+                )
+              )
+
+              expect(chef_run).to render_file(config_file).with_content { |content|
+                expect(content).to match(%r(#{yaml_param}: #{custom_value}$))
+              }
+            end
+          end
+        end
+      end
+    end
+
+    describe 'gitlab_workhorse_secret' do
+      let(:templatesymlink) { chef_run.templatesymlink('Create a gitlab_workhorse_secret and create a symlink to Rails root') }
+
+      context 'by default' do
+        cached(:chef_run) do
+          ChefSpec::SoloRunner.new.converge('gitlab::default')
+        end
+
+        it 'creates the template' do
+          expect(chef_run).to create_templatesymlink("Create a gitlab_workhorse_secret and create a symlink to Rails root").with(
+            owner: 'root',
+            group: 'root',
+            mode: '0644'
+          )
+        end
+
+        it 'template triggers notifications' do
+          expect(templatesymlink).to notify('runit_service[gitlab-workhorse]').to(:restart).delayed
+          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
+          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
+        end
+      end
+
+      context 'with specific gitlab_workhorse_secret' do
+        cached(:chef_run) do
+          RSpec::Mocks.with_temporary_scope do
+            stub_gitlab_rb(gitlab_workhorse: { secret_token: 'abc123-gitlab-workhorse' })
+          end
+
+          ChefSpec::SoloRunner.new.converge('gitlab::default')
+        end
+
+        it 'renders the correct node attribute' do
+          expect(chef_run).to create_templatesymlink("Create a gitlab_workhorse_secret and create a symlink to Rails root").with_variables(
+            secret_token: 'abc123-gitlab-workhorse'
+          )
+        end
+
+        it 'uses the correct owner and permissions' do
+          expect(chef_run).to create_templatesymlink('Create a gitlab_workhorse_secret and create a symlink to Rails root').with(
+            owner: 'root',
+            group: 'root',
+            mode: '0644'
+          )
+        end
+
+        it 'template triggers notifications' do
+          expect(templatesymlink).to notify('runit_service[gitlab-workhorse]').to(:restart).delayed
+          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
+          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
+        end
+      end
+    end
+
+    describe 'gitlab_pages_secret' do
+      let(:templatesymlink) { chef_run.templatesymlink('Create a gitlab_pages_secret and create a symlink to Rails root') }
+
+      context 'with pages disabled' do
+        let(:api_secret_key) { SecureRandom.base64(32) }
+
+        cached(:chef_run) do
+          RSpec::Mocks.with_temporary_scope do
+            stub_gitlab_rb(
+              pages_enabled: false,
+              gitlab_pages: { api_secret_key: api_secret_key, enable: false },
+              pages_external_url: 'http://pages.example.com'
+            )
+          end
+
+          ChefSpec::SoloRunner.new.converge('gitlab::default')
+        end
+
+        it 'creates the template' do
+          expect(chef_run).to create_templatesymlink("Create a gitlab_pages_secret and create a symlink to Rails root").with(
+            owner: 'root',
+            group: 'root',
+            mode: '0644'
+          )
+        end
+      end
+
+      context 'by default' do
+        cached(:chef_run) do
+          RSpec::Mocks.with_temporary_scope do
+            stub_gitlab_rb(
+              external_url: 'http://gitlab.example.com',
+              pages_external_url: 'http://pages.example.com'
+            )
+          end
+
+          ChefSpec::SoloRunner.new.converge('gitlab::default')
+        end
+
+        it 'creates the template' do
+          expect(chef_run).to create_templatesymlink("Create a gitlab_pages_secret and create a symlink to Rails root").with(
+            owner: 'root',
+            group: 'root',
+            mode: '0644'
+          )
+        end
+
+        it 'template triggers notifications' do
+          expect(templatesymlink).to notify('runit_service[gitlab-pages]').to(:restart).delayed
+          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
+          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
+        end
+      end
+
+      context 'with specific gitlab_pages_secret' do
+        let(:api_secret_key) { SecureRandom.base64(32) }
+
+        cached(:chef_run) do
+          RSpec::Mocks.with_temporary_scope do
+            stub_gitlab_rb(
+              gitlab_pages: { api_secret_key: api_secret_key },
+              external_url: 'http://gitlab.example.com',
+              pages_external_url: 'http://pages.example.com'
+            )
+          end
+
+          ChefSpec::SoloRunner.new.converge('gitlab::default')
+        end
+
+        it 'renders the correct node attribute' do
+          expect(chef_run).to create_templatesymlink("Create a gitlab_pages_secret and create a symlink to Rails root").with_variables(
+            secret_token: api_secret_key
+          )
+        end
+
+        it 'uses the correct owner and permissions' do
+          expect(chef_run).to create_templatesymlink('Create a gitlab_pages_secret and create a symlink to Rails root').with(
+            owner: 'root',
+            group: 'root',
+            mode: '0644'
+          )
+        end
+
+        it 'template triggers notifications' do
+          expect(templatesymlink).to notify('runit_service[gitlab-pages]').to(:restart).delayed
+          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
+          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
+        end
+      end
+    end
+
+    describe 'gitlab_kas_secret' do
+      let(:templatesymlink) { chef_run.templatesymlink('Create a gitlab_kas_secret and create a symlink to Rails root') }
+
+      shared_examples 'creates the KAS template' do
+        it 'creates the template' do
+          expect(chef_run).to create_templatesymlink('Create a gitlab_kas_secret and create a symlink to Rails root').with(
+            owner: 'root',
+            group: 'root',
+            mode: '0644'
+          )
+        end
+      end
+
+      context 'with KAS disabled' do
+        cached(:chef_run) do
+          RSpec::Mocks.with_temporary_scope do
+            stub_gitlab_rb(
+              gitlab_kas: { enable: false }
+            )
+          end
+
+          ChefSpec::SoloRunner.new.converge('gitlab::default')
+        end
+
+        it_behaves_like 'creates the KAS template'
+      end
+
+      context 'with KAS enabled' do
+        cached(:chef_run) do
+          ChefSpec::SoloRunner.new.converge('gitlab::default')
+        end
+
+        it_behaves_like 'creates the KAS template'
+
+        it 'template triggers notifications' do
+          expect(templatesymlink).to notify('runit_service[gitlab-kas]').to(:restart).delayed
+          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
+          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
+        end
+      end
+
+      context 'with specific gitlab_kas_secret' do
+        let(:api_secret_key) { SecureRandom.base64(32) }
+
+        cached(:chef_run) do
+          RSpec::Mocks.with_temporary_scope do
+            stub_gitlab_rb(
+              gitlab_kas: { api_secret_key: api_secret_key }
+            )
+          end
+
+          ChefSpec::SoloRunner.new.converge('gitlab::default')
+        end
+
+        it 'renders the correct node attribute' do
+          expect(chef_run).to create_templatesymlink('Create a gitlab_kas_secret and create a symlink to Rails root').with_variables(
+            secret_token: api_secret_key
+          )
+        end
+
+        it_behaves_like 'creates the KAS template'
+
+        it 'template triggers notifications' do
+          expect(templatesymlink).to notify('runit_service[gitlab-kas]').to(:restart).delayed
+          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
+          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
+        end
+      end
+    end
+  end
+
+  describe 'GitLab Registry files' do
+    describe 'gitlab-registry.key file' do
+      context 'Registry is disabled' do
+        it 'does not generate gitlab-registry.key file' do
+          expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key")
+        end
+      end
+
+      context 'Registry is enabled' do
+        context 'with default configuration' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                registry_enabled: true
+              }
+            )
+          end
+
+          it 'generates key file in the default location' do
+            expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key").with_content(/\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m)
+          end
+        end
+
+        context 'with user specified configuration' do
+          context 'when location of key file is specified' do
+            before do
+              stub_gitlab_rb(
+                gitlab_rails: {
+                  registry_enabled: true,
+                  registry_key_path: '/fake/path'
+                }
+              )
+            end
+
+            it 'generates key file in the specified location' do
+              expect(chef_run).to render_file("/fake/path").with_content(/\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m)
+            end
+          end
+
+          context 'when key content is specified' do
+            before do
+              stub_gitlab_rb(
+                gitlab_rails: {
+                  registry_enabled: true
+                },
+                registry: {
+                  internal_key: 'foobar'
+                }
+              )
+            end
+
+            it 'generates key file with specified content' do
+              expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key").with_content('foobar')
+            end
+          end
+        end
+      end
+    end
+  end
+
+  context 'SMTP settings' do
+    context 'when connection pooling is not configured' do
+      it 'creates smtp_settings.rb with pooling disabled' do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            smtp_enable: true
+          }
+        )
+
+        expect(chef_run).to create_templatesymlink('Create a smtp_settings.rb and create a symlink to Rails root').with_variables(
+          hash_including(
+            'smtp_pool' => false
+          )
+        )
+      end
+    end
+
+    context 'when connection pooling is enabled' do
+      it 'creates smtp_settings.rb with pooling enabled' do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            smtp_enable: true,
+            smtp_pool: true
+          }
+        )
+
+        expect(chef_run).to create_templatesymlink('Create a smtp_settings.rb and create a symlink to Rails root').with_variables(
+          hash_including(
+            'smtp_pool' => true
+          )
+        )
+
+        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/smtp_settings.rb').with_content { |content|
+          expect(content).to include('ActionMailer::Base.delivery_method = :smtp_pool')
+        }
+      end
+    end
+  end
+
+  describe 'logrotate settings' do
+    context 'default values' do
+      it_behaves_like 'configured logrotate service', 'gitlab-pages', 'git', 'git'
+    end
+
+    context 'specified username and group' do
+      before do
+        stub_gitlab_rb(
+          user: {
+            username: 'foo',
+            group: 'bar'
+          }
+        )
+      end
+
+      it_behaves_like 'configured logrotate service', 'gitlab-pages', 'foo', 'bar'
+    end
+  end
+
+  describe 'cleaning up the legacy sidekiq log symlink' do
+    it 'removes the link if it existed' do
+      allow(File).to receive(:symlink?).with('/var/log/gitlab/gitlab-rails/sidekiq.log') { true }
+
+      expect(chef_run).to delete_link('/var/log/gitlab/gitlab-rails/sidekiq.log')
+    end
+
+    it 'does nothing if it did not exist' do
+      allow(File).to receive(:symlink?).with('/var/log/gitlab/gitlab-rails/sidekiq.log') { false }
+
+      expect(chef_run).not_to delete_link('/var/log/gitlab/gitlab-rails/sidekiq.log')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-shell_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-shell_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItc2hlbGxfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-shell_spec.rb
@@ -0,0 +1,265 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-shell' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe 'logrotate settings' do
+    context 'default values' do
+      it_behaves_like 'configured logrotate service', 'gitlab-shell', 'git', 'git'
+    end
+
+    context 'specified username and group' do
+      before do
+        stub_gitlab_rb(
+          user: {
+            username: 'foo',
+            group: 'bar'
+          }
+        )
+      end
+
+      it_behaves_like 'configured logrotate service', 'gitlab-shell', 'foo', 'bar'
+    end
+  end
+
+  it 'defaults the auth_file to be within the user\'s home directory' do
+    stub_gitlab_rb(user: { home: '/tmp/user' })
+    expect(chef_run.node['gitlab']['gitlab-shell']['auth_file']).to eq('/tmp/user/.ssh/authorized_keys')
+  end
+
+  it 'uses custom auth_files set in gitlab.rb' do
+    stub_gitlab_rb(user: { home: '/tmp/user' }, gitlab_shell: { auth_file: '/tmp/authorized_keys' })
+    expect(chef_run.node['gitlab']['gitlab-shell']['auth_file']).to eq('/tmp/authorized_keys')
+  end
+
+  it 'creates authorized_keys file if missing' do
+    stub_gitlab_rb(gitlab_shell: { auth_file: '/tmp/authorized_keys' })
+    expect(chef_run).to create_file_if_missing('/tmp/authorized_keys')
+  end
+
+  context 'with default settings' do
+    it 'create config file in default location with default values' do
+      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
+        hash_including(
+          log_file: '/var/log/gitlab/gitlab-shell/gitlab-shell.log',
+          log_format: "json",
+          migration: { enabled: true, features: [] },
+          gitlab_url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
+          gitlab_relative_path: '',
+          ssl_cert_dir: '/opt/gitlab/embedded/ssl/certs/'
+        )
+      )
+    end
+  end
+
+  context 'with a non-default directory' do
+    before do
+      stub_gitlab_rb(gitlab_shell: {
+                       dir: '/export/gitlab/gitlab-shell',
+                     })
+    end
+
+    it 'create config file in specified location with default values' do
+      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_link_to('/export/gitlab/gitlab-shell/config.yml')
+    end
+  end
+
+  context 'with a non-default log directory' do
+    before do
+      stub_gitlab_rb(gitlab_shell: {
+                       log_directory: '/tmp/log',
+                       git_trace_log_file: '/tmp/log/gitlab-shell-git-trace.log'
+                     })
+    end
+
+    it 'create config file with provided values' do
+      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
+        hash_including(
+          log_file: '/tmp/log/gitlab-shell.log',
+          git_trace_log_file: '/tmp/log/gitlab-shell-git-trace.log'
+        )
+      )
+    end
+  end
+
+  context 'when using the default auth_file location' do
+    before { stub_gitlab_rb(user: { home: '/tmp/user' }) }
+
+    it 'creates the ssh dir in the user\'s home directory' do
+      expect(chef_run).to create_storage_directory('/tmp/user/.ssh').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the config file with the auth_file within user\'s ssh directory' do
+      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
+        hash_including(
+          authorized_keys: '/tmp/user/.ssh/authorized_keys'
+        )
+      )
+    end
+  end
+
+  context 'when using a different location for auth_file' do
+    before { stub_gitlab_rb(user: { home: '/tmp/user' }, gitlab_shell: { auth_file: '/tmp/ssh/authorized_keys' }) }
+
+    it 'creates the ssh dir in the user\'s home directory' do
+      expect(chef_run).to create_storage_directory('/tmp/user/.ssh').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the auth_file\'s parent directory' do
+      expect(chef_run).to create_storage_directory('/tmp/ssh').with(owner: 'git', group: 'git', mode: '0700')
+    end
+
+    it 'creates the config file with the auth_file at the specified location' do
+      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
+        hash_including(
+          authorized_keys: '/tmp/ssh/authorized_keys'
+        )
+      )
+    end
+  end
+
+  context 'when manage-storage-directories is disabled' do
+    before { stub_gitlab_rb(user: { home: '/tmp/user' }, manage_storage_directories: { enable: false }) }
+
+    it 'doesn\'t create the ssh dir in the user\'s home directory' do
+      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/user/.ssh')
+    end
+  end
+
+  context 'with custom settings' do
+    before do
+      stub_gitlab_rb(
+        gitlab_shell: {
+          log_format: 'json',
+          migration: {
+            enabled: true,
+            features: ['discover']
+          }
+        }
+      )
+    end
+
+    it 'creates the config file with custom values' do
+      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
+        hash_including(
+          log_format: 'json',
+          migration: {
+            enabled: true,
+            features: ['discover']
+          }
+        )
+      )
+    end
+
+    context 'migration is disabled (set to false)' do
+      before do
+        stub_gitlab_rb(
+          gitlab_shell: {
+            migration: {
+              enabled: false,
+              features: []
+            }
+          }
+        )
+      end
+
+      it 'creates the config file with migration disabled' do
+        expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
+          hash_including(
+            migration: {
+              enabled: false,
+              features: []
+            }
+          )
+        )
+      end
+    end
+
+    context 'with a non-default workhorse unix socket' do
+      context 'without sockets_directory defined' do
+        before do
+          stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/fake/workhorse/socket' })
+        end
+
+        it 'create config file with provided values' do
+          expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
+            hash_including(
+              gitlab_url: 'http+unix://%2Ffake%2Fworkhorse%2Fsocket',
+              gitlab_relative_path: ''
+            )
+          )
+        end
+      end
+
+      context 'with sockets_directory defined' do
+        before do
+          stub_gitlab_rb(gitlab_workhorse: { 'sockets_directory': '/fake/workhorse/sockets/' })
+        end
+
+        it 'create config file with provided values' do
+          expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
+            hash_including(
+              gitlab_url: 'http+unix://%2Ffake%2Fworkhorse%2Fsockets%2Fsocket',
+              gitlab_relative_path: ''
+            )
+          )
+        end
+      end
+    end
+
+    context 'with a tcp workhorse listener' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'http://example.com/gitlab',
+          gitlab_workhorse: {
+            listen_network: 'tcp',
+            listen_addr: 'localhost:1234'
+          }
+        )
+      end
+
+      it 'create config file with provided values' do
+        expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
+          hash_including(
+            gitlab_url: 'http://localhost:1234/gitlab',
+            gitlab_relative_path: nil
+          )
+        )
+      end
+    end
+
+    context 'with relative path in external_url' do
+      before do
+        stub_gitlab_rb(external_url: 'http://example.com/gitlab')
+      end
+
+      it 'create config file with provided values' do
+        expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
+          hash_including(
+            gitlab_url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
+            gitlab_relative_path: '/gitlab'
+          )
+        )
+      end
+    end
+
+    context 'with internal_api_url specified' do
+      before do
+        stub_gitlab_rb(gitlab_rails: { internal_api_url: 'http://localhost:8080' })
+      end
+
+      it 'create config file with provided values' do
+        expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
+          hash_including(
+            gitlab_url: 'http://localhost:8080',
+            gitlab_relative_path: ''
+          )
+        )
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-workhorse_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-workhorse_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItd29ya2hvcnNlX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-workhorse_spec.rb
@@ -0,0 +1,580 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-workhorse' do
+  let(:node_cpus) { 1 }
+  let(:chef_run) do
+    ChefSpec::SoloRunner.new(step_into: %w(runit_service)) do |node|
+      node.automatic['cpu']['total'] = node_cpus
+    end.converge('gitlab::default')
+  end
+  let(:default_vars) do
+    {
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
+      'HOME' => '/var/opt/gitlab',
+      'PATH' => '/opt/gitlab/bin:/opt/gitlab/embedded/bin:/bin:/usr/bin'
+    }
+  end
+  let(:config_file) { '/var/opt/gitlab/gitlab-workhorse/config.toml' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'by default' do
+    it 'creates a default VERSION file and restarts service' do
+      expect(chef_run).to create_version_file('Create version file for Workhorse').with(
+        version_file_path: '/var/opt/gitlab/gitlab-workhorse/VERSION',
+        version_check_cmd: '/opt/gitlab/embedded/bin/gitlab-workhorse --version'
+      )
+
+      expect(chef_run.version_file('Create version file for Workhorse')).to notify('runit_service[gitlab-workhorse]').to(:restart)
+    end
+
+    it 'includes both authSocket and authBackend arguments' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
+        expect(content).to match(%r(-authSocket /var/opt/gitlab/gitlab-rails/sockets/gitlab.socket))
+        expect(content).to match(%r(-authBackend http://localhost:8080))
+      }
+    end
+
+    it 'does not include alternate document root' do
+      expect(chef_run).to render_file(config_file).with_content { |content|
+        expect(content).not_to match(/alt_document_root/)
+      }
+    end
+
+    it 'does not include shutdown timeout' do
+      expect(chef_run).to render_file(config_file).with_content { |content|
+        expect(content).not_to match(/shutdown_timeout/)
+      }
+    end
+
+    it 'does not include object storage configs' do
+      expect(chef_run).to render_file(config_file).with_content { |content|
+        expect(content).not_to match(/object_storage/)
+      }
+    end
+
+    it 'does not propagate correlation ID' do
+      expect(chef_run).to render_file(config_file).with_content { |content|
+        expect(content).not_to match(/propagateCorrelationID/)
+      }
+    end
+  end
+
+  context 'user and group' do
+    context 'default values' do
+      it_behaves_like "enabled runit service", "gitlab-workhorse", "root", "root"
+      it_behaves_like 'configured logrotate service', 'gitlab-workhorse', 'git', 'git'
+    end
+
+    context 'custom values' do
+      before do
+        stub_gitlab_rb(
+          user: {
+            username: 'foo',
+            group: 'bar'
+          }
+        )
+      end
+
+      it_behaves_like "enabled runit service", "gitlab-workhorse", "root", "root"
+      it_behaves_like 'configured logrotate service', 'gitlab-workhorse', 'foo', 'bar'
+    end
+  end
+
+  context 'with environment variables' do
+    context 'by default' do
+      it 'creates necessary env variable files' do
+        expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-workhorse/env').with_variables(default_vars)
+      end
+
+      context 'when a custom env variable is specified' do
+        before do
+          stub_gitlab_rb(gitlab_workhorse: { env: { 'IAM' => 'CUSTOMVAR' } })
+        end
+
+        it 'creates necessary env variable files' do
+          expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-workhorse/env').with_variables(
+            default_vars.merge(
+              {
+                'IAM' => 'CUSTOMVAR'
+              }
+            )
+          )
+        end
+      end
+    end
+  end
+
+  context 'with alternate document root' do
+    before do
+      stub_gitlab_rb(gitlab_workhorse: { alt_document_root: '/tmp/test' })
+    end
+
+    it 'includes alternate document root setting' do
+      expect(chef_run).to render_file(config_file).with_content { |content|
+        expect(content).to match(%r(alt_document_root = "/tmp/test"))
+      }
+    end
+  end
+
+  context 'with shutdown timeout' do
+    before do
+      stub_gitlab_rb(gitlab_workhorse: { shutdown_timeout: '60s' })
+    end
+
+    it 'includes alternate document root setting' do
+      expect(chef_run).to render_file(config_file).with_content { |content|
+        expect(content).to match(%r(shutdown_timeout = "60s"))
+      }
+    end
+  end
+
+  context 'auth_socket and auth_backend' do
+    context 'with only auth_socket specified' do
+      context "auth_socket set to legacy '' value" do
+        before do
+          stub_gitlab_rb(gitlab_workhorse: { auth_socket: "''" })
+        end
+
+        it 'includes both authSocket and authBackend arguments' do
+          expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
+            expect(content).to match(%r(-authSocket /var/opt/gitlab/gitlab-rails/sockets/gitlab.socket))
+            expect(content).to match(%r(-authBackend http://localhost:8080))
+          }
+        end
+      end
+    end
+
+    context 'with only auth_backend specified' do
+      before do
+        stub_gitlab_rb(gitlab_workhorse: { auth_backend: 'https://test.example.com:8080' })
+      end
+
+      it 'omits authSocket argument' do
+        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
+          expect(content).not_to match(/\-authSocket/)
+          expect(content).to match(%r(-authBackend https://test.example.com:8080))
+        }
+      end
+    end
+
+    context "with nil auth_socket" do
+      before do
+        stub_gitlab_rb(gitlab_workhorse: { auth_socket: nil })
+      end
+
+      it 'includes both authSocket and authBackend arguments' do
+        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
+          expect(content).to match(%r(-authSocket /var/opt/gitlab/gitlab-rails/sockets/gitlab.socket))
+          expect(content).to match(%r(-authBackend http://localhost:8080))
+        }
+      end
+
+      context 'with auth_backend specified' do
+        before do
+          stub_gitlab_rb(gitlab_workhorse: { auth_socket: nil, auth_backend: 'https://test.example.com:8080' })
+        end
+
+        it 'omits authSocket argument' do
+          expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
+            expect(content).not_to match(/\-authSocket/)
+            expect(content).to match(%r(-authBackend https://test.example.com:8080))
+          }
+        end
+      end
+    end
+
+    context 'with auth_backend and auth_socket set' do
+      before do
+        stub_gitlab_rb(gitlab_workhorse: { auth_socket: '/tmp/test.socket', auth_backend: 'https://test.example.com:8080' })
+      end
+
+      it 'includes both authSocket and authBackend arguments' do
+        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
+          expect(content).to match(%r(-authSocket /tmp/test.socket))
+          expect(content).to match(%r(-authBackend https://test.example.com:8080))
+        }
+      end
+    end
+  end
+
+  context 'consolidated object store settings' do
+    include_context 'object storage config'
+
+    context 'with S3 config' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            object_store: {
+              enabled: true,
+              connection: aws_connection_hash,
+              objects: object_config
+            }
+          }
+        )
+      end
+
+      it 'includes S3 credentials' do
+        expect(chef_run).to render_file(config_file).with_content { |content|
+          expect(content).to match(/\[object_storage\]\n  provider = "AWS"\n/m)
+          expect(content).to match(/\[object_storage.s3\]\n  aws_access_key_id = "AKIAKIAKI"\n  aws_secret_access_key = "secret123"\n/m)
+        }
+      end
+    end
+
+    context 'with Azure config' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            object_store: {
+              enabled: true,
+              connection: azure_connection_hash,
+              objects: object_config
+            }
+          }
+        )
+      end
+
+      it 'includes Azure credentials' do
+        expect(chef_run).to render_file(config_file).with_content { |content|
+          expect(content).to match(/\[object_storage\]\n  provider = "AzureRM"\n/m)
+          expect(content).to match(/\[object_storage.azurerm\]\n  azure_storage_account_name = "testaccount"\n  azure_storage_access_key = "1234abcd"\n/m)
+        }
+      end
+    end
+  end
+
+  context 'without api rate limiting' do
+    it 'correctly renders out the workhorse service file' do
+      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiLimit/)
+      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiQueueDuration/)
+      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiQueueLimit/)
+    end
+  end
+
+  context 'with api rate limiting' do
+    before do
+      stub_gitlab_rb(gitlab_workhorse: { api_limit: 3, api_queue_limit: 6, api_queue_duration: '1m' })
+    end
+
+    it 'correctly renders out the workhorse service file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiLimit 3 \\/)
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiQueueDuration 1m \\/)
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiQueueLimit 6 \\/)
+    end
+  end
+
+  context 'without prometheus listen address' do
+    before do
+      stub_gitlab_rb(gitlab_workhorse: { prometheus_listen_addr: '' })
+    end
+
+    it 'correctly renders out the workhorse service file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run")
+      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-prometheusListenAddr/)
+    end
+  end
+
+  context 'with prometheus listen address' do
+    before do
+      stub_gitlab_rb(gitlab_workhorse: { prometheus_listen_addr: ':9229' })
+    end
+
+    it 'correctly renders out the workhorse service file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-prometheusListenAddr :9229 \\/)
+    end
+  end
+
+  context 'without api ci long polling duration defined' do
+    it 'correctly renders out the workhorse service file' do
+      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiCiLongPollingDuration/)
+    end
+  end
+
+  context 'with api ci long polling duration defined' do
+    before do
+      stub_gitlab_rb(gitlab_workhorse: { api_ci_long_polling_duration: "60s" })
+    end
+
+    it 'correctly renders out the workhorse service file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiCiLongPollingDuration 60s/)
+    end
+  end
+
+  context 'with propagate_correlation_id enabled' do
+    before do
+      stub_gitlab_rb(gitlab_workhorse: { propagate_correlation_id: true })
+    end
+
+    it 'correctly renders out the workhorse service file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-propagateCorrelationID/)
+    end
+
+    context 'with trusted_cidrs_for_propagation defined' do
+      before do
+        stub_gitlab_rb(gitlab_workhorse: {
+                         propagate_correlation_id: true,
+                         trusted_cidrs_for_propagation: %w(127.0.0.1/32 192.168.0.1/8)
+                       })
+      end
+
+      it 'correctly renders out the workhorse service file' do
+        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-propagateCorrelationID/)
+      end
+
+      it 'should generate an array in the config file' do
+        expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content { |content|
+          expect(content).to include(%(trusted_cidrs_for_propagation = ["127.0.0.1/32","192.168.0.1/8"]))
+          expect(content).not_to include(%(trusted_cidrs_for_x_forwarded_for))
+        }
+      end
+    end
+
+    context 'with trusted_cidrs_for_propagation and trusted_cidrs_for_x_forwarded_for defined' do
+      before do
+        stub_gitlab_rb(gitlab_workhorse: {
+                         propagate_correlation_id: true,
+                         trusted_cidrs_for_propagation: %w(127.0.0.1/32 192.168.0.1/8),
+                         trusted_cidrs_for_x_forwarded_for: %w(1.2.3.4/16 5.6.7.8/24)
+                       })
+      end
+
+      it 'correctly renders out the workhorse service file' do
+        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-propagateCorrelationID/)
+      end
+
+      it 'should generate arrays in the config file' do
+        expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content { |content|
+          expect(content).to include(%(trusted_cidrs_for_propagation = ["127.0.0.1/32","192.168.0.1/8"]))
+          expect(content).to include(%(trusted_cidrs_for_x_forwarded_for = ["1.2.3.4/16","5.6.7.8/24"]))
+        }
+      end
+    end
+  end
+
+  context 'with log format defined as json' do
+    before do
+      stub_gitlab_rb(gitlab_workhorse: { log_format: "json" })
+    end
+
+    it 'correctly renders out the workhorse service file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-logFormat json/)
+    end
+
+    it 'renders svlogd file which will not prepend timestamp' do
+      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/log/run").with_content(/\-tt/)
+    end
+  end
+
+  context 'with log format defined as text' do
+    before do
+      stub_gitlab_rb(gitlab_workhorse: { log_format: "text" })
+    end
+
+    it 'correctly renders out the workhorse service file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-logFormat text/)
+    end
+
+    it 'renders svlogd file which will prepend timestamp' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/log/run").with_content(/\-tt/)
+    end
+  end
+
+  context 'with default value for working directory' do
+    it 'should generate config file in the correct directory' do
+      expect(chef_run).to render_file(config_file)
+    end
+  end
+
+  context 'with working directory specified' do
+    before do
+      stub_gitlab_rb(gitlab_workhorse: { dir: "/home/random/dir" })
+    end
+    it 'should generate config file in the correct directory' do
+      expect(chef_run).to render_file("/home/random/dir/config.toml")
+    end
+  end
+
+  context 'with default values for redis' do
+    it 'should generate config file' do
+      content_url = 'URL = "unix:/var/opt/gitlab/redis/redis.socket"'
+      expect(chef_run).to render_file(config_file).with_content(content_url)
+      expect(chef_run).not_to render_file(config_file).with_content(/Sentinel/)
+    end
+
+    it 'should pass config file to workhorse' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-config config.toml/)
+    end
+  end
+
+  context 'with host/port/password values for redis specified and socket disabled' do
+    before do
+      stub_gitlab_rb(
+        gitlab_rails: {
+          redis_host: "10.0.15.1",
+          redis_port: "1234",
+          redis_password: 'examplepassword'
+        }
+      )
+    end
+
+    it 'should generate config file with the specified values' do
+      content_url = 'URL = "redis://:examplepassword@10.0.15.1:1234/"'
+      content_password = 'Password = "examplepassword"'
+      content_sentinel = 'Sentinel'
+      content_sentinel_master = 'SentinelMaster'
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_url)
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_password)
+      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_sentinel)
+      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_sentinel_master)
+    end
+  end
+
+  context 'with socket for redis specified' do
+    before do
+      stub_gitlab_rb(gitlab_rails: { redis_socket: "/home/random/path.socket", redis_password: 'examplepassword' })
+    end
+
+    it 'should generate config file with the specified values' do
+      content_url = 'URL = "unix:/home/random/path.socket"'
+      content_password = 'Password = "examplepassword"'
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_url)
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_password)
+      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(/Sentinel/)
+      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(/SentinelMaster/)
+    end
+  end
+
+  context 'with Sentinels specified with default master' do
+    before do
+      stub_gitlab_rb(
+        gitlab_rails: {
+          redis_sentinels: [
+            { 'host' => '127.0.0.1', 'port' => 2637 },
+            { 'host' => '127.0.8.1', 'port' => 1234 }
+          ]
+        }
+      )
+    end
+
+    it 'should generate config file with the specified values' do
+      content = 'Sentinel = ["tcp://127.0.0.1:2637", "tcp://127.0.8.1:1234"]'
+      content_url = 'URL ='
+      content_sentinel_master = 'SentinelMaster = "gitlab-redis"'
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content)
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_sentinel_master)
+      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_url)
+    end
+  end
+
+  context 'with Sentinels and master specified' do
+    before do
+      stub_gitlab_rb(
+        gitlab_rails: {
+          redis_sentinels: [
+            { 'host' => '127.0.0.1', 'port' => 26379 },
+            { 'host' => '127.0.8.1', 'port' => 12345 }
+          ]
+        },
+        redis: {
+          master_name: 'examplemaster',
+          master_password: 'examplepassword'
+        }
+      )
+    end
+
+    it 'should generate config file with the specified values' do
+      content = 'Sentinel = ["tcp://127.0.0.1:26379", "tcp://127.0.8.1:12345"]'
+      content_sentinel_master = 'SentinelMaster = "examplemaster"'
+      content_sentinel_password = 'Password = "examplepassword"'
+      content_url = 'URL ='
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content)
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_sentinel_master)
+      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_sentinel_password)
+      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_url)
+    end
+  end
+
+  context 'image scaler' do
+    context 'with default values' do
+      it 'sets the default maximum file size' do
+        expect(chef_run).to render_file(config_file).with_content { |content|
+          expect(content).to match(/\[image_resizer\]\n  max_scaler_procs = \d+\n  max_filesize = 250000/m)
+        }
+      end
+
+      context 'when reported CPU cores are at least 4' do
+        let(:node_cpus) { 4 }
+
+        it 'sets default max procs to half the number of cores' do
+          expect(chef_run).to render_file(config_file).with_content { |content|
+            expect(content).to match(/\[image_resizer\]\n  max_scaler_procs = 2/m)
+          }
+        end
+      end
+
+      context 'when reported CPU cores are less than 4' do
+        let(:node_cpus) { 3 }
+
+        it 'pins default max procs to 2' do
+          expect(chef_run).to render_file(config_file).with_content { |content|
+            expect(content).to match(/\[image_resizer\]\n  max_scaler_procs = 2/m)
+          }
+        end
+      end
+    end
+
+    context 'with custom values' do
+      before do
+        stub_gitlab_rb(
+          gitlab_workhorse: {
+            image_scaler_max_procs: 5,
+            image_scaler_max_filesize: 1024
+          }
+        )
+      end
+
+      it 'should generate config file with the specified values' do
+        expect(chef_run).to render_file(config_file).with_content { |content|
+          expect(content).to match(/\[image_resizer\]\n  max_scaler_procs = 5\n  max_filesize = 1024/m)
+        }
+      end
+    end
+  end
+
+  context 'with workhorse keywatcher enabled' do
+    before do
+      stub_gitlab_rb(
+        gitlab_workhorse: {
+          workhorse_keywatcher: true,
+        }
+      )
+    end
+
+    it 'should generate redis block in the configuration file' do
+      expect(chef_run).to render_file(config_file).with_content { |content|
+        expect(content).to match(/\[redis\]/m)
+      }
+    end
+  end
+
+  context 'with workhorse keywatcher disabled' do
+    before do
+      stub_gitlab_rb(
+        gitlab_workhorse: {
+          workhorse_keywatcher: false,
+        }
+      )
+    end
+
+    it 'should not generate redis block in the configuration file' do
+      expect(chef_run).to render_file(config_file).with_content { |content|
+        expect(content).not_to match(/\[redis\]/m)
+      }
+    end
+  end
+
+  include_examples "consul service discovery", "gitlab_workhorse", "workhorse"
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/mailroom_spec.rb b/spec/chef/cookbooks/gitlab/recipes/mailroom_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9tYWlscm9vbV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/mailroom_spec.rb
@@ -0,0 +1,291 @@
+#
+# Copyright:: Copyright (c) 2018 GitLab Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef_helper'
+
+RSpec.describe 'gitlab::mailroom' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service templatesymlink)).converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  shared_examples 'renders Microsoft Graph config' do
+    let(:gitlab_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/gitlab.yml') }
+    let(:gitlab_yml_file_content) { ChefSpec::Renderer.new(chef_run, gitlab_yml_template).content }
+    let(:gitlab_yml) { YAML.safe_load(gitlab_yml_file_content, [], [], true, symbolize_names: true) }
+    let(:inbox_options) do
+      {
+        tenant_id: 'MY-TENANT-ID',
+        client_id: '12345',
+        client_secret: 'MY-CLIENT-SECRET'
+      }
+    end
+
+    before do
+      config_sections.each do |config_section|
+        stub_gitlab_rb(
+          gitlab_rails: {
+            "#{config_section}_enabled".to_sym => true,
+            "#{config_section}_inbox_method".to_sym => 'microsoft_graph',
+            "#{config_section}_inbox_options".to_sym => inbox_options
+          }
+        )
+      end
+    end
+
+    it 'renders gitlab.yml with the right data' do
+      config_sections.each do |config_section|
+        expect(gitlab_yml[:production][config_section][:inbox_method]).to eq('microsoft_graph')
+        expect(gitlab_yml[:production][config_section][:inbox_options]).to eq(inbox_options)
+      end
+    end
+  end
+
+  shared_examples 'configured sidekiq delivery method' do
+    let(:gitlab_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/gitlab.yml') }
+    let(:gitlab_yml_file_content) { ChefSpec::Renderer.new(chef_run, gitlab_yml_template).content }
+    let(:gitlab_yml) { YAML.safe_load(gitlab_yml_file_content, [], [], true, symbolize_names: true) }
+
+    before do
+      stub_gitlab_rb(
+        gitlab_rails: config_sections.each_with_object({}) do |config_section, memo|
+          memo.merge!(
+            "#{config_section}_enabled".to_sym => true,
+            "#{config_section}_delivery_method".to_sym => 'sidekiq'
+          )
+        end
+      )
+    end
+
+    it 'renders gitlab.yml with the right data' do
+      config_sections.each do |config_section|
+        expect(gitlab_yml[:production][config_section][:delivery_method]).to eq('sidekiq')
+      end
+    end
+  end
+
+  shared_examples 'configured webhook delivery method' do
+    let(:gitlab_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/gitlab.yml') }
+    let(:gitlab_yml_file_content) { ChefSpec::Renderer.new(chef_run, gitlab_yml_template).content }
+    let(:gitlab_yml) { YAML.safe_load(gitlab_yml_file_content, [], [], true, symbolize_names: true) }
+
+    before do
+      stub_gitlab_rb(
+        external_url: 'http://localhost/gitlab/', # Mind the last "/" character
+        gitlab_rails: config_sections.each_with_object({}) do |config_section, memo|
+          memo.merge!(
+            "#{config_section}_enabled".to_sym => true,
+            "#{config_section}_delivery_method".to_sym => 'webhook'
+          )
+        end
+      )
+    end
+
+    it 'renders gitlab.yml with the right data' do
+      config_sections.each do |config_section|
+        expect(gitlab_yml[:production][config_section][:delivery_method]).to eq('webhook')
+        expect(gitlab_yml[:production][config_section][:secret_file]).to eq(".gitlab_#{config_section}_secret")
+        expect(gitlab_yml[:production][config_section][:gitlab_url]).to eq("http://localhost/gitlab")
+      end
+    end
+
+    it 'triggers notifications' do
+      config_sections.each do |config_section|
+        templatesymlink = chef_run.templatesymlink("Create a gitlab_#{config_section}_secret and create a symlink to Rails root")
+        expect(templatesymlink).to notify('runit_service[mailroom]').to(:restart).delayed
+      end
+    end
+
+    context 'auth token is not specified' do
+      before do
+        allow(SecretsHelper).to receive(:generate_base64).with(32).and_return("a" * 32)
+      end
+
+      it 'renders the correct node attribute with auto-generated auth token' do
+        config_sections.each do |config_section|
+          expect(chef_run).to create_templatesymlink("Create a gitlab_#{config_section}_secret and create a symlink to Rails root").with(
+            owner: 'root',
+            group: 'root',
+            mode: '0644'
+          ).with_variables(
+            a_hash_including(
+              secret_token: "a" * 32
+            )
+          )
+        end
+      end
+    end
+
+    context 'auth token is set' do
+      let(:auth_token) { SecureRandom.base64(32) }
+
+      before do
+        stub_gitlab_rb(
+          external_url: 'http://localhost/gitlab/',
+          gitlab_rails: config_sections.each_with_object({}) do |config_section, memo|
+            memo.merge!(
+              "#{config_section}_enabled".to_sym => true,
+              "#{config_section}_delivery_method".to_sym => 'webhook',
+              "#{config_section}_auth_token".to_sym => "#{auth_token}-#{config_section}"
+            )
+          end
+        )
+      end
+
+      it 'renders the correct node attribute' do
+        config_sections.each do |config_section|
+          expect(chef_run).to create_templatesymlink("Create a gitlab_#{config_section}_secret and create a symlink to Rails root").with(
+            owner: 'root',
+            group: 'root',
+            mode: '0644'
+          ).with_variables(
+            a_hash_including(
+              secret_token: "#{auth_token}-#{config_section}"
+            )
+          )
+        end
+      end
+    end
+  end
+
+  describe 'when disabled' do
+    it_behaves_like 'disabled runit service', 'mailroom'
+  end
+
+  describe 'when enabled' do
+    context 'when only service_desk_email enabled' do
+      let(:config_sections) { %i[service_desk_email] }
+
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            service_desk_email_enabled: true
+          }
+        )
+      end
+
+      it_behaves_like 'enabled runit service', 'mailroom', 'root', 'root'
+      it_behaves_like 'configured logrotate service', 'mailroom', 'git', 'git'
+      it_behaves_like 'configured sidekiq delivery method'
+    end
+
+    context 'when both service_desk_email and incoming_email enabled' do
+      let(:config_sections) { %i[incoming_email service_desk_email] }
+
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            incoming_email_enabled: true,
+            service_desk_email_enabled: true
+          }
+        )
+      end
+
+      it_behaves_like 'enabled runit service', 'mailroom', 'root', 'root'
+      it_behaves_like 'configured logrotate service', 'mailroom', 'git', 'git'
+      it_behaves_like 'configured sidekiq delivery method'
+    end
+
+    context 'default values' do
+      let(:config_sections) { %i[incoming_email] }
+
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            incoming_email_enabled: true
+          }
+        )
+      end
+
+      it_behaves_like 'enabled runit service', 'mailroom', 'root', 'root'
+      it_behaves_like 'configured logrotate service', 'mailroom', 'git', 'git'
+
+      it 'uses --log-exit-as plain' do
+        expect(chef_run).to render_file("/opt/gitlab/sv/mailroom/run").with_content(/\-\-log\-exit\-as plain/)
+      end
+    end
+
+    context 'custom values' do
+      let(:config_sections) { %i[incoming_email] }
+
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            incoming_email_enabled: true
+          },
+          user: {
+            username: 'foo',
+            group: 'bar'
+          }
+        )
+      end
+
+      it_behaves_like 'enabled runit service', 'mailroom', 'root', 'root'
+      it_behaves_like 'configured logrotate service', 'mailroom', 'foo', 'bar'
+    end
+
+    context 'incoming email with Microsoft Graph' do
+      let(:config_sections) { %i[incoming_email] }
+
+      it_behaves_like 'renders Microsoft Graph config'
+    end
+
+    context 'Service Desk email with Microsoft Graph' do
+      let(:config_sections) { %i[service_desk_email] }
+
+      it_behaves_like 'renders Microsoft Graph config'
+    end
+
+    context 'Incoming Email webhook delivery method' do
+      let(:config_sections) { %i[incoming_email] }
+
+      it_behaves_like 'configured webhook delivery method'
+    end
+
+    context 'Service Desk Email webhook delivery method' do
+      let(:config_sections) { %i[incoming_email] }
+
+      it_behaves_like 'configured webhook delivery method'
+    end
+
+    context 'Both Incoming Email and Service Desk Email webhook delivery method' do
+      let(:config_sections) { %i[incoming_email service_desk_email] }
+
+      it_behaves_like 'configured webhook delivery method'
+    end
+  end
+
+  context 'with specified exit_log_format' do
+    before do
+      stub_gitlab_rb(
+        gitlab_rails: {
+          incoming_email_enabled: true
+        },
+        mailroom: {
+          exit_log_format: "json"
+        }
+      )
+    end
+
+    it 'correctly passes the --log-exit-as ' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/mailroom/run").with_content(/\-\-log\-exit\-as json/)
+    end
+
+    it_behaves_like 'enabled runit service', 'mailroom', 'root', 'root'
+    it_behaves_like 'configured logrotate service', 'mailroom', 'git', 'git'
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/nginx_spec.rb b/spec/chef/cookbooks/gitlab/recipes/nginx_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9uZ2lueF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/nginx_spec.rb
@@ -0,0 +1,698 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::nginx' do
+  let(:chef_runner) do
+    ChefSpec::SoloRunner.new(step_into: %w(runit_service)) do |node|
+      node.normal['gitlab']['nginx']['enable'] = true
+      node.normal['package']['install-dir'] = '/opt/gitlab'
+    end
+  end
+
+  let(:chef_run) do
+    chef_runner.converge('gitlab::config', 'gitlab::nginx')
+  end
+
+  let(:gitlab_http_config) { '/var/opt/gitlab/nginx/conf/gitlab-http.conf' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow(Gitlab).to receive(:[]).with('node') { chef_runner.node }
+
+    # generate a random number to use as error code
+    @code = rand(1000)
+    @nginx_errors = {
+      @code => {
+        'title' => 'TEST TITLE',
+        'header' => 'TEST HEADER',
+        'message' => 'TEST MESSAGE'
+      }
+    }
+  end
+
+  it_behaves_like 'enabled runit service', 'nginx', 'root', 'root'
+
+  it 'creates a custom error_page entry when a custom error is defined' do
+    allow(Gitlab).to receive(:[]).with('nginx').and_return({ 'custom_error_pages' => @nginx_errors })
+
+    expect(chef_run).to render_file(gitlab_http_config).with_content { |content|
+      expect(content).to include("error_page #{@code} /#{@code}-custom.html;")
+    }
+  end
+
+  it 'renders an error template when a custom error is defined' do
+    chef_runner.node.normal['gitlab']['nginx']['custom_error_pages'] = @nginx_errors
+    expect(chef_run).to render_file("/opt/gitlab/embedded/service/gitlab-rails/public/#{@code}-custom.html").with_content { |content|
+      expect(content).to include("TEST MESSAGE")
+    }
+  end
+
+  it 'creates a standard error_page entry when no custom error is defined' do
+    chef_runner.node.normal['gitlab']['nginx'].delete('custom_error_pages')
+    expect(chef_run).to render_file(gitlab_http_config).with_content { |content|
+      expect(content).to include("error_page 404 /404.html;")
+    }
+  end
+
+  it 'enables the proxy_intercept_errors option when custom_error_pages is defined' do
+    chef_runner.node.normal['gitlab']['nginx']['custom_error_pages'] = @nginx_errors
+    expect(chef_run).to render_file(gitlab_http_config).with_content { |content|
+      expect(content).to include("proxy_intercept_errors on")
+    }
+  end
+
+  it 'uses the default proxy_intercept_errors option when custom_error_pages is not defined' do
+    chef_runner.node.normal['gitlab']['nginx'].delete('custom_error_pages')
+    expect(chef_run).to render_file(gitlab_http_config).with_content { |content|
+      expect(content).not_to include("proxy_intercept_errors")
+    }
+  end
+end
+
+RSpec.describe 'nginx' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  subject { chef_run }
+
+  let(:gitlab_http_config) { '/var/opt/gitlab/nginx/conf/gitlab-http.conf' }
+  let(:nginx_status_config) { /include \/var\/opt\/gitlab\/nginx\/conf\/nginx-status\.conf;/ }
+
+  let(:basic_nginx_headers) do
+    {
+      "Host" => "$http_host",
+      "X-Real-IP" => "$remote_addr",
+      "X-Forwarded-Proto" => "http",
+      "X-Forwarded-For" => "$proxy_add_x_forwarded_for"
+    }
+  end
+
+  let(:http_conf) do
+    {
+      "gitlab" => "/var/opt/gitlab/nginx/conf/gitlab-http.conf",
+      "mattermost" => "/var/opt/gitlab/nginx/conf/gitlab-mattermost-http.conf",
+      "registry" => "/var/opt/gitlab/nginx/conf/gitlab-registry.conf",
+      "pages" => "/var/opt/gitlab/nginx/conf/gitlab-pages.conf",
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when http external urls are being used' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'http://localhost',
+        mattermost_external_url: 'http://mattermost.localhost',
+        registry_external_url: 'http://registry.localhost',
+        pages_external_url: 'http://pages.localhost'
+      )
+    end
+
+    it 'properly sets the default nginx proxy headers' do
+      expect(chef_run.node['gitlab']['nginx']['proxy_set_headers']).to eql(nginx_headers({
+                                                                                           "Host" => "$http_host_with_default",
+                                                                                           "Upgrade" => "$http_upgrade",
+                                                                                           "Connection" => "$connection_upgrade"
+                                                                                         }))
+      expect(chef_run.node['gitlab']['registry-nginx']['proxy_set_headers']).to eql(basic_nginx_headers)
+      expect(chef_run.node['gitlab']['mattermost-nginx']['proxy_set_headers']).to eql(nginx_headers({
+                                                                                                      "X-Frame-Options" => "SAMEORIGIN",
+                                                                                                      "Upgrade" => "$http_upgrade",
+                                                                                                      "Connection" => "$connection_upgrade"
+                                                                                                    }))
+      expect(chef_run.node['gitlab']['pages-nginx']['proxy_set_headers']).to eql(basic_nginx_headers)
+    end
+
+    it 'supports overriding default nginx headers' do
+      expect_headers = nginx_headers({ "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp" })
+      stub_gitlab_rb(
+        "nginx" => { proxy_set_headers: { "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp" } },
+        "mattermost_nginx" => { proxy_set_headers: { "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp" } },
+        "registry_nginx" => { proxy_set_headers: { "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp" } }
+      )
+
+      expect(chef_run.node['gitlab']['nginx']['proxy_set_headers']).to include(expect_headers)
+      expect(chef_run.node['gitlab']['mattermost-nginx']['proxy_set_headers']).to include(expect_headers)
+      expect(chef_run.node['gitlab']['registry-nginx']['proxy_set_headers']).to include(expect_headers)
+    end
+  end
+
+  context 'when https external urls are being used' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'https://localhost',
+        mattermost_external_url: 'https://mattermost.localhost',
+        registry_external_url: 'https://registry.localhost',
+        pages_external_url: 'https://pages.localhost'
+      )
+    end
+
+    it 'properly sets the default nginx proxy ssl forward headers' do
+      expect(chef_run.node['gitlab']['nginx']['proxy_set_headers']).to eql(nginx_headers({
+                                                                                           "Host" => "$http_host_with_default",
+                                                                                           "X-Forwarded-Proto" => "https",
+                                                                                           "X-Forwarded-Ssl" => "on",
+                                                                                           "Upgrade" => "$http_upgrade",
+                                                                                           "Connection" => "$connection_upgrade"
+                                                                                         }))
+
+      expect(chef_run.node['gitlab']['registry-nginx']['proxy_set_headers']).to eql(nginx_headers({
+                                                                                                    "X-Forwarded-Proto" => "https",
+                                                                                                    "X-Forwarded-Ssl" => "on"
+                                                                                                  }))
+
+      expect(chef_run.node['gitlab']['mattermost-nginx']['proxy_set_headers']).to eql(nginx_headers({
+                                                                                                      "X-Forwarded-Proto" => "https",
+                                                                                                      "X-Forwarded-Ssl" => "on",
+                                                                                                      "X-Frame-Options" => "SAMEORIGIN",
+                                                                                                      "Upgrade" => "$http_upgrade",
+                                                                                                      "Connection" => "$connection_upgrade"
+                                                                                                    }))
+
+      expect(chef_run.node['gitlab']['pages-nginx']['proxy_set_headers']).to eql(nginx_headers({
+                                                                                                 "X-Forwarded-Proto" => "https",
+                                                                                                 "X-Forwarded-Ssl" => "on"
+                                                                                               }))
+    end
+
+    it 'supports overriding default nginx headers' do
+      expect_headers = nginx_headers({ "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp", "X-Forwarded-Ssl" => "on", 'Connection' => 'close' })
+      set_headers = { "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp", 'Connection' => 'close' }
+      stub_gitlab_rb(
+        "nginx" => { proxy_set_headers: set_headers },
+        "mattermost_nginx" => { proxy_set_headers: set_headers },
+        "registry_nginx" => { proxy_set_headers: set_headers },
+        "pages_nginx" => { proxy_set_headers: set_headers }
+      )
+
+      expect(chef_run.node['gitlab']['nginx']['proxy_set_headers']).to include(expect_headers)
+      expect(chef_run.node['gitlab']['mattermost-nginx']['proxy_set_headers']).to include(expect_headers)
+      expect(chef_run.node['gitlab']['registry-nginx']['proxy_set_headers']).to include(expect_headers)
+      expect(chef_run.node['gitlab']['pages-nginx']['proxy_set_headers']).to include(expect_headers)
+    end
+
+    it 'disables Connection header' do
+      expect_headers = nginx_headers({ "Host" => "nohost.example.com", "X-Forwarded-Proto" => "https", "X-Forwarded-Ssl" => "on" })
+      set_headers = { "Host" => "nohost.example.com", "Connection" => nil }
+      stub_gitlab_rb(
+        "nginx" => { proxy_set_headers: set_headers },
+        "mattermost_nginx" => { proxy_set_headers: set_headers },
+        "registry_nginx" => { proxy_set_headers: set_headers },
+        "pages_nginx" => { proxy_set_headers: set_headers }
+      )
+
+      expect(chef_run.node['gitlab']['nginx']['proxy_set_headers']).to include(expect_headers)
+      expect(chef_run.node['gitlab']['mattermost-nginx']['proxy_set_headers']).to include(expect_headers)
+      expect(chef_run.node['gitlab']['registry-nginx']['proxy_set_headers']).to include(expect_headers)
+      expect(chef_run.node['gitlab']['pages-nginx']['proxy_set_headers']).to include(expect_headers)
+    end
+
+    it 'does not set ssl_client_certificate by default' do
+      http_conf.each_value do |conf|
+        expect(chef_run).to render_file(conf).with_content { |content|
+          expect(content).not_to include("ssl_client_certificate")
+        }
+      end
+    end
+
+    it 'does not set ssl_verify_client by default' do
+      http_conf.each_value do |conf|
+        expect(chef_run).to render_file(conf).with_content { |content|
+          expect(content).not_to include("ssl_verify_client")
+        }
+      end
+    end
+
+    it 'does not set ssl_verify_depth by default' do
+      http_conf.each_value do |conf|
+        expect(chef_run).to render_file(conf).with_content { |content|
+          expect(content).not_to include("ssl_verify_depth")
+        }
+      end
+    end
+
+    it 'sets the default ssl_verify_depth when ssl_verify_client is defined' do
+      verify_client = { "ssl_verify_client" => "on" }
+      stub_gitlab_rb(
+        "nginx" => verify_client,
+        "mattermost_nginx" => verify_client,
+        "registry_nginx" => verify_client,
+        "pages_nginx" => verify_client
+      )
+      chef_run.converge('gitlab::default')
+      http_conf.each_value do |conf|
+        expect(chef_run).to render_file(conf).with_content { |content|
+          expect(content).to include("ssl_verify_depth 1")
+        }
+      end
+    end
+
+    it 'applies nginx verify client settings to gitlab-http' do
+      stub_gitlab_rb("nginx" => {
+                       "ssl_client_certificate" => "/etc/gitlab/ssl/gitlab-http-ca.crt",
+                       "ssl_verify_client" => "on",
+                       "ssl_verify_depth" => "2",
+                     })
+      chef_run.converge('gitlab::default')
+      expect(chef_run).to render_file(http_conf['gitlab']).with_content { |content|
+        expect(content).to include("ssl_client_certificate /etc/gitlab/ssl/gitlab-http-ca.crt")
+        expect(content).to include("ssl_verify_client on")
+        expect(content).to include("ssl_verify_depth 2")
+      }
+    end
+
+    it 'applies nginx request_buffering path regex' do
+      expect(chef_run).to render_file(http_conf['gitlab']).with_content { |content|
+        expect(content).to include("location ~ (/api/v\\d/jobs/\\d+/artifacts$|\\.git/git-receive-pack$|\\.git/gitlab-lfs/objects|\\.git/info/lfs/objects/batch$)")
+      }
+    end
+
+    it 'applies mattermost_nginx verify client settings to gitlab-mattermost-http' do
+      stub_gitlab_rb("mattermost_nginx" => {
+                       "ssl_client_certificate" => "/etc/gitlab/ssl/gitlab-mattermost-http-ca.crt",
+                       "ssl_verify_client" => "on",
+                       "ssl_verify_depth" => "3",
+                     })
+      chef_run.converge('gitlab::default')
+      expect(chef_run).to render_file(http_conf['mattermost']).with_content { |content|
+        expect(content).to include("ssl_client_certificate /etc/gitlab/ssl/gitlab-mattermost-http-ca.crt")
+        expect(content).to include("ssl_verify_client on")
+        expect(content).to include("ssl_verify_depth 3")
+      }
+    end
+
+    it 'applies registry_nginx verify client settings to gitlab-registry' do
+      stub_gitlab_rb("registry_nginx" => {
+                       "ssl_client_certificate" => "/etc/gitlab/ssl/gitlab-registry-ca.crt",
+                       "ssl_verify_client" => "off",
+                       "ssl_verify_depth" => "5",
+                     })
+      chef_run.converge('gitlab::default')
+      expect(chef_run).to render_file(http_conf['registry']).with_content { |content|
+        expect(content).to include("ssl_client_certificate /etc/gitlab/ssl/gitlab-registry-ca.crt")
+        expect(content).to include("ssl_verify_client off")
+        expect(content).to include("ssl_verify_depth 5")
+      }
+    end
+
+    it 'applies pages_nginx verify client settings to gitlab-pages' do
+      stub_gitlab_rb("pages_nginx" => {
+                       "ssl_client_certificate" => "/etc/gitlab/ssl/gitlab-pages-ca.crt",
+                       "ssl_verify_client" => "on",
+                       "ssl_verify_depth" => "7",
+                     })
+      chef_run.converge('gitlab::default')
+      expect(chef_run).to render_file(http_conf['pages']).with_content { |content|
+        expect(content).to include("ssl_client_certificate /etc/gitlab/ssl/gitlab-pages-ca.crt")
+        expect(content).to include("ssl_verify_client on")
+        expect(content).to include("ssl_verify_depth 7")
+      }
+    end
+  end
+
+  context 'when is enabled' do
+    it 'enables nginx status by default' do
+      expect(chef_run.node['gitlab']['nginx']['status']).to eql({
+                                                                  "enable" => true,
+                                                                  "listen_addresses" => ["*"],
+                                                                  "fqdn" => "localhost",
+                                                                  "port" => 8060,
+                                                                  "vts_enable" => true,
+                                                                  "options" => {
+                                                                    "server_tokens" => "off",
+                                                                    "access_log" => "off",
+                                                                    "allow" => "127.0.0.1",
+                                                                    "deny" => "all"
+                                                                  }
+                                                                })
+      expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/nginx.conf').with_content(nginx_status_config)
+    end
+
+    it "supports overrading nginx status default configuration" do
+      custom_nginx_status_config = {
+        "enable" => true,
+        "listen_addresses" => ["127.0.0.1"],
+        "fqdn" => "dev.example.com",
+        "port" => 9999,
+        "vts_enable" => true,
+        "options" => {
+          "server_tokens" => "off",
+          "access_log" => "on",
+          "allow" => "127.0.0.1",
+          "deny" => "all"
+        }
+      }
+
+      stub_gitlab_rb("nginx" => {
+                       "status" => custom_nginx_status_config
+                     })
+
+      chef_run.converge('gitlab::default')
+
+      expect(chef_run.node['gitlab']['nginx']['status']).to eql(custom_nginx_status_config)
+    end
+
+    it "will not load the nginx status config if nginx status is disabled" do
+      stub_gitlab_rb("nginx" => { "status" => { "enable" => false } })
+      expect(chef_run).not_to render_file('/var/opt/gitlab/nginx/conf/nginx.conf').with_content(nginx_status_config)
+    end
+
+    it 'defaults to redirect_http_to_https off' do
+      expect(chef_run.node['gitlab']['nginx']['redirect_http_to_https']).to be false
+      expect(chef_run).to render_file(gitlab_http_config).with_content { |content|
+        expect(content).not_to include('return 301 https://fauxhai.local:80$request_uri;')
+      }
+    end
+
+    it 'enables redirect when redirect_http_to_https is true' do
+      stub_gitlab_rb(nginx: { listen_https: true, redirect_http_to_https: true })
+      expect(chef_run.node['gitlab']['nginx']['redirect_http_to_https']).to be true
+      expect(chef_run).to render_file(gitlab_http_config).with_content('return 301 https://fauxhai.local:80$request_uri;')
+    end
+
+    context 'when smartcard authentication is enabled' do
+      let(:gitlab_smartcard_http_config) { '/var/opt/gitlab/nginx/conf/gitlab-smartcard-http.conf' }
+
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            smartcard_enabled: true
+          },
+          nginx: { listen_https: true }
+        )
+      end
+
+      it 'listens on a separate port' do
+        expect(chef_run).to render_file(gitlab_smartcard_http_config).with_content { |content|
+          expect(content).to include('server_name fauxhai.local;')
+          expect(content).to include('listen *:3444 ssl http2;')
+        }
+      end
+
+      it 'requires client side certificate' do
+        expect(chef_run).to render_file(gitlab_smartcard_http_config).with_content { |content|
+          expect(content).to include('ssl_client_certificate /etc/gitlab/ssl/CA.pem')
+          expect(content).to include('ssl_verify_client on')
+          expect(content).to include('ssl_verify_depth 2')
+        }
+      end
+
+      it 'forwards client side certificate in header' do
+        expect(chef_run).to render_file(gitlab_smartcard_http_config).with_content('proxy_set_header X-SSL-Client-Certificate')
+      end
+
+      context 'when smartcard_client_certificate_required_host is set' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              smartcard_enabled: true,
+              smartcard_client_certificate_required_host: 'smartcard.fauxhai.local'
+            },
+            nginx: { listen_https: true }
+          )
+        end
+
+        it 'sets smartcard nginx server name' do
+          expect(chef_run).to render_file(gitlab_smartcard_http_config).with_content { |content|
+            expect(content).to include('server_name smartcard.fauxhai.local;')
+            expect(content).to include('listen *:3444 ssl http2;')
+          }
+        end
+      end
+    end
+
+    context 'when smartcard authentication is disabled' do
+      let(:gitlab_smartcard_http_config) { '/var/opt/gitlab/nginx/conf/gitlab-smartcard-http.conf' }
+
+      before do
+        stub_gitlab_rb(gitlab_rails: { smartcard_enabled: false })
+      end
+
+      it 'should not add the gitlab smartcard config' do
+        expect(chef_run).not_to render_file(gitlab_smartcard_http_config)
+      end
+    end
+  end
+
+  context 'when is disabled' do
+    it 'should not add the nginx status config' do
+      stub_gitlab_rb("nginx" => { "enable" => false })
+      expect(chef_run).not_to render_file('/var/opt/gitlab/nginx/conf/nginx.conf').with_content(nginx_status_config)
+    end
+  end
+
+  context 'when grafana is enabled' do
+    before do
+      stub_gitlab_rb(
+        grafana: { enable: true }
+      )
+    end
+
+    it 'applies nginx grafana proxy' do
+      expect(chef_run).to render_file(http_conf['gitlab']).with_content { |content|
+        expect(content).to include('location /-/grafana/ {')
+        expect(content).to include('proxy_pass http://localhost:3000/;')
+      }
+    end
+  end
+
+  context 'when KAS is enabled' do
+    before do
+      stub_gitlab_rb(
+        gitlab_kas: { enable: true }
+      )
+    end
+
+    it 'applies nginx KAS proxy' do
+      expect(chef_run).to render_file(http_conf['gitlab']).with_content { |content|
+        expect(content).to include('location = /-/kubernetes-agent/ {')
+        expect(content).to include('proxy_pass http://localhost:8150/;')
+
+        expect(content).to include('location /-/kubernetes-agent/k8s-proxy/ {')
+        expect(content).to include('proxy_pass http://localhost:8154/;')
+      }
+    end
+  end
+
+  context 'when hsts is disabled' do
+    before do
+      stub_gitlab_rb(nginx: { hsts_max_age: 0 })
+    end
+    it { is_expected.not_to render_file(gitlab_http_config).with_content(/add_header Strict-Transport-Security/) }
+  end
+
+  it { is_expected.to render_file(gitlab_http_config).with_content(/add_header Strict-Transport-Security "max-age=63072000";/) }
+
+  context 'when referrer_policy is disabled' do
+    before do
+      stub_gitlab_rb(nginx: { referrer_policy: false })
+    end
+
+    it { is_expected.not_to render_file(gitlab_http_config).with_content(/add_header Referrer-Policy/) }
+  end
+
+  context 'when referrer_policy is set to origin' do
+    before do
+      stub_gitlab_rb(nginx: { referrer_policy: 'origin' })
+    end
+
+    it { is_expected.to render_file(gitlab_http_config).with_content(/add_header Referrer-Policy origin;/) }
+  end
+
+  it { is_expected.to render_file(gitlab_http_config).with_content(/add_header Referrer-Policy strict-origin-when-cross-origin;/) }
+
+  context 'when gzip is disabled' do
+    before do
+      stub_gitlab_rb(nginx: { gzip_enabled: false })
+    end
+    it { is_expected.to render_file(gitlab_http_config).with_content(/gzip off;/) }
+  end
+
+  it { is_expected.to render_file(gitlab_http_config).with_content(/gzip on;/) }
+
+  context 'when include_subdomains is enabled' do
+    before do
+      stub_gitlab_rb(nginx: { hsts_include_subdomains: true })
+    end
+
+    it { is_expected.to render_file(gitlab_http_config).with_content(/add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";/) }
+  end
+
+  context 'when max-age is set to 10' do
+    before do
+      stub_gitlab_rb(nginx: { hsts_max_age: 10 })
+    end
+
+    it { is_expected.to render_file(gitlab_http_config).with_content(/"max-age=10[^"]*"/) }
+  end
+
+  context 'when error log level is set to debug' do
+    before do
+      stub_gitlab_rb(nginx: { error_log_level: 'debug' })
+    end
+    it { is_expected.to render_file(gitlab_http_config).with_content(/error_log   \/var\/log\/gitlab\/nginx\/gitlab_error.log debug;/) }
+  end
+
+  it { is_expected.to render_file(gitlab_http_config).with_content(/error_log   \/var\/log\/gitlab\/nginx\/gitlab_error.log error;/) }
+
+  context 'when NGINX RealIP module is configured' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'https://localhost',
+        mattermost_external_url: 'https://mattermost.localhost',
+        registry_external_url: 'https://registry.localhost',
+        pages_external_url: 'https://pages.localhost'
+      )
+    end
+
+    context 'when real_ip_header is configured' do
+      before do
+        stub_gitlab_rb(
+          nginx: { real_ip_header: 'X-FAKE' },
+          mattermost_nginx: { real_ip_header: 'X-FAKE' },
+          registry_nginx: { real_ip_header: 'X-FAKE' },
+          pages_nginx: { real_ip_header: 'X-FAKE' }
+        )
+      end
+
+      it 'populates all config with real_ip_header' do
+        http_conf.each_value do |conf|
+          expect(chef_run).to render_file(conf).with_content(/real_ip_header X-FAKE/)
+        end
+      end
+    end
+
+    context 'when real_ip_recursive is configured' do
+      before do
+        stub_gitlab_rb(
+          nginx: { real_ip_recursive: 'On' },
+          mattermost_nginx: { real_ip_recursive: 'On' },
+          registry_nginx: { real_ip_recursive: 'On' },
+          pages_nginx: { real_ip_recursive: 'On' }
+        )
+      end
+
+      it 'populates all config with real_up_recursive' do
+        http_conf.each_value do |conf|
+          expect(chef_run).to render_file(conf).with_content(/real_ip_recursive On/)
+        end
+      end
+    end
+
+    context 'when real_ip_trusted_addresses is configured' do
+      before do
+        stub_gitlab_rb(
+          nginx: { real_ip_trusted_addresses: %w(one two three) },
+          mattermost_nginx: { real_ip_trusted_addresses: %w(one two three) },
+          registry_nginx: { real_ip_trusted_addresses: %w(one two three) },
+          pages_nginx: { real_ip_trusted_addresses: %w(one two three) }
+        )
+      end
+
+      it 'populates all config with all items for real_ip_trusted_addresses' do
+        http_conf.each_value do |conf|
+          expect(chef_run).to render_file(conf).with_content { |content|
+            expect(content).to match(/set_real_ip_from one/)
+            expect(content).to match(/set_real_ip_from two/)
+            expect(content).to match(/set_real_ip_from three/)
+          }
+        end
+      end
+    end
+
+    context 'when proxy_protocol is enabled' do
+      before do
+        stub_gitlab_rb(
+          nginx: { proxy_protocol: true },
+          mattermost_nginx: { proxy_protocol: true },
+          registry_nginx: { proxy_protocol: true },
+          pages_nginx: { proxy_protocol: true }
+        )
+      end
+
+      it 'applies nginx proxy_protocol settings' do
+        http_conf.each_value do |conf|
+          expect(chef_run).to render_file(conf).with_content { |content|
+            expect(content).to match(/listen .*:\d+ proxy_protocol/)
+            expect(content).to include('real_ip_header proxy_protocol;')
+            expect(content).to include('proxy_set_header X-Real-IP $proxy_protocol_addr;')
+            expect(content).to include('proxy_set_header X-Forwarded-For $proxy_protocol_addr;')
+          }
+        end
+      end
+    end
+
+    it 'does not set proxy_protocol settings by default' do
+      http_conf.each_value do |conf|
+        expect(chef_run).to render_file(conf).with_content { |content|
+          expect(content).not_to match(/listen .*:\d+ proxy_protocol/)
+          expect(content).not_to include('real_ip_header proxy_protocol;')
+          expect(content).not_to include('proxy_set_header X-Real-IP $proxy_protocol_addr;')
+          expect(content).not_to include('proxy_set_header X-Forwarded-For $proxy_protocol_addr;')
+        }
+      end
+    end
+  end
+
+  context 'for proxy_custom_buffer_size' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'https://localhost',
+        mattermost_external_url: 'https://mattermost.localhost',
+        pages_external_url: 'https://pages.localhost'
+      )
+    end
+
+    context 'when proxy_custom_buffer_size is set' do
+      before do
+        stub_gitlab_rb(
+          nginx: { proxy_custom_buffer_size: '42k' },
+          mattermost_nginx: { proxy_custom_buffer_size: '42k' },
+          pages_nginx: { proxy_custom_buffer_size: '42k' }
+        )
+      end
+
+      it 'applies nginx proxy_custom_buffer_size settings' do
+        ['gitlab', 'mattermost', 'pages'].each do |conf|
+          expect(chef_run).to render_file(http_conf[conf]).with_content { |content|
+            expect(content).to include('proxy_buffers 8 42k;')
+            expect(content).to include('proxy_buffer_size 42k;')
+          }
+        end
+      end
+    end
+
+    it 'does not set proxy_custom_buffer_size by default' do
+      ['gitlab', 'mattermost', 'pages'].each do |conf|
+        expect(chef_run).to render_file(http_conf[conf]).with_content { |content|
+          expect(content).not_to include('proxy_buffers 8 42k;')
+          expect(content).not_to include('proxy_buffer_size 42k;')
+        }
+      end
+    end
+  end
+
+  include_examples "consul service discovery", "nginx", "nginx"
+
+  describe 'logrotate settings' do
+    context 'default values' do
+      it_behaves_like 'configured logrotate service', 'nginx', 'root', 'root'
+    end
+
+    context 'specified username and group' do
+      before do
+        stub_gitlab_rb(
+          web_server: {
+            username: 'foo',
+            group: 'bar'
+          }
+        )
+      end
+
+      it_behaves_like 'configured logrotate service', 'nginx', 'root', 'root'
+    end
+  end
+
+  def nginx_headers(additional_headers)
+    basic_nginx_headers.merge(additional_headers)
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/puma_spec.rb b/spec/chef/cookbooks/gitlab/recipes/puma_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9wdW1hX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/puma_spec.rb
@@ -0,0 +1,260 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::puma with Ubuntu 16.04' do
+  let(:chef_run) do
+    runner = ChefSpec::SoloRunner.new(
+      step_into: %w(runit_service puma_config),
+      path: 'spec/chef/fixtures/fauxhai/ubuntu/16.04.json'
+    )
+    runner.converge('gitlab::default')
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    stub_default_should_notify?(true)
+    stub_should_notify?('puma', true)
+  end
+
+  context 'when puma is enabled' do
+    it_behaves_like 'enabled runit service', 'puma', 'root', 'root'
+
+    describe 'logrotate settings' do
+      context 'default values' do
+        it_behaves_like 'configured logrotate service', 'puma', 'git', 'git'
+      end
+
+      context 'specified username and group' do
+        before do
+          stub_gitlab_rb(
+            user: {
+              username: 'foo',
+              group: 'bar'
+            }
+          )
+        end
+
+        it_behaves_like 'configured logrotate service', 'puma', 'foo', 'bar'
+      end
+    end
+
+    it 'creates runtime directories' do
+      expect(chef_run).to create_directory('/var/log/gitlab/puma').with(
+        owner: 'git',
+        group: nil,
+        mode: '0700'
+      )
+      expect(chef_run).to create_directory('/opt/gitlab/var/puma').with(
+        owner: 'git',
+        group: nil,
+        mode: '0700'
+      )
+      expect(chef_run).to create_directory('/var/opt/gitlab/gitlab-rails/sockets').with(
+        owner: 'git',
+        group: 'gitlab-www',
+        mode: '0750'
+      )
+    end
+
+    it 'renders the runit configuration with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/puma/run')
+        .with_content { |content|
+          expect(content).not_to match(/export prometheus_run_dir=\'\'/)
+          expect(content).not_to match(/rm \/run\/gitlab\/puma/)
+          expect(content).to match(/-u git:git/)
+          expect(content).to match(/-U git:git/)
+          expect(content).to match(/mkdir -p \/run\/gitlab\/puma/)
+          expect(content).to match(/chmod 0700 \/run\/gitlab\/puma/)
+          expect(content).to match(/chown git \/run\/gitlab\/puma/)
+          expect(content).to match(/export prometheus_run_dir=\'\/run\/gitlab\/puma\'/)
+          expect(content).to match(%r(/opt/gitlab/embedded/bin/bundle exec puma -C /var/opt/gitlab/gitlab-rails/etc/puma.rb))
+        }
+    end
+
+    it 'renders the puma.rb file' do
+      expect(chef_run).to create_puma_config('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with(
+        tag: 'gitlab-puma-worker',
+        rackup: 'config.ru',
+        environment: 'production',
+        pid: '/opt/gitlab/var/puma/puma.pid',
+        state_path: '/opt/gitlab/var/puma/puma.state',
+        listen_socket: '/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket',
+        listen_tcp: '127.0.0.1:8080',
+        working_directory: '/var/opt/gitlab/gitlab-rails/working',
+        worker_processes: 2,
+        min_threads: 4,
+        max_threads: 4
+      )
+      expect(chef_run).to create_template('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with_content { |content|
+        expect(content).to match(/lowlevel_error_handler/)
+        expect(content).to include('Raven.capture_exception')
+      }
+    end
+
+    it 'creates sysctl files' do
+      expect(chef_run).to create_gitlab_sysctl('net.core.somaxconn').with_value(1024)
+    end
+  end
+
+  context 'with custom Puma settings' do
+    before do
+      stub_gitlab_rb(
+        puma: {
+          worker_timeout: 120,
+          worker_processes: 4,
+          min_threads: 5,
+          max_threads: 10,
+          listen: '10.0.0.1',
+          port: 9000,
+          socket: '/tmp/puma.socket',
+          state_path: '/tmp/puma.state',
+          per_worker_max_memory_mb: 1000
+        }
+      )
+    end
+
+    it 'renders the puma.rb file' do
+      expect(chef_run).to create_puma_config('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with(
+        state_path: '/tmp/puma.state',
+        listen_socket: '/tmp/puma.socket',
+        listen_tcp: '10.0.0.1:9000',
+        worker_processes: 4,
+        min_threads: 5,
+        max_threads: 10,
+        per_worker_max_memory_mb: 1000
+      )
+      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
+        expect(content).to match(%r(-authSocket /tmp/puma.socket))
+      }
+      expect(Gitlab['gitlab_workhorse']['auth_socket']).to eq('/tmp/puma.socket')
+    end
+  end
+
+  context 'with custom user and group' do
+    before do
+      stub_gitlab_rb(
+        user: {
+          username: 'foo',
+          group: 'bar'
+        }
+      )
+    end
+
+    it_behaves_like 'enabled runit service', 'puma', 'root', 'root'
+  end
+
+  context 'with custom runtime_dir' do
+    before do
+      stub_gitlab_rb(
+        runtime_dir: '/tmp/test-dir'
+      )
+    end
+
+    it 'uses the user-specific runtime_dir' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/puma/run')
+        .with_content { |content|
+          expect(content).to match(%r(export prometheus_run_dir='/tmp/test-dir/gitlab/puma'))
+          expect(content).to match(%r(mkdir -p /tmp/test-dir/gitlab/puma))
+        }
+    end
+  end
+
+  context 'with ActionCable enabled' do
+    before do
+      stub_gitlab_rb(
+        actioncable: {
+          worker_pool_size: 7
+        }
+      )
+    end
+
+    it 'renders the runit configuration with ActionCable environment variables' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/puma/run')
+        .with_content { |content|
+          expect(content).to match(/ACTION_CABLE_WORKER_POOL_SIZE=7/)
+          expect(content).to match(%r(/opt/gitlab/embedded/bin/bundle exec puma -C /var/opt/gitlab/gitlab-rails/etc/puma.rb))
+        }
+    end
+  end
+
+  include_examples "consul service discovery", "puma", "rails"
+end
+
+RSpec.describe 'gitlab::puma Ubuntu 16.04 with no tmpfs' do
+  let(:chef_run) do
+    runner = ChefSpec::SoloRunner.new(
+      path: 'spec/chef/fixtures/fauxhai/ubuntu/16.04-no-run-tmpfs.json',
+      step_into: %w(runit_service)
+    )
+    runner.converge('gitlab::default')
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when puma is enabled on a node with no /run or /dev/shm tmpfs' do
+    it_behaves_like 'enabled runit service', 'puma', 'root', 'root'
+
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/puma/run')
+        .with_content { |content|
+          expect(content).to match(/export prometheus_run_dir=\'\'/)
+          expect(content).not_to match(/mkdir -p \/run\/gitlab\/puma/)
+        }
+    end
+  end
+end
+
+RSpec.describe 'gitlab::puma Ubuntu 16.04 Docker' do
+  let(:chef_run) do
+    runner = ChefSpec::SoloRunner.new(
+      path: 'spec/chef/fixtures/fauxhai/ubuntu/16.04-docker.json',
+      step_into: %w(runit_service)
+    )
+    runner.converge('gitlab::default')
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when puma is enabled on a node with a /dev/shm tmpfs' do
+    it_behaves_like 'enabled runit service', 'puma', 'root', 'root'
+
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/puma/run')
+        .with_content { |content|
+          expect(content).to match(/export prometheus_run_dir=\'\/dev\/shm\/gitlab\/puma\'/)
+          expect(content).to match(/mkdir -p \/dev\/shm\/gitlab\/puma/)
+        }
+    end
+  end
+end
+
+RSpec.describe 'gitlab::puma with more CPUs' do
+  let(:chef_run) do
+    runner = ChefSpec::SoloRunner.new(
+      step_into: %w(runit_service),
+      path: 'spec/chef/fixtures/fauxhai/ubuntu/16.04-more-cpus.json'
+    )
+    runner.converge('gitlab::default')
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when puma is enabled' do
+    it 'renders the puma.rb file' do
+      expect(chef_run).to create_puma_config('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with(
+        environment: 'production',
+        pid: '/opt/gitlab/var/puma/puma.pid',
+        state_path: '/opt/gitlab/var/puma/puma.state',
+        listen_socket: '/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket',
+        listen_tcp: '127.0.0.1:8080',
+        working_directory: '/var/opt/gitlab/gitlab-rails/working',
+        worker_processes: 16
+      )
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/rails_cache_clear_spec.rb b/spec/chef/cookbooks/gitlab/recipes/rails_cache_clear_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9yYWlsc19jYWNoZV9jbGVhcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/rails_cache_clear_spec.rb
@@ -0,0 +1,45 @@
+require 'chef_helper'
+
+# NOTE: We do not try to verify if we pass proper notifications to
+# execute['clear the gitlab-rails cache'] resource that's done in other specs
+# We just test if we use proper command and that we can change default
+# attribute value with gilab.rb setting.
+
+RSpec.describe 'gitlab::rails-cache-clear' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'test clear cache execution' do
+    let(:clear_cache_exec) { chef_run.execute('clear the gitlab-rails cache') }
+    let(:gilab_yml_temp) do
+      chef_run.find_resource(:templatesymlink,
+                             'Create a gitlab.yml and create a symlink to Rails root')
+    end
+
+    it 'check rake_cache_clear default attribute value set to true' do
+      expect(chef_run.node['gitlab']['gitlab-rails']['rake_cache_clear'])
+        .to be(true)
+    end
+
+    it 'check rake_cache_clear attribute value set to true' do
+      stub_gitlab_rb(gitlab_rails: { rake_cache_clear: true })
+      expect(chef_run.node['gitlab']['gitlab-rails']['rake_cache_clear'])
+        .to be(true)
+    end
+
+    it 'check rake_cache_clear attribute value set to false' do
+      stub_gitlab_rb(gitlab_rails: { rake_cache_clear: false })
+      expect(chef_run.node['gitlab']['gitlab-rails']['rake_cache_clear'])
+        .to be(false)
+    end
+
+    it 'check command used to clear cache' do
+      expect(clear_cache_exec.command).to match(
+        '/opt/gitlab/bin/gitlab-rake cache:clear')
+      expect(clear_cache_exec).to do_nothing
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/remote-syslog_spec.rb b/spec/chef/cookbooks/gitlab/recipes/remote-syslog_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9yZW1vdGUtc3lzbG9nX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/remote-syslog_spec.rb
@@ -0,0 +1,84 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::remote-syslog' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'creates service' do
+    before do
+      stub_gitlab_rb(
+        remote_syslog: {
+          enable: true
+        }
+      )
+    end
+    it_behaves_like "enabled runit service", "remote-syslog", "root", "root"
+  end
+
+  context 'when remote logger is enabled' do
+    before do
+      stub_gitlab_rb(
+        remote_syslog: {
+          enable: true
+        },
+        logging: {
+          udp_log_shipping_hostname: "example.com"
+        }
+      )
+    end
+
+    it 'creates the remote_syslog config' do
+      expect(chef_run.node['gitlab']['remote-syslog']['enable']).to eq true
+    end
+
+    it 'creates remote-syslog config file with correct log directories' do
+      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml')
+
+      # Checking if log_directory of both type of services - those accessible
+      # via node[service] and those via node['gitlab'][service] are populated.
+      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/redis\/\*.log/)
+      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/nginx\/\*.log/)
+      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/gitlab-rails\/\*.log/)
+      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/postgresql\/\*.log/)
+      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/sidekiq\/\*.log/)
+      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/gitlab-workhorse\/\*.log/)
+      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/gitlab-pages\/\*.log/)
+      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/gitlab-kas\/\*.log/)
+    end
+
+    it 'creates the remote-syslog sv file without setting hostname' do
+      stub_gitlab_rb(
+        remote_syslog: {
+          enable: true
+        }
+      )
+      contents = <<~END
+      #!/bin/sh
+      exec 2>&1
+
+      exec /opt/gitlab/embedded/bin/remote_syslog --no-detach --debug-level DEBUG -c /var/opt/gitlab/remote-syslog/remote_syslog.yml
+      END
+      expect(chef_run).to render_file('/opt/gitlab/sv/remote-syslog/run').with_content(contents.chomp)
+    end
+
+    it 'creates the remote-syslog sv file with setting hostname' do
+      stub_gitlab_rb(
+        remote_syslog: {
+          enable: true
+        },
+        logging: {
+          udp_log_shipping_hostname: "example.com"
+        }
+      )
+      contents = <<~END
+      #!/bin/sh
+      exec 2>&1
+
+      exec /opt/gitlab/embedded/bin/remote_syslog --no-detach --debug-level DEBUG -c /var/opt/gitlab/remote-syslog/remote_syslog.yml --hostname example.com
+      END
+      expect(chef_run).to render_file('/opt/gitlab/sv/remote-syslog/run').with_content(contents.chomp)
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/selinux_spec.rb b/spec/chef/cookbooks/gitlab/recipes/selinux_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9zZWxpbnV4X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/selinux_spec.rb
@@ -0,0 +1,96 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::gitlab-selinux' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink storage_directory)).converge('gitlab::default') }
+  let(:templatesymlink) { chef_run.templatesymlink('Create a config.yml and create a symlink to Rails root') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    stub_default_should_notify?(true)
+  end
+
+  context 'when NOT running on selinux' do
+    before do
+      allow_any_instance_of(ShellOutHelper).to receive(:success?).with('id -Z').and_return(false)
+    end
+
+    it 'should not run the semanage bash command' do
+      expect(templatesymlink).to_not notify('bash[Set proper security context on ssh files for selinux]').delayed
+    end
+  end
+
+  context 'when running on selinux' do
+    before do
+      allow_any_instance_of(ShellOutHelper).to receive(:success?).with('id -Z').and_return(true)
+      allow(File).to receive(:exist?).and_call_original
+      allow(File).to receive(:exist?).with('/var/opt/gitlab/.ssh').and_return(true)
+      allow(File).to receive(:exist?).with('/var/opt/gitlab/.ssh/authorized_keys').and_return(true)
+      allow(File).to receive(:exist?).with('/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret').and_return(true)
+      allow(File).to receive(:exist?).with('/var/opt/gitlab/gitlab-shell/config.yml').and_return(true)
+      allow(File).to receive(:exist?).with('/var/opt/gitlab/gitlab-workhorse/sockets').and_return(true)
+    end
+
+    let(:bash_block) { chef_run.bash('Set proper security context on ssh files for selinux') }
+
+    def semanage_fcontext(filename)
+      "semanage fcontext -a -t gitlab_shell_t '#{filename}'"
+    end
+
+    it 'should run the semanage bash command' do
+      expect(templatesymlink).to notify('bash[Set proper security context on ssh files for selinux]').delayed
+    end
+
+    it 'sets the security context of gitlab-shell files' do
+      lines = bash_block.code.split("\n")
+      files = %w(/var/opt/gitlab/.ssh(/.*)?
+                 /var/opt/gitlab/.ssh/authorized_keys
+                 /var/opt/gitlab/gitlab-shell/config.yml
+                 /var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret
+                 /var/opt/gitlab/gitlab-workhorse/sockets)
+      managed_files = files.map { |file| semanage_fcontext(file) }
+
+      expect(lines).to include(*managed_files)
+      expect(lines).to include("restorecon -R -v '/var/opt/gitlab/.ssh'")
+      expect(lines).to include("restorecon -v '/var/opt/gitlab/.ssh/authorized_keys'")
+      expect(lines).to include("restorecon -v '/var/opt/gitlab/gitlab-shell/config.yml'")
+      expect(lines).to include("restorecon -v '/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret'")
+      expect(lines).to include("restorecon -v '/var/opt/gitlab/gitlab-workhorse/sockets'")
+    end
+
+    context 'and the user configured a custom workhorse sockets directory' do
+      let(:user_sockets_directory) { '/how/do/you/do' }
+      before do
+        stub_gitlab_rb(
+          gitlab_workhorse: {
+            listen_network: 'unix',
+            sockets_directory: user_sockets_directory
+          }
+        )
+      end
+
+      it 'sets the security context of a custom workhorse sockets directory' do
+        allow(File).to receive(:exist?).with(user_sockets_directory).and_return(true)
+        lines = bash_block.code.split("\n")
+        files = [user_sockets_directory]
+        managed_files = files.map { |file| semanage_fcontext(file) }
+
+        expect(lines).to include(*managed_files)
+        expect(lines).to include("restorecon -v '#{user_sockets_directory}'")
+      end
+    end
+
+    context 'when gitlab-rails is disabled' do
+      before do
+        stub_gitlab_rb(
+          gitlab_rails: {
+            enable: false
+          }
+        )
+      end
+
+      it 'should not attempt to set security context' do
+        expect(chef_run).not_to run_bash('Set proper security context on ssh files for selinux')
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/show_config_spec.rb b/spec/chef/cookbooks/gitlab/recipes/show_config_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9zaG93X2NvbmZpZ19zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/show_config_spec.rb
@@ -0,0 +1,20 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::show_config' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::show_config') }
+
+  it 'outputs user-defined gitlab.rb configuration to stdout' do
+    allow(Gitlab).to receive(:[]).and_call_original
+    stub_gitlab_rb(
+      patroni: { scope: 'test-cluster' }
+    )
+
+    expect { chef_run }.to output(/"scope": "test-cluster"/).to_stdout
+  end
+
+  it 'outputs GitlabCluster defined attributes to stdout' do
+    GitlabCluster.config.set('primary', true)
+
+    expect { chef_run }.to output(/"primary": true/).to_stdout
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/sidekiq_spec.rb b/spec/chef/cookbooks/gitlab/recipes/sidekiq_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9zaWRla2lxX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/sidekiq_spec.rb
@@ -0,0 +1,57 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::sidekiq' do
+  let(:chef_run) do
+    runner = ChefSpec::SoloRunner.new(
+      step_into: %w(sidekiq_service runit_service),
+      path: 'spec/chef/fixtures/fauxhai/ubuntu/16.04.json'
+    )
+    runner.converge('gitlab::default')
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'with default values' do
+    it 'correctly renders out the sidekiq service file' do
+      expect(chef_run).to render_file("/opt/gitlab/sv/sidekiq/run")
+        .with_content { |content|
+          expect(content).not_to match(/export prometheus_run_dir=\'\'/)
+          expect(content).to match(/mkdir -p \/run\/gitlab\/sidekiq/)
+          expect(content).to match(/rm \/run\/gitlab\/sidekiq/)
+          expect(content).to match(/chmod 0700 \/run\/gitlab\/sidekiq/)
+          expect(content).to match(/chown git \/run\/gitlab\/sidekiq/)
+          expect(content).to match(/export prometheus_run_dir=\'\/run\/gitlab\/sidekiq\'/)
+          expect(content).to match(%r{bin/sidekiq-cluster})
+          expect(content).to match(/-m 50/) # max_concurrency
+          expect(content).to match(/--timeout 25/) # shutdown timeout
+          expect(content).to match(/\*/) # all queues
+        }
+    end
+
+    it_behaves_like "enabled runit service", "sidekiq", "root", "root"
+  end
+
+  describe 'log_format' do
+    context 'by default' do
+      it 'does not pass timestamp flag to svlogd' do
+        expect(chef_run).not_to render_file("/opt/gitlab/sv/sidekiq/log/run").with_content(/-tt/)
+      end
+    end
+
+    context 'when user specifies text log format' do
+      before do
+        stub_gitlab_rb(
+          sidekiq: { log_format: 'text' }
+        )
+      end
+
+      it 'passes timestamp flag to svlogd' do
+        expect(chef_run).to render_file("/opt/gitlab/sv/sidekiq/log/run").with_content(/-tt/)
+      end
+    end
+  end
+
+  include_examples "consul service discovery", "sidekiq", "sidekiq"
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/storage-check_spec.rb b/spec/chef/cookbooks/gitlab/recipes/storage-check_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9zdG9yYWdlLWNoZWNrX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/storage-check_spec.rb
@@ -0,0 +1,57 @@
+#
+# Copyright:: Copyright (c) 2017 GitLab Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef_helper'
+
+RSpec.describe 'gitlab::storage-check' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe 'when disabled' do
+    it_behaves_like 'disabled runit service', 'storage-check'
+  end
+
+  describe 'when enabled' do
+    context 'default values' do
+      before do
+        stub_gitlab_rb(
+          storage_check: { enable: true }
+        )
+      end
+
+      it_behaves_like 'enabled runit service', 'storage-check', 'root', 'root'
+    end
+
+    context 'custom values' do
+      before do
+        stub_gitlab_rb(
+          storage_check: {
+            enable: true
+          },
+          user: {
+            username: 'foo',
+            group: 'bar'
+          }
+        )
+      end
+
+      it_behaves_like 'enabled runit service', 'storage-check', 'root', 'root'
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/recipes/users_spec.rb b/spec/chef/cookbooks/gitlab/recipes/users_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy91c2Vyc19zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/recipes/users_spec.rb
@@ -0,0 +1,47 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::users' do
+  let(:chef_run) { ChefSpec::SoloRunner.new.converge('gitlab::users') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'with default attributes' do
+    it 'creates gitlab user' do
+      account_params = {
+        username: 'git',
+        groupname: 'git',
+        home: '/var/opt/gitlab',
+        shell: '/bin/sh'
+      }
+
+      expect(chef_run).to create_directory('/var/opt/gitlab')
+      expect(chef_run).to create_account('GitLab user and group').with(account_params)
+    end
+
+    it 'creates .gitconfig' do
+      template_params = {
+        owner: 'git',
+        group: 'git',
+        mode: '0644'
+      }
+
+      expect(chef_run).to create_template('/var/opt/gitlab/.gitconfig').with(template_params)
+      expect(chef_run).to render_file('/var/opt/gitlab/.gitconfig').with_content { |content|
+        expect(content).to match(/name = GitLab/)
+        expect(content).to match(/email = gitlab@fauxhai.local/)
+        expect(content).to match(/fsyncObjectFiles = true/)
+      }
+    end
+
+    it 'creates .bundle directory' do
+      directory_params = {
+        owner: 'git',
+        group: 'git'
+      }
+
+      expect(chef_run).to create_directory('/var/opt/gitlab/.bundle').with(directory_params)
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/resources/puma_config_spec.rb b/spec/chef/cookbooks/gitlab/resources/puma_config_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVzb3VyY2VzL3B1bWFfY29uZmlnX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/resources/puma_config_spec.rb
@@ -0,0 +1,53 @@
+require 'chef_helper'
+
+RSpec.describe 'puma_config' do
+  let(:runner) do
+    ChefSpec::SoloRunner.new(step_into: %w(puma_config))
+  end
+
+  context 'create' do
+    let(:chef_run) { runner.converge('test_gitlab::puma_config_create') }
+
+    it 'creates necessary directories' do
+      expect(chef_run).to create_directory('/var/opt/gitlab/gitlab-rails/etc')
+    end
+
+    it 'renders puma.rb file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with_content { |content|
+        expect(content).to match(%r(^environment 'production'))
+        expect(content).to match(%r(^rackup '/opt/gitlab/embedded/service/gitlab-rails/config.ru'))
+        expect(content).to match(%r(^pidfile '/opt/gitlab/var/puma/puma.pid'))
+        expect(content).to match(%r(^state_path '/opt/gitlab/var/puma/puma.state'))
+        expect(content).to match(%r(^bind 'unix:///var/opt/gitlab/gitlab-rails/sockets/gitlab.socket'))
+        expect(content).to match(%r(^bind 'tcp://127.0.0.1:8080'))
+        expect(content).to match(%r(^directory '/var/opt/gitlab/gitlab-rails/working'))
+        expect(content).to match(%r(^require_relative "/opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/cluster/lifecycle_events"$))
+        expect(content).to match(/^options = { workers: 2 }$/)
+        expect(content).to match(%r(Gitlab::Cluster::PumaWorkerKillerInitializer.start\(options\)))
+        expect(content).to match(/^preload_app!$/)
+        expect(content).to match(%r(^require_relative "/opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/puma_logging/json_formatter"$))
+      }
+    end
+  end
+
+  context 'create with default puma config' do
+    let(:chef_run) { runner.converge('test_gitlab::puma_config_create') }
+
+    it 'renders puma.rb file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with_content { |content|
+        expect(content).to match(%r(rackup '/opt/gitlab/embedded/service/gitlab-rails))
+      }
+    end
+  end
+
+  context 'create with custom Puma settings' do
+    let(:chef_run) { runner.converge('test_gitlab::puma_config_custom') }
+
+    it 'renders puma.rb file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with_content { |content|
+        expect(content).to match(%r(Gitlab::Cluster::PumaWorkerKillerInitializer.start\(options, puma_per_worker_max_memory_mb: 1000\)))
+        expect(content).to match(%r(rackup '/opt/custom/gitlab/embedded/service/gitlab-rails))
+      }
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/gitlab/resources/rails_migration_spec.rb b/spec/chef/cookbooks/gitlab/resources/rails_migration_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVzb3VyY2VzL3JhaWxzX21pZ3JhdGlvbl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/gitlab/resources/rails_migration_spec.rb
@@ -0,0 +1,67 @@
+require 'chef_helper'
+
+RSpec.describe 'rails_migration' do
+  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(rails_migration)) }
+
+  before do
+    allow_any_instance_of(PgHelper).to receive(:postgresql_user).and_return('fakeuser')
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'run' do
+    let(:chef_run) { runner.converge('test_gitlab::rails_migration_run') }
+    let(:bash_block) { chef_run.bash('migrate gitlab-test database') }
+    let(:migration_block) { chef_run.rails_migration('gitlab-test') }
+
+    context 'when database has not been migrated' do
+      before do
+        allow_any_instance_of(GitlabGeoHelper).to receive(:migrated?).and_return(false)
+      end
+
+      it 'executes the bash script migration' do
+        expect(chef_run).to run_bash('migrate gitlab-test database')
+      end
+
+      it 'restarts dependent_services' do
+        expect(bash_block).to notify('ruby_block[test-dependent]').to(:restart)
+      end
+    end
+
+    context 'when database has been migrated already' do
+      it 'doesnt execute the bash script' do
+        expect_any_instance_of(RailsMigrationHelper).to receive(:migrated?) { true }
+
+        expect(chef_run).not_to run_bash('migrate gitlab-test database')
+      end
+    end
+
+    context 'bash script' do
+      it 'defines log file based on logfile_prefix' do
+        log_file = %(log_file="/var/log/gitlab/gitlab-rails/gitlab-test-db-migrate-$(date +%Y-%m-%d-%H-%M-%S).log")
+
+        expect(bash_block.code).to include(log_file)
+      end
+
+      it 'passes appropriate environment variables through' do
+        expect(bash_block.environment).to match(hash_including('SOME_ENV'))
+      end
+
+      it 'triggers provided rake_task' do
+        migrate = %(/opt/gitlab/bin/gitlab-rake gitlab:db:configure 2>& 1 | tee ${log_file})
+
+        expect(bash_block.code).to match(/#{migrate}/)
+      end
+
+      it 'pipes exit code to db-migrate file' do
+        fake_connection_digest = '63467bb60aa187d2a6830aa8f1b5cbe0'
+        fake_revision = 'f9631484e7f'
+        pipe = %(echo $STATUS > /var/opt/gitlab/gitlab-rails/upgrade-status/db-migrate-#{fake_connection_digest}-#{fake_revision})
+
+        expect_any_instance_of(RailsMigrationHelper).to receive(:connection_digest) { fake_connection_digest }
+        expect_any_instance_of(RailsMigrationHelper).to receive(:revision) { fake_revision }
+
+        expect(bash_block.code).to include(pipe)
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/letsencrypt/libraries/lets_encrypt_spec.rb b/spec/chef/cookbooks/letsencrypt/libraries/lets_encrypt_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9sZXRzZW5jcnlwdC9saWJyYXJpZXMvbGV0c19lbmNyeXB0X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/letsencrypt/libraries/lets_encrypt_spec.rb
@@ -0,0 +1,179 @@
+require 'chef_helper'
+
+RSpec.describe LetsEncrypt do
+  subject { ::LetsEncrypt }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context '.parse_variables' do
+    it 'calls to parse_enable' do
+      expect(subject).to receive(:parse_enable)
+
+      subject.parse_variables
+    end
+  end
+
+  context '.parse_enable' do
+    context 'when specifying letsencrypt enabled' do
+      context 'true' do
+        before { stub_gitlab_rb(letsencrypt: { enable: true }) }
+
+        it 'should not call should_auto_enable?' do
+          expect(subject).not_to receive(:should_auto_enable?)
+
+          subject.parse_enable
+        end
+      end
+
+      context 'false' do
+        before { stub_gitlab_rb(letsencrypt: { enable: false }) }
+
+        it 'should not call should_auto_enable?' do
+          expect(subject).not_to receive(:should_auto_enable?)
+
+          subject.parse_enable
+        end
+      end
+
+      context 'unspecified' do
+        it 'should use the value of should_auto_enable' do
+          allow(subject).to receive(:should_auto_enable?).and_return('bananas')
+          subject.parse_enable
+
+          expect(Gitlab['letsencrypt']['enable']).to eq('bananas')
+        end
+      end
+    end
+  end
+
+  context '.should_auto_enable?' do
+    let(:node) { Mash.new(gitlab: { nginx: {} }) }
+
+    before do
+      stub_gitlab_rb(
+        gitlab_rails: {
+          gitlab_https: true
+        },
+        nginx: {
+          ssl_certificate_key: 'example.key',
+          ssl_certificate: 'example.crt'
+        }
+      )
+
+      allow(Gitlab).to receive(:[]).with(:node).and_return(node)
+      allow(File).to receive(:exist?).with('example.key').and_return(false)
+      allow(File).to receive(:exist?).with('example.crt').and_return(false)
+    end
+
+    it 'is true' do
+      expect(subject.should_auto_enable?).to be_truthy
+    end
+
+    it 'is false when not using a https url' do
+      stub_gitlab_rb(gitlab_rails: { gitlab_https: false })
+
+      expect(subject.should_auto_enable?).to be_falsey
+    end
+
+    it 'is false when nginx is not enabled' do
+      stub_gitlab_rb(nginx: { enable: false })
+
+      expect(subject.should_auto_enable?).to be_falsey
+    end
+
+    it 'is false when nginx is disabled by roles' do
+      allow(node['gitlab']['nginx']).to receive(:[]).with('enable').and_return(false)
+
+      expect(subject.should_auto_enable?).to be_falsey
+    end
+
+    it 'is false with explicit nginx.listen_https = false' do
+      stub_gitlab_rb(nginx: { listen_https: false })
+
+      expect(subject.should_auto_enable?).to be_falsey
+    end
+
+    it 'is false with the key present' do
+      allow(File).to receive(:exist?).with('example.key').and_return(true)
+
+      expect(subject.should_auto_enable?).to be_falsey
+    end
+
+    it 'is false with the cert present' do
+      mock_cert = OpenSSL::X509::Certificate.new
+      allow(mock_cert).to receive(:not_after).and_return(Time.now + 600)
+      allow(File).to receive(:exist?).with('example.crt').and_return(true)
+      allow(File).to receive(:read).with('example.crt').and_return(nil)
+      allow(OpenSSL::X509::Certificate).to receive(:new).and_return(mock_cert)
+
+      expect(subject.should_auto_enable?).to be_falsey
+    end
+
+    it 'is true when files present, but we provisioned them before' do
+      stub_gitlab_rb(letsencrypt: { auto_enabled: true })
+
+      allow(File).to receive(:exist?).with('example.key').and_return(true)
+      allow(File).to receive(:exist?).with('example.crt').and_return(true)
+
+      expect(subject.should_auto_enable?).to be_truthy
+    end
+
+    it 'is true when files present, but LE certificate is expired' do
+      mock_cert = OpenSSL::X509::Certificate.new
+      allow(mock_cert).to receive(:not_after).and_return(Time.now - 1)
+      allow(mock_cert).to receive(:issuer).and_return(
+        OpenSSL::X509::Name.parse(%(/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3)))
+      allow(File).to receive(:exist?).with('example.key').and_return(true)
+      allow(File).to receive(:exist?).with('example.crt').and_return(true)
+      allow(File).to receive(:read).with('example.crt').and_return(nil)
+      allow(OpenSSL::X509::Certificate).to receive(:new).and_return(mock_cert)
+
+      expect(subject.should_auto_enable?).to be_truthy
+    end
+
+    it 'is false when files present, but non-LE certificate is expired' do
+      mock_cert = OpenSSL::X509::Certificate.new
+      allow(mock_cert).to receive(:not_after).and_return(Time.now - 1)
+      allow(mock_cert).to receive(:issuer).and_return(
+        OpenSSL::X509::Name.parse('/C=US/O=Example Corporation/CN=Example'))
+      allow(File).to receive(:exist?).with('example.key').and_return(true)
+      allow(File).to receive(:exist?).with('example.crt').and_return(true)
+      allow(File).to receive(:read).with('example.crt').and_return(nil)
+      allow(OpenSSL::X509::Certificate).to receive(:new).and_return(mock_cert)
+
+      expect(subject.should_auto_enable?).to be_falsey
+    end
+  end
+
+  context '.save_auto_enabled' do
+    it 'does nothing if not auto_enabled' do
+      expect(SecretsHelper).not_to receive(:load_gitlab_secrets)
+
+      subject.save_auto_enabled
+    end
+
+    context 'auto_enabled' do
+      before do
+        stub_gitlab_rb(letsencrypt: { auto_enabled: true })
+        allow(SecretsHelper).to receive(:load_gitlab_secrets).and_return({})
+        allow(SecretsHelper).to receive(:write_to_gitlab_secrets)
+      end
+
+      it 'writes when secret is absent' do
+        expect(SecretsHelper).to receive(:write_to_gitlab_secrets)
+
+        subject.save_auto_enabled
+      end
+
+      it 'does not write if secret is already true' do
+        allow(SecretsHelper).to receive(:load_gitlab_secrets)
+          .and_return('letsencrypt' => { 'auto_enabled' => true })
+        expect(SecretsHelper).not_to receive(:write_to_gitlab_secrets)
+
+        subject.save_auto_enabled
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/letsencrypt/recipes/letsencrypt_spec.rb b/spec/chef/cookbooks/letsencrypt/recipes/letsencrypt_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9sZXRzZW5jcnlwdC9yZWNpcGVzL2xldHNlbmNyeXB0X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/letsencrypt/recipes/letsencrypt_spec.rb
@@ -0,0 +1,227 @@
+require 'chef_helper'
+
+RSpec.describe 'enabling letsencrypt' do
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    stub_gitlab_rb(external_url: 'https://fakehost.example.com')
+  end
+
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+  context 'unspecified uses LetsEncrypt.should_auto_enable?' do
+    it 'true' do
+      allow(LetsEncrypt).to receive(:should_auto_enable?).and_return(true)
+
+      expect(chef_run).to include_recipe('letsencrypt::enable')
+    end
+
+    it 'false' do
+      allow(LetsEncrypt).to receive(:should_auto_enable?).and_return(false)
+
+      expect(chef_run).to include_recipe('letsencrypt::disable')
+      expect(chef_run).not_to include_recipe('letsencrypt::enable')
+    end
+  end
+
+  context 'specified' do
+    it 'true' do
+      stub_gitlab_rb(letsencrypt: { enable: true })
+
+      expect(chef_run).to include_recipe('letsencrypt::enable')
+    end
+
+    it 'false' do
+      stub_gitlab_rb(letsencrypt: { enable: false })
+
+      expect(chef_run).not_to include_recipe('letsencrypt::enable')
+    end
+  end
+end
+
+RSpec.describe 'letsencrypt::enable' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+  let(:node) { chef_run.node }
+
+  let(:https_redirect_block) { %r!server { ## HTTPS redirect server(.*)} ## end HTTPS redirect server!m }
+  let(:https_block) { %r!server { ## HTTPS server(.*)} ## end HTTPS server!m }
+  let(:acme_challenge_block) { %r!\s*location /.well-known/acme-challenge/ {! }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow(LetsEncrypt).to receive(:should_auto_enable?).and_return(false)
+    allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
+    allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('nginx').and_return(true)
+  end
+
+  context 'default' do
+    it 'does not run' do
+      expect(chef_run).not_to include_recipe('letsencrypt::enable')
+    end
+  end
+
+  context 'enabled' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'https://fakehost.example.com',
+        mattermost_external_url: 'https://fakemost.example.com',
+        registry_external_url: 'https://fakereg.example.com',
+        letsencrypt: {
+          enable: true
+        }
+      )
+    end
+
+    it 'is included' do
+      expect(chef_run).to include_recipe('letsencrypt::enable')
+    end
+
+    context 'when updating nginx configurations' do
+      using RSpec::Parameterized::TableSyntax
+
+      where(:redirect_config_key, :nginx_config_file) do
+        'nginx' | '/var/opt/gitlab/nginx/conf/gitlab-http.conf'
+        'mattermost-nginx' | '/var/opt/gitlab/nginx/conf/gitlab-mattermost-http.conf'
+        'registry-nginx' | '/var/opt/gitlab/nginx/conf/gitlab-registry.conf'
+      end
+
+      with_them do
+        it 'redirects http to https' do
+          expect(node['gitlab'][redirect_config_key]['redirect_http_to_https']).to be_truthy
+        end
+
+        it 'includes the well known acme challenge location block' do
+          expect(chef_run).to render_file(nginx_config_file).with_content { |content|
+            https_redirect_server = content.match(https_redirect_block)
+            https_server = content.match(https_block)
+
+            expect(https_redirect_server).not_to be_nil
+            expect(https_server).not_to be_nil
+
+            expect(https_redirect_server.to_s).to match(acme_challenge_block)
+            expect(https_server.to_s).to match(acme_challenge_block)
+          }
+        end
+      end
+    end
+
+    it 'uses http authorization by default' do
+      expect(chef_run).to include_recipe('letsencrypt::http_authorization')
+    end
+
+    it 'creates a self signed certificate' do
+      expect(chef_run).to create_acme_selfsigned('fakehost.example.com').with(
+        key: '/etc/gitlab/ssl/fakehost.example.com.key',
+        crt: '/etc/gitlab/ssl/fakehost.example.com.crt'
+      )
+    end
+
+    it 'creates a letsencrypt certificate' do
+      expect(chef_run).to create_letsencrypt_certificate('fakehost.example.com').with(
+        key: '/etc/gitlab/ssl/fakehost.example.com.key',
+        crt: '/etc/gitlab/ssl/fakehost.example.com.crt'
+      )
+    end
+
+    it 'warns the user' do
+      prod_cert = chef_run.letsencrypt_certificate('fakehost.example.com')
+      expect(prod_cert).to notify('ruby_block[display_le_message]').to(:run)
+    end
+
+    context 'auto_renew' do
+      context 'default' do
+        it 'enables crond' do
+          expect(chef_run).to include_recipe('crond::enable')
+        end
+
+        it 'adds a crond_job' do
+          expect(chef_run).to create_crond_job('letsencrypt-renew').with(
+            user: "root",
+            hour: 0,
+            minute: 31,
+            day_of_month: "*/4",
+            command: "/opt/gitlab/bin/gitlab-ctl renew-le-certs"
+          )
+        end
+
+        it 'does not log a warning' do
+          expect(LoggingHelper).not_to receive(:warning).with("Let's Encrypt is enabled, but external_url is using http")
+          chef_run.ruby_block('display_le_message').block.call
+        end
+      end
+
+      context 'false' do
+        before do
+          stub_gitlab_rb(letsencrypt: { enable: true, auto_renew: false })
+          allow_any_instance_of(PgHelper).to receive(:is_standby?).and_return false
+        end
+
+        it 'removes the letsencrypt-renew cronjob' do
+          expect(chef_run).to delete_crond_job('letsencrypt-renew')
+        end
+
+        it 'warns that we do not setup automatic renewal' do
+          chef_run.ruby_block('display_le_message').block.call
+
+          expect_logged_warning(/does not setup/)
+        end
+      end
+    end
+
+    context 'external_url uses http' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'http://plainhost.example.com',
+          letsencrypt: {
+            enable: true
+          }
+        )
+      end
+
+      it 'logs a warning' do
+        expect(chef_run).to run_ruby_block('http external-url')
+      end
+    end
+  end
+end
+
+# This should work standalone for renewal purposes
+RSpec.describe 'letsencrypt::renew' do
+  let(:chef_run) do
+    ChefSpec::SoloRunner.converge('gitlab::letsencrypt_renew')
+  end
+
+  before do
+    allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
+    allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('nginx').and_return(true)
+  end
+
+  context 'letsencrypt enabled' do
+    before do
+      allow(Gitlab).to receive(:[]).and_call_original
+      stub_gitlab_rb(
+        external_url: 'https://standalone.fakehost.com',
+        letsencrypt: {
+          enable: true
+        }
+      )
+    end
+
+    it 'executes letsencrypt_certificate' do
+      expect(chef_run).to create_letsencrypt_certificate('standalone.fakehost.com')
+    end
+  end
+
+  context 'letsencrypt auto-enabled' do
+    before do
+      allow(Gitlab).to receive(:[]).and_call_original
+      allow(OpenSSL::X509::Certificate).to receive(:not_after).and_return(Time.now - 1)
+      stub_gitlab_rb(
+        external_url: 'https://standalone.fakehost.com'
+      )
+    end
+
+    it 'executes letsencrypt_certificate' do
+      expect(chef_run).to create_letsencrypt_certificate('standalone.fakehost.com')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/letsencrypt/resources/letsencrypt_certificate_spec.rb b/spec/chef/cookbooks/letsencrypt/resources/letsencrypt_certificate_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9sZXRzZW5jcnlwdC9yZXNvdXJjZXMvbGV0c2VuY3J5cHRfY2VydGlmaWNhdGVfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/letsencrypt/resources/letsencrypt_certificate_spec.rb
@@ -0,0 +1,115 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::letsencrypt' do
+  let(:chef_run) do
+    ChefSpec::SoloRunner.new(step_into: %(letsencrypt_certificate)).converge('gitlab::default')
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    stub_gitlab_rb(
+      external_url: 'https://fakehost.example.com',
+      letsencrypt: {
+        enable: true,
+      }
+    )
+  end
+
+  context 'with NGINX running' do
+    before do
+      allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
+      allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('nginx').and_return(true)
+    end
+
+    it 'creates a staging certificate' do
+      expect(chef_run).to create_acme_certificate('staging').with(
+        crt: '/etc/gitlab/ssl/fakehost.example.com.crt-staging',
+        key: '/etc/gitlab/ssl/fakehost.example.com.key-staging',
+        wwwroot: '/var/opt/gitlab/nginx/www',
+        dir: 'https://acme-staging-v02.api.letsencrypt.org/directory',
+        sensitive: true
+      )
+    end
+
+    it "updates the node['acme']['private_key'] attribute" do
+      expect(chef_run).to run_ruby_block('reset private key')
+    end
+
+    it 'creates a production certificate' do
+      expect(chef_run).to create_acme_certificate('production').with(
+        crt: '/etc/gitlab/ssl/fakehost.example.com.crt',
+        key: '/etc/gitlab/ssl/fakehost.example.com.key',
+        wwwroot: '/var/opt/gitlab/nginx/www',
+        sensitive: true
+      )
+    end
+
+    context 'specifying a different key_size' do
+      before do
+        allow(File).to receive(:file?).and_call_original
+        allow(File).to receive(:file?).with('/etc/gitlab/ssl/fakehost.example.com.key').and_return(true)
+        allow(File).to receive(:read).with(anything).and_call_original
+        allow(File).to receive(:read).with('/etc/gitlab/ssl/fakehost.example.com.key').and_return(OpenSSL::PKey::RSA.new(2048))
+
+        stub_gitlab_rb(
+          external_url: 'https://fakehost.example.com',
+          letsencrypt: {
+            enable: true,
+            key_size: 3072
+          }
+        )
+      end
+
+      it 'deletes and recreates the SSL files' do
+        expect(chef_run).to delete_file('/etc/gitlab/ssl/fakehost.example.com.key')
+        expect(chef_run).to delete_file('/etc/gitlab/ssl/fakehost.example.com.crt')
+
+        expect(chef_run).to create_acme_certificate('production').with(
+          crt: '/etc/gitlab/ssl/fakehost.example.com.crt',
+          key: '/etc/gitlab/ssl/fakehost.example.com.key',
+          key_size: 3072,
+          wwwroot: '/var/opt/gitlab/nginx/www',
+          sensitive: true
+        )
+      end
+    end
+
+    it 'reloads nginx' do
+      prod_cert = chef_run.acme_certificate('production')
+      expect(prod_cert).to notify('execute[reload nginx]').to(:run)
+    end
+
+    context 'with extra options' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://fakehost.example.com',
+          letsencrypt: {
+            enable: true,
+            alt_names: %w(one.example.com two.example.com),
+            contact_emails: %w(foo@bar.com one@two.com)
+          }
+        )
+      end
+
+      it 'adds alt_names to the certificate resource' do
+        expect(chef_run).to create_acme_certificate('production')
+                              .with(
+                                alt_names: %w(one.example.com two.example.com),
+                                contact: %w(mailto:foo@bar.com mailto:one@two.com)
+                              )
+      end
+    end
+  end
+
+  context 'when NGINX is not running' do
+    before do
+      allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
+      allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('nginx').and_return(false)
+    end
+
+    it 'does not attempt to create a certificate' do
+      expect(chef_run).not_to create_acme_certificate('staging')
+      expect(chef_run).not_to create_acme_certificate('production')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/logrotate/recipes/folders_and_configs_spec.rb b/spec/chef/cookbooks/logrotate/recipes/folders_and_configs_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9sb2dyb3RhdGUvcmVjaXBlcy9mb2xkZXJzX2FuZF9jb25maWdzX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/logrotate/recipes/folders_and_configs_spec.rb
@@ -0,0 +1,143 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::logrotate_folder_and_configs_spec' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when logrotate is enabled' do
+    it 'creates default set of directories' do
+      expect(chef_run.node['logrotate']['dir'])
+        .to eql('/var/opt/gitlab/logrotate')
+      expect(chef_run.node['logrotate']['log_directory'])
+        .to eql('/var/log/gitlab/logrotate')
+
+      expect(chef_run).to create_directory('/var/opt/gitlab/logrotate').with(
+        owner: nil,
+        group: nil,
+        mode: '0700'
+      )
+      expect(chef_run).to create_directory('/var/log/gitlab/logrotate').with(
+        owner: nil,
+        group: nil,
+        mode: '0700'
+      )
+      expect(chef_run).to create_directory('/var/opt/gitlab/logrotate/logrotate.d').with(
+        owner: nil,
+        group: nil,
+        mode: '0700'
+      )
+    end
+
+    it 'creates logrotate directories in alternative locations' do
+      stub_gitlab_rb(logrotate: { dir: "/tmp/logrotate", log_directory: "/tmp/logs/logrotate" })
+
+      expect(chef_run).to create_directory('/tmp/logrotate').with(
+        owner: nil,
+        group: nil,
+        mode: '0700'
+      )
+      expect(chef_run).to create_directory('/tmp/logs/logrotate').with(
+        owner: nil,
+        group: nil,
+        mode: '0700'
+      )
+      expect(chef_run).to create_directory('/tmp/logrotate/logrotate.d').with(
+        owner: nil,
+        group: nil,
+        mode: '0700'
+      )
+    end
+
+    it 'creates default set of configuration templates' do
+      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitlab-rails')
+      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitlab-shell')
+      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitlab-workhorse')
+      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitlab-pages')
+      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitaly')
+    end
+
+    it 'populates configuration template with default values' do
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/daily/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/rotate 30/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/compress/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/copytruncate/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/notifempty/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/postrotate/)
+      expect(chef_run).not_to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/size/)
+      expect(chef_run).not_to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/maxsize/)
+      expect(chef_run).not_to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/dateext/)
+    end
+
+    it 'populates configuration template with custom values when logrotate is disabled' do
+      stub_gitlab_rb(logging:
+        { logrotate_frequency: "weekly",
+          logrotate_size: "50",
+          logrotate_maxsize: "50",
+          logrotate_rotate: "50",
+          logrotate_compress: "nocompress",
+          logrotate_method: "copy",
+          logrotate_postrotate: "/usr/bin/killall -HUP nginx",
+          logrotate_dateformat: "-%Y-%m-%d", }, logrotate: { enable: false })
+
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/weekly/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/rotate 50/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/nocompress/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/copy/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/postrotate/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/size 50/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/maxsize 50/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/dateext/)
+      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
+        .with_content(/dateformat -%Y-%m-%d/)
+    end
+
+    context 'when services not under gitlab key are specified' do
+      it 'populates files correctly' do
+        stub_gitlab_rb(
+          logrotate: {
+            services: ['gitlab-rails', 'gitaly']
+          },
+          gitaly: {
+            log_directory: '/my/log/directory'
+          }
+        )
+        expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitlab-rails')
+        expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/gitaly')
+          .with_content(/my\/log\/directory\/\*\.log/)
+      end
+    end
+
+    context 'when services that are not supported are specified' do
+      it 'raises an error' do
+        stub_gitlab_rb(
+          logrotate: {
+            services: ['gitlab-rails', 'foo-bar']
+          }
+        )
+
+        expect { chef_run }.to raise_error("Service foo-bar was specified in logrotate['services'], but is not a valid service.")
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/logrotate/recipes/logrotate_spec.rb b/spec/chef/cookbooks/logrotate/recipes/logrotate_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9sb2dyb3RhdGUvcmVjaXBlcy9sb2dyb3RhdGVfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/logrotate/recipes/logrotate_spec.rb
@@ -0,0 +1,54 @@
+require 'chef_helper'
+
+RSpec.describe 'logrotate' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when logrotate is enabled' do
+    let(:config_template) { chef_run.template('/opt/gitlab/sv/logrotate/log/config') }
+
+    it_behaves_like "enabled runit service", "logrotate", "root", "root"
+
+    it 'populates the files with expected configuration' do
+      expect(config_template).to notify('ruby_block[reload_log_service]')
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/run')
+        .with_content(/cd \/var\/opt\/gitlab\/logrotate/)
+      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/run')
+        .with_content(/exec \/opt\/gitlab\/embedded\/bin\/chpst -P \/usr\/bin\/env/)
+      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/run')
+        .with_content(/\/opt\/gitlab\/embedded\/bin\/gitlab-logrotate-wrapper/)
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/log/run')
+        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/logrotate/)
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/log/config')
+        .with_content(/s209715200/)
+      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/log/config')
+        .with_content(/n30/)
+      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/log/config')
+        .with_content(/t86400/)
+      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/log/config')
+        .with_content(/!gzip/)
+    end
+
+    it 'executes start command' do
+      expect(chef_run).to run_execute('/opt/gitlab/bin/gitlab-ctl start logrotate').with(retries: 20)
+    end
+  end
+
+  context 'when logrotate is disabled' do
+    before do
+      stub_gitlab_rb(logrotate: { enable: false })
+    end
+
+    it_behaves_like "disabled runit service", "logrotate"
+
+    it 'does not execute the start command' do
+      expect(chef_run).not_to run_execute('/opt/gitlab/bin/gitlab-ctl start logrotate').with(retries: 20)
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/mattermost/recipes/mattermost_spec.rb b/spec/chef/cookbooks/mattermost/recipes/mattermost_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9tYXR0ZXJtb3N0L3JlY2lwZXMvbWF0dGVybW9zdF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/mattermost/recipes/mattermost_spec.rb
@@ -0,0 +1,322 @@
+require 'chef_helper'
+
+RSpec.describe 'gitlab::mattermost' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  let(:default_vars) do
+    {
+      'MM_FILESETTINGS_DIRECTORY' => '/var/opt/gitlab/mattermost/data',
+      'MM_GITLABSETTINGS_AUTHENDPOINT' => 'http://gitlab.example.com/oauth/authorize',
+      'MM_GITLABSETTINGS_ENABLE' => 'false',
+      'MM_GITLABSETTINGS_ID' => '',
+      'MM_GITLABSETTINGS_SCOPE' => '',
+      'MM_GITLABSETTINGS_SECRET' => '',
+      'MM_GITLABSETTINGS_TOKENENDPOINT' => 'http://gitlab.example.com/oauth/token',
+      'MM_GITLABSETTINGS_USERAPIENDPOINT' => 'http://gitlab.example.com/api/v4/user',
+      'MM_INSTALL_TYPE' => 'gitlab_omnibus',
+      'MM_LOGSETTINGS_FILELOCATION' => '/var/log/gitlab/mattermost',
+      'MM_PLUGINSETTINGS_CLIENTDIRECTORY' => '/var/opt/gitlab/mattermost/client-plugins',
+      'MM_PLUGINSETTINGS_DIRECTORY' => '/var/opt/gitlab/mattermost/plugins',
+      'MM_SERVICESETTINGS_ALLOWEDUNTRUSTEDINTERNALCONNECTIONS' => ' gitlab.example.com',
+      'MM_SERVICESETTINGS_ENABLEAPITEAMDELETION' => 'true',
+      'MM_SERVICESETTINGS_LISTENADDRESS' => '127.0.0.1:8065',
+      'MM_SERVICESETTINGS_SITEURL' => 'http://mattermost.example.com',
+      'MM_SQLSETTINGS_ATRESTENCRYPTKEY' => 'aabbccddeeff11223344556677889900',
+      'MM_SQLSETTINGS_DATASOURCE' => 'user=gitlab_mattermost host=/var/opt/gitlab/postgresql port=5432 dbname=mattermost_production',
+      'MM_SQLSETTINGS_DRIVERNAME' => 'postgres',
+      'MM_TEAMSETTINGS_SITENAME' => 'GitLab Mattermost',
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    stub_gitlab_rb(external_url: 'http://gitlab.example.com', mattermost_external_url: 'http://mattermost.example.com')
+    allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
+    allow_any_instance_of(PgHelper).to receive(:database_exists?).and_return(true)
+    allow(SecretsHelper).to receive(:generate_hex).with(64).and_return('aaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000')
+    allow(SecretsHelper).to receive(:generate_hex).with(16).and_return('aabbccddeeff11223344556677889900')
+  end
+
+  context 'by default' do
+    it 'creates a default VERSION file and restarts service' do
+      expect(chef_run).to create_version_file('Create version file for Mattermost').with(
+        version_file_path: '/var/opt/gitlab/mattermost/VERSION',
+        version_check_cmd: 'cat /opt/gitlab/embedded/service/mattermost/VERSION'
+      )
+
+      expect(chef_run.version_file('Create version file for Mattermost')).to notify('runit_service[mattermost]').to(:hup)
+    end
+  end
+
+  context 'service user and group' do
+    context 'default values' do
+      it_behaves_like "enabled runit service", "mattermost", "root", "root"
+    end
+
+    context 'custom user and group' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'http://gitlab.example.com',
+          mattermost_external_url: 'http://mattermost.example.com',
+          mattermost: {
+            username: 'foo',
+            group: 'bar'
+          }
+        )
+      end
+
+      it_behaves_like "enabled runit service", "mattermost", "root", "root"
+    end
+  end
+
+  context 'SiteUrl setting' do
+    context 'default value' do
+      it 'creates necessary env variable files' do
+        expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
+          variables: default_vars
+        )
+      end
+    end
+
+    context 'when explicitly set' do
+      before do
+        stub_gitlab_rb(mattermost: {
+                         service_site_url: 'http://mattermost.gitlab.example'
+                       })
+      end
+
+      it 'creates necessary env variable files' do
+        expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
+          variables: default_vars.merge({ 'MM_SERVICESETTINGS_SITEURL' => 'http://mattermost.gitlab.example' })
+        )
+      end
+    end
+  end
+
+  it 'authorizes mattermost with gitlab' do
+    allow(MattermostHelper).to receive(:authorize_with_gitlab)
+
+    expect(chef_run).to run_ruby_block('authorize mattermost with gitlab')
+      .at_converge_time
+    expect(MattermostHelper).to receive(:authorize_with_gitlab)
+      .with 'http://gitlab.example.com'
+
+    chef_run.ruby_block('authorize mattermost with gitlab').block.call
+  end
+
+  it 'populates mattermost configuration options to node attributes' do
+    stub_gitlab_rb(mattermost: { enable: true, gitlab_id: 'old' })
+    allow(MattermostHelper).to receive(:authorize_with_gitlab) do |url|
+      Gitlab['mattermost']['gitlab_id'] = 'new'
+    end
+
+    expect(chef_run).to run_ruby_block('populate mattermost configuration options')
+      .at_converge_time
+
+    chef_run.ruby_block('authorize mattermost with gitlab').block.call
+    chef_run.ruby_block('populate mattermost configuration options').block.call
+
+    expect(chef_run.node['mattermost']['gitlab_id']).to eq 'new'
+  end
+
+  context 'populate env variables based on provided gitlab settings' do
+    before do
+      stub_gitlab_rb(mattermost: {
+                       enable: true,
+                       gitlab_enable: true,
+                       gitlab_id: 'gitlab_id',
+                       gitlab_secret: 'gitlab_secret',
+                       gitlab_scope: 'scope',
+                     })
+    end
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
+        variables: default_vars.merge(
+          {
+            'MM_GITLABSETTINGS_ENABLE' => 'true',
+            'MM_GITLABSETTINGS_SECRET' => 'gitlab_secret',
+            'MM_GITLABSETTINGS_ID' => 'gitlab_id',
+            'MM_GITLABSETTINGS_SCOPE' => 'scope',
+            'MM_GITLABSETTINGS_AUTHENDPOINT' => 'http://gitlab.example.com/oauth/authorize',
+            'MM_GITLABSETTINGS_TOKENENDPOINT' => 'http://gitlab.example.com/oauth/token',
+            'MM_GITLABSETTINGS_USERAPIENDPOINT' => 'http://gitlab.example.com/api/v4/user',
+          }
+        )
+      )
+    end
+  end
+
+  context 'allows overrides to the mattermost settings regarding GitLab endpoints' do
+    before do
+      stub_gitlab_rb(mattermost: {
+                       enable: true,
+                       gitlab_enable: true,
+                       gitlab_auth_endpoint: 'https://test-endpoint.example.com/test/auth',
+                       gitlab_token_endpoint: 'https://test-endpoint.example.com/test/token',
+                       gitlab_user_api_endpoint: 'https://test-endpoint.example.com/test/user/api'
+                     })
+    end
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
+        variables: default_vars.merge(
+          {
+            'MM_GITLABSETTINGS_ENABLE' => 'true',
+            'MM_GITLABSETTINGS_AUTHENDPOINT' => 'https://test-endpoint.example.com/test/auth',
+            'MM_GITLABSETTINGS_TOKENENDPOINT' => 'https://test-endpoint.example.com/test/token',
+            'MM_GITLABSETTINGS_USERAPIENDPOINT' => 'https://test-endpoint.example.com/test/user/api'
+          }
+        )
+      )
+    end
+  end
+
+  context 'gitlab is added to untrusted internal connections list' do
+    context 'when no allowed internal connections are provided by gitlab.rb' do
+      it 'creates necessary env variable files' do
+        expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(variables: default_vars)
+      end
+    end
+
+    context 'when some allowed internal connections are provided by gitlab.rb' do
+      before do
+        stub_gitlab_rb(mattermost: { enable: true, service_allowed_untrusted_internal_connections: 'localhost' })
+      end
+
+      it 'creates necessary env variable files' do
+        expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
+          variables: default_vars.merge(
+            {
+              'MM_SERVICESETTINGS_ALLOWEDUNTRUSTEDINTERNALCONNECTIONS' => 'localhost gitlab.example.com'
+            }
+          )
+        )
+      end
+    end
+  end
+
+  shared_examples 'no gitlab authorization performed' do
+    it 'does not authorize mattermost with gitlab' do
+      expect(chef_run).not_to run_ruby_block('authorize mattermost with gitlab')
+    end
+  end
+
+  context 'when gitlab authentication parameters are specified explicitly' do
+    before { stub_gitlab_rb(mattermost: { enable: true, gitlab_enable: true }) }
+
+    it_behaves_like 'no gitlab authorization performed'
+  end
+
+  context 'when gitlab-rails is disabled' do
+    before { stub_gitlab_rb(gitlab_rails: { enable: false }) }
+
+    it_behaves_like 'no gitlab authorization performed'
+
+    it 'does not add gitlab automatically to the list of allowed internal addresses' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
+        variables: default_vars.merge(
+          {
+            'MM_SERVICESETTINGS_ALLOWEDUNTRUSTEDINTERNALCONNECTIONS' => nil
+          }
+        )
+      )
+    end
+  end
+
+  context 'when database is not running' do
+    before { allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(false) }
+
+    it_behaves_like 'no gitlab authorization performed'
+  end
+
+  context 'when mattermost database does not exist' do
+    before { allow_any_instance_of(PgHelper).to receive(:database_exists?).and_return(false) }
+
+    it_behaves_like 'no gitlab authorization performed'
+  end
+
+  context 'when a custom env variable is specified' do
+    before do
+      stub_gitlab_rb(mattermost: { env: { 'IAM' => 'CUSTOMVAR' } })
+    end
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
+        variables: default_vars.merge(
+          {
+            'IAM' => 'CUSTOMVAR'
+          }
+        )
+      )
+    end
+  end
+
+  describe 'letsencrypt' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'https://gitlab.example.com',
+        mattermost_external_url: 'https://mattermost.example.com'
+      )
+
+      allow(File).to receive(:exist?).and_call_original
+    end
+
+    describe 'HTTP to HTTPS redirection' do
+      context 'by default' do
+        it 'is enabled' do
+          expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/gitlab-mattermost-http.conf').with_content("return 301 https://mattermost.example.com:443$request_uri;")
+        end
+      end
+
+      context 'if disabled in gitlab.rb' do
+        before do
+          stub_gitlab_rb(
+            external_url: 'https://gitlab.example.com',
+            mattermost_external_url: 'https://mattermost.example.com',
+            mattermost_nginx: {
+              redirect_http_to_https: false
+            }
+          )
+        end
+
+        it 'is disabled' do
+          expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/gitlab-mattermost-http.conf')
+          expect(chef_run).not_to render_file('/var/opt/gitlab/nginx/conf/gitlab-mattermost.conf').with_content("return 301 https://mattermost.example.com:443$request_uri;")
+        end
+      end
+    end
+
+    context 'default certificate file is missing' do
+      before do
+        allow(File).to receive(:exist?).with('/etc/gitlab/ssl/mattermost.example.com.crt').and_return(false)
+      end
+
+      it 'adds itself to letsencrypt alt_names' do
+        expect(chef_run.node['letsencrypt']['alt_names']).to eql(['mattermost.example.com'])
+      end
+
+      it 'is reflected in the acme_selfsigned' do
+        expect(chef_run).to create_acme_selfsigned('gitlab.example.com').with(
+          alt_names: ['mattermost.example.com']
+        )
+      end
+    end
+
+    context 'default certificate file is present' do
+      before do
+        allow(File).to receive(:exist?).with('/etc/gitlab/ssl/mattermost.example.com.crt').and_return(true)
+      end
+
+      it 'does not alter letsencrypt alt_names' do
+        expect(chef_run.node['letsencrypt']['alt_names']).to eql([])
+      end
+
+      it 'is reflected in the acme_selfsigned' do
+        expect(chef_run).to create_acme_selfsigned('gitlab.example.com').with(
+          alt_names: []
+        )
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/monitoring/libraries/prometheus_helper_spec.rb b/spec/chef/cookbooks/monitoring/libraries/prometheus_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9tb25pdG9yaW5nL2xpYnJhcmllcy9wcm9tZXRoZXVzX2hlbHBlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/monitoring/libraries/prometheus_helper_spec.rb
@@ -0,0 +1,44 @@
+#
+# Copyright:: Copyright (c) 2017 GitLab Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef_helper'
+
+RSpec.describe PrometheusHelper do
+  let(:chef_run) { ChefSpec::SoloRunner.new }
+  subject { described_class.new(chef_run.node) }
+
+  context 'flags for prometheus' do
+    context 'with default options' do
+      it 'returns the correct default config string' do
+        chef_run.converge('gitlab::default')
+        expect(subject.flags('prometheus')).to eq(
+          '--web.listen-address=localhost:9090 --storage.tsdb.path=/var/opt/gitlab/prometheus/data --config.file=/var/opt/gitlab/prometheus/prometheus.yml')
+      end
+    end
+
+    context 'with custom options' do
+      before { allow(Gitlab).to receive(:[]).and_call_original }
+
+      it 'does not return the correct string if any attributes have been changed' do
+        chef_run.node.normal['monitoring']['prometheus']['home'] = '/fake/dir'
+        chef_run.converge('gitlab::default')
+
+        expect(subject.flags('prometheus')).to eq(
+          '--web.listen-address=localhost:9090 --storage.tsdb.path=/fake/dir/data --config.file=/fake/dir/prometheus.yml')
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/monitoring/libraries/prometheus_spec.rb b/spec/chef/cookbooks/monitoring/libraries/prometheus_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9tb25pdG9yaW5nL2xpYnJhcmllcy9wcm9tZXRoZXVzX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/monitoring/libraries/prometheus_spec.rb
@@ -0,0 +1,17 @@
+require 'chef_helper'
+
+RSpec.describe Prometheus do
+  before { Services.add_services('gitlab', Services::BaseServices.list) }
+
+  it 'should return a list of known services' do
+    expect(Prometheus.services).to match_array(%w(
+                                                 prometheus
+                                                 alertmanager
+                                                 grafana
+                                                 node-exporter
+                                                 redis-exporter
+                                                 postgres-exporter
+                                                 gitlab-exporter
+                                               ))
+  end
+end
diff --git a/spec/chef/cookbooks/monitoring/recipes/alertmanager_spec.rb b/spec/chef/cookbooks/monitoring/recipes/alertmanager_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9tb25pdG9yaW5nL3JlY2lwZXMvYWxlcnRtYW5hZ2VyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/monitoring/recipes/alertmanager_spec.rb
@@ -0,0 +1,149 @@
+require 'chef_helper'
+
+alertmanager_yml_output = <<-ALERTMANAGERYML
+  ---
+  global:
+    smtp_from: gitlab-omnibus
+    smtp_smarthost: testhost:25
+  templates: []
+  route:
+    receiver: default-receiver
+    routes: []
+  receivers:
+  - name: default-receiver
+    email_configs:
+    - to: admin@example.com
+  inhibit_rules: []
+ALERTMANAGERYML
+
+RSpec.describe 'monitoring::alertmanager' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  let(:default_vars) do
+    {
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when alertmanager is enabled' do
+    let(:config_template) { chef_run.template('/opt/gitlab/sv/alertmanager/log/config') }
+
+    before do
+      stub_gitlab_rb(
+        prometheus: { enable: true },
+        alertmanager: {
+          enable: true,
+          admin_email: 'admin@example.com',
+        },
+        gitlab_rails: {
+          gitlab_email_from: 'gitlab-omnibus',
+          smtp_enable: true,
+          smtp_address: 'testhost',
+          smtp_port: 25,
+        }
+      )
+    end
+
+    it_behaves_like 'enabled runit service', 'alertmanager', 'root', 'root'
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/alertmanager/env').with_variables(default_vars)
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(config_template).to notify('ruby_block[reload_log_service]')
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/alertmanager/run')
+        .with_content { |content|
+          expect(content).to match(/exec chpst -P/)
+          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/alertmanager/)
+          expect(content).to match(/alertmanager.yml/)
+        }
+
+      expect(chef_run).to render_file('/var/opt/gitlab/alertmanager/alertmanager.yml')
+        .with_content(alertmanager_yml_output.gsub(/^ {2}/, ''))
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/alertmanager/log/run')
+        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/alertmanager/)
+    end
+
+    it 'creates default set of directories' do
+      expect(chef_run).to create_directory('/var/log/gitlab/alertmanager').with(
+        owner: 'gitlab-prometheus',
+        group: nil,
+        mode: '0700'
+      )
+      expect(chef_run).to create_directory('/var/opt/gitlab/alertmanager').with(
+        owner: 'gitlab-prometheus',
+        group: nil,
+        mode: '0750'
+      )
+    end
+
+    it 'should create a gitlab-prometheus user account' do
+      expect(chef_run).to create_account('Prometheus user and group').with(username: 'gitlab-prometheus')
+    end
+
+    it 'sets a default listen address' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/alertmanager/run')
+        .with_content(/web.listen-address=localhost:9093/)
+    end
+  end
+
+  context 'with user provided settings' do
+    before do
+      stub_gitlab_rb(
+        prometheus: { enable: true },
+        alertmanager: {
+          listen_address: ':9093',
+          enable: true,
+          env: {
+            'USER_SETTING' => 'asdf1234'
+          },
+          global: {
+            'smtp_from' => 'override_value'
+          }
+        },
+        gitlab_rails: {
+          smtp_enable: true,
+          smtp_address: 'other-testhost',
+          smtp_port: 465,
+          smtp_from: 'default_value'
+        }
+      )
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/alertmanager/run')
+        .with_content(/web.listen-address=:9093/)
+    end
+
+    it 'keeps the defaults that the user did not override' do
+      expect(chef_run).to render_file('/var/opt/gitlab/alertmanager/alertmanager.yml')
+        .with_content(/receiver: default-receiver/)
+    end
+
+    it 'renders alertmanager.yml with the non-default value' do
+      expect(chef_run).to render_file('/var/opt/gitlab/alertmanager/alertmanager.yml')
+        .with_content(/smtp_smarthost: other-testhost:465/)
+    end
+
+    it 'renders alertmanager.yml with the user override value' do
+      expect(chef_run).to render_file('/var/opt/gitlab/alertmanager/alertmanager.yml')
+        .with_content(/smtp_from: override_value/)
+    end
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/alertmanager/env').with_variables(
+        default_vars.merge(
+          {
+            'USER_SETTING' => 'asdf1234'
+          }
+        )
+      )
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/monitoring/recipes/gitlab-exporter_spec.rb b/spec/chef/cookbooks/monitoring/recipes/gitlab-exporter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9tb25pdG9yaW5nL3JlY2lwZXMvZ2l0bGFiLWV4cG9ydGVyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/monitoring/recipes/gitlab-exporter_spec.rb
@@ -0,0 +1,151 @@
+require 'chef_helper'
+
+RSpec.describe 'monitoring::gitlab-exporter' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  let(:default_env_vars) do
+    {
+      'LD_PRELOAD' => '/opt/gitlab/embedded/lib/libjemalloc.so',
+      'MALLOC_CONF' => 'dirty_decay_ms:0,muzzy_decay_ms:0',
+      'RUBY_GC_HEAP_INIT_SLOTS' => 80000,
+      'RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO' => 0.055,
+      'RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO' => 0.111,
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
+      'SSL_CERT_FILE' => '/opt/gitlab/embedded/ssl/cert.pem'
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when gitlab-exporter is enabled' do
+    let(:config_template) { chef_run.template('/opt/gitlab/sv/gitlab-exporter/log/config') }
+
+    before do
+      stub_gitlab_rb(
+        gitlab_exporter: { enable: true }
+      )
+    end
+
+    it 'creates a default VERSION file and restarts service' do
+      expect(chef_run).to create_version_file('Create version file for GitLab-Exporter').with(
+        version_file_path: '/var/opt/gitlab/gitlab-exporter/RUBY_VERSION',
+        version_check_cmd: '/opt/gitlab/embedded/bin/ruby --version'
+      )
+
+      expect(chef_run.version_file('Create version file for GitLab-Exporter')).to notify('runit_service[gitlab-exporter]').to(:restart)
+    end
+
+    it_behaves_like 'enabled runit service', 'gitlab-exporter', 'root', 'root'
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-exporter/env').with_variables(default_env_vars)
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(config_template).to notify('ruby_block[reload_log_service]')
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/gitlab-exporter/run')
+        .with_content { |content|
+          expect(content).to match(/exec chpst -P/)
+          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/gitlab-exporter/)
+        }
+
+      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-exporter/gitlab-exporter.yml')
+        .with_content { |content|
+          # Not disabling this Cop fails the test with:
+          # Psych::BadAlias: Unknown alias: db_common
+          settings = YAML.load(content) # rubocop:disable Security/YAMLLoad
+          expect(settings.dig('server', 'name')).to eq('webrick')
+          expect(settings.dig('probes', 'database')).not_to be_nil
+          expect(settings.dig('probes', 'ruby')).not_to be_nil
+          expect(settings.dig('probes', 'metrics', 'rows_count')).not_to be_nil
+
+          expect(content).to match(/host=\/var\/opt\/gitlab\/postgresql/)
+          expect(content).to match(/redis_enable_client: true/)
+        }
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/gitlab-exporter/log/run')
+        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/gitlab-exporter/)
+    end
+
+    it 'creates default set of directories' do
+      expect(chef_run).to create_directory('/var/log/gitlab/gitlab-exporter').with(
+        owner: 'git',
+        group: nil,
+        mode: '0700'
+      )
+    end
+  end
+
+  context 'with custom user and group' do
+    before do
+      stub_gitlab_rb(
+        gitlab_exporter: {
+          enable: true
+        },
+        user: {
+          username: 'foo',
+          group: 'bar'
+        }
+      )
+    end
+
+    it_behaves_like 'enabled runit service', 'gitlab-exporter', 'root', 'root'
+  end
+
+  context 'when gitlab-exporter is enabled and postgres is disabled' do
+    let(:config_template) { chef_run.template('/opt/gitlab/sv/gitlab-exporter/log/config') }
+
+    before do
+      stub_gitlab_rb(
+        gitlab_exporter: { enable: true },
+        gitlab_rails: { db_host: 'postgres.example.com', db_port: '5432', db_password: 'secret' },
+        postgresql: { enabled: false }
+      )
+    end
+
+    it 'populates a config with a remote host' do
+      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-exporter/gitlab-exporter.yml')
+        .with_content { |content|
+          expect(content).to match(/host=postgres\.example\.com/)
+          expect(content).to match(/port=5432/)
+          expect(content).to match(/password=secret/)
+        }
+    end
+  end
+
+  context 'with custom Redis settings' do
+    before do
+      stub_gitlab_rb(
+        gitlab_exporter: { enable: true },
+        gitlab_rails: { redis_enable_client: false }
+      )
+    end
+
+    it 'disables Redis CLIENT' do
+      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-exporter/gitlab-exporter.yml')
+        .with_content { |content|
+          expect(content).to match(/redis_enable_client: false/)
+        }
+    end
+  end
+
+  context 'when log dir is changed' do
+    before do
+      stub_gitlab_rb(
+        gitlab_exporter: {
+          log_directory: 'foo',
+          enable: true
+        }
+      )
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/gitlab-exporter/log/run')
+        .with_content(/exec svlogd -tt foo/)
+    end
+  end
+
+  include_examples "consul service discovery", "gitlab_exporter", "gitlab-exporter"
+end
diff --git a/spec/chef/cookbooks/monitoring/recipes/grafana_spec.rb b/spec/chef/cookbooks/monitoring/recipes/grafana_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9tb25pdG9yaW5nL3JlY2lwZXMvZ3JhZmFuYV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/monitoring/recipes/grafana_spec.rb
@@ -0,0 +1,356 @@
+require 'chef_helper'
+
+default_dashboards_yml_output = <<-DASHBOARDYML
+---
+apiVersion: 1
+providers:
+- name: GitLab Omnibus
+  orgId: 1
+  folder: GitLab Omnibus
+  type: file
+  disableDeletion: true
+  updateIntervalSeconds: 600
+  options:
+    path: "/opt/gitlab/embedded/service/grafana-dashboards"
+DASHBOARDYML
+
+default_datasources_yml_output = <<-DATASOURCEYML
+---
+apiVersion: 1
+datasources:
+- name: GitLab Omnibus
+  type: prometheus
+  access: proxy
+  url: http://localhost:9090
+DATASOURCEYML
+
+RSpec.describe 'monitoring::grafana' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  let(:default_vars) do
+    {
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
+    allow_any_instance_of(PgHelper).to receive(:database_exists?).and_return(true)
+  end
+
+  context 'when grafana is enabled' do
+    let(:config_template) { chef_run.template('/opt/gitlab/sv/grafana/log/config') }
+
+    before do
+      stub_gitlab_rb(
+        grafana: { enable: true },
+        prometheus: { enable: true }
+      )
+    end
+
+    it_behaves_like 'enabled runit service', 'grafana', 'root', 'root'
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/grafana/env').with_variables(default_vars)
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(config_template).to notify('ruby_block[reload_log_service]')
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/grafana/run')
+        .with_content { |content|
+          expect(content).to match(/exec chpst -P/)
+          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/grafana-server/)
+          expect(content).to match(/-config '\/var\/opt\/gitlab\/grafana\/grafana.ini'/)
+        }
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/grafana/log/run')
+        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/grafana/)
+    end
+
+    it 'creates default set of directories' do
+      expect(chef_run).to create_directory('/var/log/gitlab/grafana').with(
+        owner: 'gitlab-prometheus',
+        group: nil,
+        mode: '0700'
+      )
+    end
+
+    it 'creates the configuration file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
+        .with_content { |content|
+          expect(content).to match(/http_addr = localhost/)
+          expect(content).to match(/http_port = 3000/)
+          expect(content).to match(/root_url = http:\/\/localhost\/-\/grafana/)
+          expect(content).not_to match(/\[auth\.gitlab\]/)
+          expect(content).to match(/reporting_enabled = true/)
+        }
+    end
+
+    it 'creates a default dashboards file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/grafana/provisioning/dashboards/gitlab_dashboards.yml')
+        .with_content(default_dashboards_yml_output)
+    end
+
+    it 'creates a default datasources file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/grafana/provisioning/datasources/gitlab_datasources.yml')
+        .with_content(default_datasources_yml_output)
+    end
+  end
+
+  context 'when grafana is enabled and prometheus is disabled' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'http://gitlab.example.com',
+        prometheus: { enable: false },
+        grafana: { enable: true }
+      )
+    end
+
+    it 'should create the gitlab-prometheus account if prometheus is disabled' do
+      expect(chef_run).to create_account('Prometheus user and group').with_username('gitlab-prometheus')
+    end
+  end
+
+  context 'when log dir is changed' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'http://gitlab.example.com',
+        grafana: {
+          log_directory: 'foo',
+          enable: true
+        }
+      )
+    end
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/grafana/log/run')
+        .with_content(/exec svlogd -tt foo/)
+    end
+  end
+
+  it 'authorizes Grafana with gitlab' do
+    stub_gitlab_rb(external_url: 'http://gitlab.example.com')
+
+    allow(GrafanaHelper).to receive(:authorize_with_gitlab)
+
+    expect(chef_run).to run_ruby_block('authorize Grafana with GitLab')
+      .at_converge_time
+    expect(GrafanaHelper).to receive(:authorize_with_gitlab)
+      .with 'http://gitlab.example.com'
+
+    chef_run.ruby_block('authorize Grafana with GitLab').block.call
+  end
+
+  context 'with user provided settings' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'https://trailingslash.example.com/',
+        grafana: {
+          http_addr: '0.0.0.0',
+          http_port: 3333,
+          enable: true,
+          gitlab_application_id: 'appid',
+          gitlab_secret: 'secret',
+          gitlab_auth_sign_up: false,
+          allowed_groups: %w[
+            allowed
+            also-allowed
+          ],
+          smtp: {
+            enabled: true,
+            host: 'smtp_server:255',
+            user: 'testuser',
+            password: 'test',
+            cert_file: '/path/to/cert_file.crt',
+            key_file: '/path/to/key_file.key',
+            skip_verify: true,
+            from_address: 'smtp_admin@grafana.gitlab',
+            from_name: 'Grafana Gitlab SMTP',
+            ehlo_identity: 'dashboard.grafama.gitlab',
+            startTLS_policy: 'NoStartTLS'
+          },
+          env: {
+            'USER_SETTING' => 'asdf1234'
+          },
+          dashboards: [
+            {
+              name: 'GitLab Omnibus',
+              orgId: 1,
+              folder: 'GitLab Omnibus',
+              type: 'file',
+              disableDeletion: true,
+              updateIntervalSeconds: 600,
+              options: {
+                path: '/etc/grafana/dashboards',
+              },
+            },
+          ],
+        }
+      )
+    end
+
+    it 'creates a custom dashboards file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/grafana/provisioning/dashboards/gitlab_dashboards.yml')
+        .with_content { |content|
+          expect(content).to match(/options:\n    path: "\/etc\/grafana\/dashboards"\n/)
+        }
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
+        .with_content { |content|
+          expect(content).to match(/http_addr = 0.0.0.0/)
+          expect(content).to match(/http_port = 3333/)
+          expect(content).to match(/root_url = https:\/\/trailingslash.example.com\/-\/grafana/)
+          expect(content).to match(/\[auth\.gitlab\]\nenabled = true\nallow_sign_up = false/)
+          expect(content).to match(/client_id = appid/)
+          expect(content).to match(/client_secret = secret/)
+          expect(content).to match(/auth_url = https:\/\/trailingslash.example.com\/oauth\/authorize/)
+          expect(content).to match(/token_url = https:\/\/trailingslash.example.com\/oauth\/token/)
+          expect(content).to match(/api_url = https:\/\/trailingslash.example.com\/api\/v4/)
+          expect(content).to match(/allowed_groups = allowed also-allowed/)
+          expect(content).to match(/\[metrics\]\n#.+\nenabled = false/)
+          expect(content).not_to match(/basic_auth_username/)
+          expect(content).not_to match(/basic_auth_password/)
+          expect(content).to match(/scopes = read_api/)
+          # smtp configuration tests
+          expect(content).to match(/\[smtp\]\nenabled = true/)
+          expect(content).to match(/host = smtp_server:255/)
+          expect(content).to match(/user = testuser/)
+          expect(content).to match(/password = test/)
+          expect(content).to match(/cert_file = \/path\/to\/cert_file.crt/)
+          expect(content).to match(/key_file = \/path\/to\/key_file.key/)
+          expect(content).to match(/skip_verify = true/)
+          expect(content).to match(/from_address = smtp_admin@grafana.gitlab/)
+          expect(content).to match(/from_name = Grafana Gitlab SMTP/)
+          expect(content).to match(/ehlo_identity = dashboard.grafama.gitlab/)
+          expect(content).to match(/startTLS_policy = NoStartTLS/)
+        }
+    end
+
+    it 'generates grafana metrics basic auth password when enabled' do
+      stub_gitlab_rb(
+        grafana: {
+          metrics_enabled: true
+        }
+      )
+
+      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
+        .with_content { |content|
+          expect(content).to match(/\[metrics\]\n#.+\nenabled = true/)
+          expect(content).not_to match(/basic_auth_username/)
+          expect(content).to match(/basic_auth_password = """\S+"""/)
+        }
+    end
+
+    it 'adds metrics basic_auth_username when set' do
+      stub_gitlab_rb(
+        grafana: {
+          metrics_enabled: true,
+          metrics_basic_auth_username: 'user'
+        }
+      )
+
+      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
+        .with_content { |content|
+          expect(content).to match(/\[metrics\]\n#.+\nenabled = true/)
+          expect(content).to match(/basic_auth_username = """user"""/)
+          expect(content).to match(/basic_auth_password = """\S+"""/)
+        }
+    end
+
+    it 'can use special chars in metrics basic auth username and password' do
+      stub_gitlab_rb(
+        grafana: {
+          metrics_enabled: true,
+          metrics_basic_auth_username: '#user;@',
+          metrics_basic_auth_password: 'password_#;@'
+        }
+      )
+
+      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
+        .with_content { |content|
+          expect(content).to match(/\[metrics\]\n#.+\nenabled = true/)
+          expect(content).to match(/basic_auth_username = """#user;@"""/)
+          expect(content).to match(/basic_auth_password = """password_#;@"""/)
+        }
+    end
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/grafana/env').with_variables(
+        default_vars.merge(
+          {
+            'USER_SETTING' => 'asdf1234'
+          }
+        )
+      )
+    end
+
+    it 'does not deduplicate attributes outside its own scope' do
+      queue_groups = ['geo,post_receive,cronjob', 'geo,post_receive,cronjob', 'geo,post_receive,cronjob']
+
+      stub_gitlab_rb(
+        grafana: {
+          metrics_enabled: true,
+        },
+        sidekiq: {
+          enable: true,
+          queue_groups: queue_groups
+        }
+      )
+
+      block = chef_run.ruby_block('populate Grafana configuration options')
+      block.block.call
+
+      expect(chef_run.node['gitlab']['sidekiq']['queue_groups']).to eq(queue_groups)
+    end
+
+    it 'disables reporting when usage_ping_enabled is disabled' do
+      stub_gitlab_rb(
+        gitlab_rails: {
+          usage_ping_enabled: false
+        }
+      )
+
+      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
+        .with_content { |content|
+          expect(content).to match(/reporting_enabled = false/)
+        }
+    end
+
+    it 'disables reporting when usage_ping_enabled is enabled but is explicitly disabled' do
+      stub_gitlab_rb(
+        gitlab_rails: {
+          usage_ping_enabled: true
+        },
+        grafana: {
+          reporting_enabled: false
+        }
+      )
+
+      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
+        .with_content { |content|
+          expect(content).to match(/reporting_enabled = false/)
+        }
+    end
+
+    context 'without allowed_groups specified' do
+      before do
+        stub_gitlab_rb(
+          external_url: 'https://trailingslash.example.com/',
+          grafana: {
+            http_addr: '0.0.0.0',
+            http_port: 3333,
+            enable: true,
+            gitlab_application_id: 'appid',
+            gitlab_secret: 'secret'
+          }
+        )
+      end
+
+      it 'sets auth scope to read_user' do
+        expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini').with_content(/scopes = read_user/)
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/monitoring/recipes/node-exporter_spec.rb b/spec/chef/cookbooks/monitoring/recipes/node-exporter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9tb25pdG9yaW5nL3JlY2lwZXMvbm9kZS1leHBvcnRlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/monitoring/recipes/node-exporter_spec.rb
@@ -0,0 +1,149 @@
+require 'chef_helper'
+
+RSpec.describe 'monitoring::node-exporter' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  let(:default_vars) do
+    {
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when node-exporter is enabled' do
+    let(:config_template) { chef_run.template('/opt/gitlab/sv/node-exporter/log/config') }
+
+    before do
+      stub_gitlab_rb(
+        node_exporter: { enable: true }
+      )
+    end
+
+    it_behaves_like 'enabled runit service', 'node-exporter', 'root', 'root'
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/node-exporter/env').with_variables(default_vars)
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(config_template).to notify('ruby_block[reload_log_service]')
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/node-exporter/run')
+        .with_content { |content|
+          expect(content).to match(/exec chpst -P/)
+          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/node_exporter/)
+          expect(content).to match(/--collector\.mountstats /)
+          expect(content).to match(/--collector\.runit /)
+          expect(content).to match(/--collector\.runit.servicedir=\/opt\/gitlab\/sv /)
+          expect(content).to match(/\/textfile_collector/)
+        }
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/node-exporter/log/run')
+        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/node-exporter/)
+    end
+
+    it 'creates default set of directories' do
+      expect(chef_run).to create_directory('/var/log/gitlab/node-exporter').with(
+        owner: 'gitlab-prometheus',
+        group: nil,
+        mode: '0700'
+      )
+      expect(chef_run).to create_directory('/var/opt/gitlab/node-exporter/textfile_collector').with(
+        owner: 'gitlab-prometheus',
+        group: nil,
+        mode: '0755'
+      )
+    end
+
+    it 'sets a default listen address' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/node-exporter/run')
+        .with_content(/web.listen-address=localhost:9100/)
+    end
+  end
+
+  context 'when node-exporter is enabled and prometheus is disabled' do
+    before do
+      stub_gitlab_rb(
+        prometheus: { enable: false },
+        node_exporter: { enable: true }
+      )
+    end
+
+    it 'should create the gitlab-prometheus account if prometheus is disabled' do
+      expect(chef_run).to create_account('Prometheus user and group').with_username('gitlab-prometheus')
+    end
+  end
+
+  context 'when node-exporter is enabled and prometheus is enabled' do
+    before do
+      stub_gitlab_rb(
+        prometheus: { enable: true },
+        node_exporter: { enable: true }
+      )
+    end
+
+    it 'creates a node.rules file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/prometheus/rules/node.rules')
+        .with_content(/instance:node_cpus:count/)
+    end
+  end
+
+  context 'when log dir is changed' do
+    before do
+      stub_gitlab_rb(
+        node_exporter: {
+          log_directory: 'foo',
+          enable: true
+        }
+      )
+    end
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/node-exporter/log/run')
+        .with_content(/exec svlogd -tt foo/)
+    end
+  end
+
+  context 'with user provided settings' do
+    before do
+      stub_gitlab_rb(
+        node_exporter: {
+          flags: {
+            'collector.textfile.directory' => '/tmp',
+            'collector.arp' => false,
+            'collector.mountstats' => false
+          },
+          listen_address: 'localhost:9899',
+          enable: true,
+          env: {
+            'USER_SETTING' => 'asdf1234'
+          }
+        }
+      )
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/node-exporter/run')
+        .with_content { |content|
+          expect(content).to match(/web\.listen-address=localhost:9899/)
+          expect(content).to match(/collector\.textfile\.directory=\/tmp/)
+          expect(content).to match(/--no-collector\.arp/)
+          expect(content).to match(/--no-collector\.mountstats/)
+          expect(content).to match(/--collector\.runit/)
+        }
+    end
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/node-exporter/env').with_variables(
+        default_vars.merge(
+          {
+            'USER_SETTING' => 'asdf1234'
+          }
+        )
+      )
+    end
+  end
+
+  include_examples "consul service discovery", "node_exporter", "node-exporter"
+end
diff --git a/spec/chef/cookbooks/monitoring/recipes/pgbouncer-exporter_spec.rb b/spec/chef/cookbooks/monitoring/recipes/pgbouncer-exporter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9tb25pdG9yaW5nL3JlY2lwZXMvcGdib3VuY2VyLWV4cG9ydGVyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/monitoring/recipes/pgbouncer-exporter_spec.rb
@@ -0,0 +1,70 @@
+#
+# Copyright:: Copyright (c) 2018 GitLab Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'chef_helper'
+
+RSpec.describe 'monitoring::pgbouncer-exporter' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
+  let(:config_yaml) { '/var/opt/gitlab/pgbouncer-exporter/pgbouncer-exporter.yaml' }
+  let(:default_vars) do
+    {
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe 'when enabled' do
+    before do
+      stub_gitlab_rb(
+        pgbouncer_exporter: {
+          enable: true
+        },
+        pgbouncer: {
+          enable: true,
+          databases: {
+            gitlabhq_production: {
+              host: '1.2.3.4'
+            }
+          }
+        },
+        postgresql: {
+          pgbouncer_user: 'fakeuser',
+          pgbouncer_user_password: 'fakeuserpassword'
+        }
+      )
+    end
+
+    it 'includes the pgbouncer-exporter recipe' do
+      expect(chef_run).to include_recipe('monitoring::pgbouncer-exporter')
+    end
+
+    it 'includes the postgresql user recipe' do
+      expect(chef_run).to include_recipe('postgresql::user')
+    end
+
+    it_behaves_like 'enabled runit service', 'pgbouncer-exporter', 'root', 'root'
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/pgbouncer-exporter/env').with_variables(default_vars)
+    end
+
+    it 'creates the appropriate directories' do
+      expect(chef_run).to create_directory('/var/log/gitlab/pgbouncer-exporter')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/monitoring/recipes/postgres_exporter_spec.rb b/spec/chef/cookbooks/monitoring/recipes/postgres_exporter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9tb25pdG9yaW5nL3JlY2lwZXMvcG9zdGdyZXNfZXhwb3J0ZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/monitoring/recipes/postgres_exporter_spec.rb
@@ -0,0 +1,194 @@
+require 'chef_helper'
+
+RSpec.describe 'monitoring::postgres-exporter' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  let(:node) { chef_run.node }
+  let(:default_vars) do
+    {
+      'DATA_SOURCE_NAME' => 'host=/var/opt/gitlab/postgresql user=gitlab-psql database=gitlabhq_production',
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when postgres is disabled locally' do
+    before do
+      stub_gitlab_rb(
+        postgresql: { enable: false }
+      )
+    end
+
+    it 'defaults the postgres-exporter to being disabled' do
+      expect(node['monitoring']['postgres-exporter']['enable']).to eq false
+    end
+
+    it 'allows postgres-exporter to be explicitly enabled' do
+      stub_gitlab_rb(postgres_exporter: { enable: true })
+
+      expect(node['monitoring']['postgres-exporter']['enable']).to eq true
+    end
+
+    it 'uses gitlab-rails db_host for the database host if postgres-exporter is explicitly enabled' do
+      stub_gitlab_rb(
+        postgres_exporter: { enable: true },
+        gitlab_rails: {
+          db_host: '10.0.0.1',
+          db_port: '4242',
+          db_username: 'foo',
+          db_password: 'bar',
+          db_database: 'baz'
+        }
+      )
+
+      expect(node['monitoring']['postgres-exporter']['env']['DATA_SOURCE_NAME'])
+        .to eq "host=10.0.0.1 port=4242 user=foo password=bar database=baz"
+    end
+  end
+
+  context 'when postgres is enabled locally' do
+    before do
+      stub_gitlab_rb(
+        postgresql: { enable: true }
+      )
+    end
+
+    it 'uses the local postgres dir for the database host' do
+      stub_gitlab_rb(
+        postgres_exporter: { enable: true },
+        gitlab_rails: { db_host: '10.0.0.1' },
+        postgresql: { dir: '/dir/to/postgresql' }
+      )
+
+      expect(node['monitoring']['postgres-exporter']['env']['DATA_SOURCE_NAME'])
+        .to eq "host=/dir/to/postgresql user=gitlab-psql database=gitlabhq_production"
+    end
+  end
+
+  context 'when postgres-exporter is enabled' do
+    let(:config_template) { chef_run.template('/opt/gitlab/sv/postgres-exporter/log/config') }
+
+    before do
+      stub_gitlab_rb(
+        postgres_exporter: { enable: true }
+      )
+    end
+
+    it_behaves_like 'enabled runit service', 'postgres-exporter', 'root', 'root'
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/postgres-exporter/env').with_variables(default_vars)
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(config_template).to notify('ruby_block[reload_log_service]')
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/run')
+        .with_content { |content|
+          expect(content).to match(/exec chpst /)
+          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/postgres_exporter/)
+        }
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/log/run')
+        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/postgres-exporter/)
+    end
+
+    it 'creates the queries.yaml file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/postgres-exporter/queries.yaml')
+        .with_content { |content|
+          expect(content).to match(/pg_replication:/)
+          expect(content).not_to match(/pg_stat_user_table:/)
+        }
+    end
+
+    it 'creates default set of directories' do
+      expect(chef_run).to create_directory('/var/log/gitlab/postgres-exporter').with(
+        owner: 'gitlab-psql',
+        group: nil,
+        mode: '0700'
+      )
+    end
+
+    it 'sets default flags' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/run')
+        .with_content { |content|
+          expect(content).to match(/web.listen-address=localhost:9187/)
+          expect(content).to match(/extend.query-path=\/var\/opt\/gitlab\/postgres-exporter\/queries.yaml/)
+        }
+    end
+  end
+
+  context 'when enabled and run as an isolated recipe' do
+    let(:chef_run) { converge_config('monitoring::postgres-exporter') }
+    before do
+      stub_gitlab_rb(postgres_exporter: { enable: true })
+    end
+
+    it 'includes the postgresql_user recipe' do
+      expect(chef_run).to include_recipe('postgresql::user')
+    end
+  end
+
+  context 'when log dir is changed' do
+    before do
+      stub_gitlab_rb(
+        postgres_exporter: {
+          log_directory: 'foo',
+          enable: true
+        }
+      )
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/log/run')
+        .with_content(/exec svlogd -tt foo/)
+    end
+  end
+
+  context 'with user provided settings' do
+    before do
+      stub_gitlab_rb(
+        postgres_exporter: {
+          flags: {
+            'some.flag' => 'foo'
+          },
+          listen_address: 'localhost:9700',
+          enable: true,
+          per_table_stats: true,
+          sslmode: 'require',
+          env: {
+            'USER_SETTING' => 'asdf1234'
+          }
+        }
+      )
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/run')
+        .with_content(/web.listen-address=localhost:9700/)
+      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/run')
+        .with_content(/some.flag=foo/)
+    end
+
+    it 'creates the queries.yaml file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/postgres-exporter/queries.yaml')
+        .with_content(/pg_stat_user_tables:/)
+    end
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/postgres-exporter/env').with_variables(
+        default_vars.merge(
+          {
+            'DATA_SOURCE_NAME' => 'host=/var/opt/gitlab/postgresql user=gitlab-psql '\
+                                  'database=gitlabhq_production sslmode=require',
+            'USER_SETTING' => 'asdf1234'
+          }
+        )
+      )
+    end
+  end
+
+  include_examples "consul service discovery", "postgres_exporter", "postgres-exporter"
+end
diff --git a/spec/chef/cookbooks/monitoring/recipes/prometheus_spec.rb b/spec/chef/cookbooks/monitoring/recipes/prometheus_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9tb25pdG9yaW5nL3JlY2lwZXMvcHJvbWV0aGV1c19zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/monitoring/recipes/prometheus_spec.rb
@@ -0,0 +1,312 @@
+require 'chef_helper'
+
+prometheus_yml_output = <<-PROMYML
+  ---
+  global:
+    scrape_interval: 15s
+    scrape_timeout: 15s
+    external_labels: {}
+  remote_read: []
+  remote_write: []
+  rule_files:
+  - "/var/opt/gitlab/prometheus/rules/*.rules"
+  scrape_configs:
+  - job_name: prometheus
+    static_configs:
+    - targets:
+      - localhost:9090
+  - job_name: nginx
+    static_configs:
+    - targets:
+      - localhost:8060
+  - job_name: redis
+    static_configs:
+    - targets:
+      - localhost:9121
+  - job_name: postgres
+    static_configs:
+    - targets:
+      - localhost:9187
+  - job_name: node
+    static_configs:
+    - targets:
+      - localhost:9100
+  - job_name: gitlab-workhorse
+    static_configs:
+    - targets:
+      - localhost:9229
+  - job_name: gitlab-rails
+    metrics_path: "/-/metrics"
+    static_configs:
+    - targets:
+      - 127.0.0.1:8080
+    relabel_configs:
+    - source_labels:
+      - __address__
+      regex: 127.0.0.1:(.*)
+      replacement: localhost:$1
+      target_label: instance
+  - job_name: gitlab-sidekiq
+    static_configs:
+    - targets:
+      - 127.0.0.1:8082
+    relabel_configs:
+    - source_labels:
+      - __address__
+      regex: 127.0.0.1:(.*)
+      replacement: localhost:$1
+      target_label: instance
+  - job_name: registry
+    static_configs:
+    - targets:
+      - localhost:5001
+  - job_name: gitlab_exporter_database
+    metrics_path: "/database"
+    static_configs:
+    - targets:
+      - localhost:9168
+  - job_name: gitlab_exporter_sidekiq
+    metrics_path: "/sidekiq"
+    static_configs:
+    - targets:
+      - localhost:9168
+  - job_name: gitlab_exporter_ruby
+    metrics_path: "/ruby"
+    static_configs:
+    - targets:
+      - localhost:9168
+  - job_name: gitaly
+    static_configs:
+    - targets:
+      - localhost:9236
+  - job_name: kubernetes-cadvisor
+    scheme: https
+    tls_config:
+      ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+      insecure_skip_verify: true
+    bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+    kubernetes_sd_configs:
+    - role: node
+      api_server: https://kubernetes.default.svc:443
+      tls_config:
+        ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+      bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+    relabel_configs:
+    - action: labelmap
+      regex: __meta_kubernetes_node_label_(.+)
+    - target_label: __address__
+      replacement: kubernetes.default.svc:443
+    - source_labels:
+      - __meta_kubernetes_node_name
+      regex: "(.+)"
+      target_label: __metrics_path__
+      replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor"
+    metric_relabel_configs:
+    - source_labels:
+      - pod_name
+      target_label: environment
+      regex: "(.+)-.+-.+"
+  - job_name: kubernetes-nodes
+    scheme: https
+    tls_config:
+      ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+      insecure_skip_verify: true
+    bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+    kubernetes_sd_configs:
+    - role: node
+      api_server: https://kubernetes.default.svc:443
+      tls_config:
+        ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+      bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+    relabel_configs:
+    - action: labelmap
+      regex: __meta_kubernetes_node_label_(.+)
+    - target_label: __address__
+      replacement: kubernetes.default.svc:443
+    - source_labels:
+      - __meta_kubernetes_node_name
+      regex: "(.+)"
+      target_label: __metrics_path__
+      replacement: "/api/v1/nodes/${1}/proxy/metrics"
+    metric_relabel_configs:
+    - source_labels:
+      - pod_name
+      target_label: environment
+      regex: "(.+)-.+-.+"
+  - job_name: kubernetes-pods
+    tls_config:
+      ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+      insecure_skip_verify: true
+    bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+    kubernetes_sd_configs:
+    - role: pod
+      api_server: https://kubernetes.default.svc:443
+      tls_config:
+        ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+      bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+    relabel_configs:
+    - source_labels:
+      - __meta_kubernetes_pod_annotation_prometheus_io_scrape
+      action: keep
+      regex: 'true'
+    - source_labels:
+      - __meta_kubernetes_pod_annotation_prometheus_io_path
+      action: replace
+      target_label: __metrics_path__
+      regex: "(.+)"
+    - source_labels:
+      - __address__
+      - __meta_kubernetes_pod_annotation_prometheus_io_port
+      action: replace
+      regex: "([^:]+)(?::[0-9]+)?;([0-9]+)"
+      replacement: "$1:$2"
+      target_label: __address__
+    - action: labelmap
+      regex: __meta_kubernetes_pod_label_(.+)
+    - source_labels:
+      - __meta_kubernetes_namespace
+      action: replace
+      target_label: kubernetes_namespace
+    - source_labels:
+      - __meta_kubernetes_pod_name
+      action: replace
+      target_label: kubernetes_pod_name
+  alerting:
+    alertmanagers:
+    - static_configs:
+      - targets:
+        - localhost:9093
+PROMYML
+
+RSpec.describe 'monitoring::prometheus' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  let(:default_vars) do
+    {
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when prometheus is enabled' do
+    let(:config_template) { chef_run.template('/opt/gitlab/sv/prometheus/log/config') }
+
+    before do
+      stub_gitlab_rb(
+        alertmanager: {
+          enable: true
+        },
+        prometheus: {
+          enable: true
+        },
+        gitlab_exporter: {
+          enable: true
+        },
+        registry: {
+          enable: true,
+          debug_addr: 'localhost:5001'
+        }
+      )
+    end
+
+    it_behaves_like 'enabled runit service', 'prometheus', 'root', 'root'
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/prometheus/env').with_variables(default_vars)
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(config_template).to notify('ruby_block[reload_log_service]')
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/prometheus/run')
+        .with_content { |content|
+                            expect(content).to match(/exec chpst -P/)
+                            expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/prometheus/)
+                            expect(content).to match(/prometheus.yml/)
+                          }
+
+      expect(chef_run).to render_file('/var/opt/gitlab/prometheus/prometheus.yml')
+        .with_content(prometheus_yml_output.gsub(/^ {2}/, ''))
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/prometheus/log/run')
+        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/prometheus/)
+    end
+
+    it 'creates default set of directories' do
+      expect(chef_run).to create_directory('/var/log/gitlab/prometheus').with(
+        owner: 'gitlab-prometheus',
+        group: nil,
+        mode: '0700'
+      )
+      expect(chef_run).to create_directory('/var/opt/gitlab/prometheus').with(
+        owner: 'gitlab-prometheus',
+        group: nil,
+        mode: '0750'
+      )
+    end
+
+    it 'should create a gitlab-prometheus user and group' do
+      expect(chef_run).to create_account('Prometheus user and group').with(username: 'gitlab-prometheus', groupname: 'gitlab-prometheus')
+    end
+
+    it 'sets a default listen address' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/prometheus/run')
+        .with_content(/web.listen-address=localhost:9090/)
+    end
+  end
+
+  context 'by default' do
+    context 'with user provided settings' do
+      it 'configures puma job' do
+        expect(chef_run).to render_file('/var/opt/gitlab/prometheus/prometheus.yml')
+          .with_content(%r{- job_name: gitlab-rails\s+metrics_path: "/-/metrics"\s+static_configs:\s+- targets:\s+- 127.0.0.1:8080})
+      end
+    end
+  end
+
+  context 'rules directory' do
+    context 'default settings' do
+      it 'creates rules directory in correct location' do
+        expect(chef_run).to create_directory("/var/opt/gitlab/prometheus/rules")
+        expect(chef_run).to render_file("/var/opt/gitlab/prometheus/rules/node.rules")
+        expect(chef_run).to render_file("/var/opt/gitlab/prometheus/rules/gitlab.rules")
+      end
+    end
+
+    context 'user specified home directory' do
+      before do
+        stub_gitlab_rb(
+          prometheus: {
+            home: "/var/opt/gitlab/prometheus-bak"
+          }
+        )
+      end
+
+      it 'creates rules directory in correct location' do
+        expect(chef_run).to create_directory("/var/opt/gitlab/prometheus-bak/rules")
+        expect(chef_run).to render_file("/var/opt/gitlab/prometheus-bak/rules/node.rules")
+        expect(chef_run).to render_file("/var/opt/gitlab/prometheus-bak/rules/gitlab.rules")
+      end
+    end
+
+    context 'user specified rules directory' do
+      before do
+        stub_gitlab_rb(
+          prometheus: {
+            rules_directory: "/var/opt/gitlab/prometheus/alert-rules"
+          }
+        )
+      end
+
+      it 'creates rules directory in correct location' do
+        expect(chef_run).to create_directory("/var/opt/gitlab/prometheus/alert-rules")
+        expect(chef_run).to render_file("/var/opt/gitlab/prometheus/alert-rules/node.rules")
+        expect(chef_run).to render_file("/var/opt/gitlab/prometheus/alert-rules/gitlab.rules")
+      end
+    end
+  end
+
+  include_examples "consul service discovery", "prometheus", "prometheus"
+end
diff --git a/spec/chef/cookbooks/monitoring/recipes/redis_exporter_spec.rb b/spec/chef/cookbooks/monitoring/recipes/redis_exporter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9tb25pdG9yaW5nL3JlY2lwZXMvcmVkaXNfZXhwb3J0ZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/monitoring/recipes/redis_exporter_spec.rb
@@ -0,0 +1,129 @@
+require 'chef_helper'
+
+RSpec.describe 'monitoring::redis-exporter' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  let(:node) { chef_run.node }
+  let(:default_vars) do
+    {
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when redis is disabled locally' do
+    before do
+      stub_gitlab_rb(
+        redis: { enable: false }
+      )
+    end
+
+    it 'defaults the redis-exporter to being disabled' do
+      expect(node['monitoring']['redis-exporter']['enable']).to eq false
+    end
+
+    it 'allows redis-exporter to be explicitly enabled' do
+      stub_gitlab_rb(redis_exporter: { enable: true })
+
+      expect(node['monitoring']['redis-exporter']['enable']).to eq true
+    end
+  end
+
+  context 'when redis-exporter is enabled' do
+    let(:config_template) { chef_run.template('/opt/gitlab/sv/redis-exporter/log/config') }
+
+    before do
+      stub_gitlab_rb(
+        redis_exporter: { enable: true }
+      )
+    end
+
+    it_behaves_like 'enabled runit service', 'redis-exporter', 'root', 'root'
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/redis-exporter/env').with_variables(default_vars)
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(config_template).to notify('ruby_block[reload_log_service]')
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/run')
+        .with_content { |content|
+          expect(content).to match(/exec chpst -P/)
+          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/redis_exporter/)
+        }
+
+      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/log/run')
+        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/redis-exporter/)
+    end
+
+    it 'creates default set of directories' do
+      expect(chef_run).to create_directory('/var/log/gitlab/redis-exporter').with(
+        owner: 'gitlab-redis',
+        group: nil,
+        mode: '0700'
+      )
+    end
+
+    it 'sets default flags' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/run')
+        .with_content(/web.listen-address=localhost:9121/)
+      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/run')
+        .with_content(%r{redis.addr=unix:///var/opt/gitlab/redis/redis.socket})
+    end
+  end
+
+  context 'when log dir is changed' do
+    before do
+      stub_gitlab_rb(
+        redis_exporter: {
+          log_directory: 'foo',
+          enable: true
+        }
+      )
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/log/run')
+        .with_content(/exec svlogd -tt foo/)
+    end
+  end
+
+  context 'with user provided settings' do
+    before do
+      stub_gitlab_rb(
+        redis_exporter: {
+          flags: {
+            'redis.addr' => '/tmp/socket'
+          },
+          listen_address: 'localhost:9900',
+          enable: true,
+          env: {
+            'USER_SETTING' => 'asdf1234'
+          }
+        }
+      )
+    end
+
+    it 'populates the files with expected configuration' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/run')
+        .with_content(/web.listen-address=localhost:9900/)
+      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/run')
+        .with_content(%r{redis.addr=/tmp/socket})
+    end
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/redis-exporter/env').with_variables(
+        default_vars.merge(
+          {
+            'USER_SETTING' => 'asdf1234'
+          }
+        )
+      )
+    end
+  end
+
+  include_examples "consul service discovery", "redis_exporter", "redis-exporter"
+end
diff --git a/spec/chef/cookbooks/package/libraries/config/gitlab_spec.rb b/spec/chef/cookbooks/package/libraries/config/gitlab_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9jb25maWcvZ2l0bGFiX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/config/gitlab_spec.rb
@@ -0,0 +1,78 @@
+require 'spec_helper'
+
+RSpec.describe Gitlab do
+  context 'when using an attribute_block' do
+    it 'sets top level attributes to the provided root' do
+      Gitlab.attribute_block('gitlab') do
+        expect(Gitlab.attribute('test_attribute')[:parent]).to eq 'gitlab'
+      end
+      expect(Gitlab['test_attribute']).not_to be_nil
+      expect(Gitlab.hyphenate_config_keys['gitlab']).to include('test-attribute')
+    end
+  end
+
+  it 'sets top level attributes when no parent is provided' do
+    Gitlab.attribute('test_attribute')
+    expect(Gitlab['test_attribute']).not_to be_nil
+    expect(Gitlab.hyphenate_config_keys).to include('test-attribute')
+  end
+
+  it 'properly defines roles' do
+    role = Gitlab.role('test_node')
+    expect(Gitlab['test_node_role']).not_to be_nil
+    expect(Gitlab.hyphenate_config_keys['roles']).to include('test-node')
+    expect(role).to include(manage_services: true)
+  end
+
+  it 'supports overriding role default configuration' do
+    role = Gitlab.role('test_node', manage_services: false)
+    expect(Gitlab['test_node_role']).not_to be_nil
+    expect(role).to include(manage_services: false)
+  end
+
+  it 'supports overriding attribute default configuration' do
+    attribute = Gitlab.attribute('test_attribute', parent: 'example', priority: 40, enable: false, default: '')
+    expect(Gitlab['test_attribute']).to eq('')
+    expect(attribute).to include(parent: 'example', priority: 40, enable: false)
+  end
+
+  it 'disables ee attributes when EE is not enabled' do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow(Gitlab).to receive(:[]).with('edition').and_return(:ce)
+    expect(Gitlab.ee_attribute('test_attribute')[:ee]).to eq true
+    expect(Gitlab['test_attribute']).not_to be_nil
+    expect(Gitlab.hyphenate_config_keys).not_to include('test-attribute')
+  end
+
+  it 'enables ee attributes when EE is enabled' do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow(Gitlab).to receive(:[]).with('edition').and_return(:ee)
+    expect(Gitlab.ee_attribute('test_attribute')[:ee]).to eq true
+    expect(Gitlab['test_attribute']).not_to be_nil
+    expect(Gitlab.hyphenate_config_keys).to include('test-attribute')
+  end
+
+  it 'sorts attributes by sequence' do
+    Gitlab.attribute('last', priority: 99)
+    Gitlab.attribute('other1')
+    Gitlab.attribute('first', priority: -99)
+    Gitlab.attribute('other2')
+
+    expect(Gitlab.send(:sorted_settings).first[0]).to eq 'first'
+    expect(Gitlab.send(:sorted_settings).last[0]).to eq 'last'
+  end
+
+  it 'filters ee settings when sorting' do
+    Gitlab.attribute('test_attribute1')
+    Gitlab.attribute('test_attribute2', ee: true)
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow(Gitlab).to receive(:[]).with('edition').and_return(:ce)
+    expect(Gitlab.send(:sorted_settings).map(&:first)).to include('test_attribute1')
+    expect(Gitlab.send(:sorted_settings).map(&:first)).not_to include('test_attribute2')
+  end
+
+  it 'allows passing a block to the attribute use method' do
+    attribute = Gitlab.attribute('test_attribute').use { 'test' }
+    expect(attribute.handler).to eq('test')
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/config/roles_spec.rb b/spec/chef/cookbooks/package/libraries/config/roles_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9jb25maWcvcm9sZXNfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/config/roles_spec.rb
@@ -0,0 +1,116 @@
+require 'chef_helper'
+
+RSpec.describe 'GitLabRoles' do
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow(Services).to receive(:enable_group).and_call_original
+  end
+
+  after do
+    # Disable all roles after each test to clean the environment
+    Gitlab.available_roles.each { |name, _value| Gitlab["#{name}_role"]['enable'] = false }
+  end
+
+  describe 'roles config array' do
+    it 'enables roles listed in the roles array' do
+      stub_gitlab_rb(roles: %w(application_role geo_primary_role))
+
+      Gitlab.load_roles
+
+      expect(Gitlab['application_role']['enable']).to be true
+      expect(Gitlab['geo_primary_role']['enable']).to be true
+    end
+
+    it 'supports providing a single role as a string' do
+      stub_gitlab_rb(roles: 'application_role')
+
+      Gitlab.load_roles
+
+      expect(Gitlab['application_role']['enable']).to be true
+    end
+
+    it 'handles users specifying hyphens instead of underscores' do
+      stub_gitlab_rb(roles: ['geo-primary-role'])
+
+      Gitlab.load_roles
+
+      expect(Gitlab['geo_primary_role']['enable']).to be true
+    end
+
+    it 'throws errors when an invalid role is used' do
+      stub_gitlab_rb(roles: ['some_invalid_role'])
+
+      expect { Gitlab.load_roles }.to raise_error(RuntimeError, /invalid roles have been set/)
+    end
+  end
+
+  describe 'DefaultRole' do
+    before do
+      allow(DefaultRole).to receive(:load_role).and_call_original
+      allow(GeoPrimaryRole).to receive(:load_role).and_call_original
+    end
+
+    it 'enables the default services when no other roles are active' do
+      Gitlab.load_roles
+
+      expect(Services).to have_received(:enable_group).with(Services::DEFAULT_GROUP, anything).once
+    end
+
+    it 'enables the default services when no "service managed" roles are active' do
+      stub_gitlab_rb(geo_primary_role: { enable: true })
+
+      Gitlab.load_roles
+
+      expect(Services).to have_received(:enable_group).with(Services::DEFAULT_GROUP, anything).once
+      expect(GeoPrimaryRole).to have_received(:load_role)
+    end
+
+    it 'leaves the default services disabled when another role is active' do
+      stub_gitlab_rb(application_role: { enable: true })
+
+      Gitlab.load_roles
+
+      expect(DefaultRole).to have_received(:load_role)
+      expect(Services).not_to have_received(:enable_group).with(Services::DEFAULT_GROUP, anything)
+    end
+
+    it 'leaves rails roles disabled when requested to maintain backward compat' do
+      stub_gitlab_rb(gitlab_rails: { enable: false })
+
+      Gitlab.load_roles
+
+      expect(Services).to have_received(:enable_group).with(Services::DEFAULT_GROUP, hash_including(except: ['rails'])).once
+    end
+  end
+
+  describe 'ApplicationRole' do
+    before do
+      allow(ApplicationRole).to receive(:load_role).and_call_original
+    end
+
+    it 'enables the rails services' do
+      stub_gitlab_rb(application_role: { enable: true })
+
+      Gitlab.load_roles
+
+      expect(ApplicationRole).to have_received(:load_role)
+      expect(Gitlab['gitlab_rails']['enable']).to eq true
+      expect(Services).to have_received(:enable_group).with('rails').once
+    end
+  end
+
+  describe 'MonitoringRole' do
+    before do
+      allow(MonitoringRole).to receive(:load_role).and_call_original
+    end
+
+    it 'enables the monitoring services' do
+      stub_gitlab_rb(roles: %w(monitoring_role))
+
+      Gitlab.load_roles
+
+      expect(MonitoringRole).to have_received(:load_role)
+      expect(Services).to have_received(:enable_group).with('monitoring_role').once
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/config/services_spec.rb b/spec/chef/cookbooks/package/libraries/config/services_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9jb25maWcvc2VydmljZXNfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/config/services_spec.rb
@@ -0,0 +1,316 @@
+require 'chef_helper'
+
+RSpec.describe Services do
+  before { allow(Gitlab).to receive(:[]).and_call_original }
+
+  describe 'when using the gitlab cookbook' do
+    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+    it 'returns the gitlab service list' do
+      chef_run
+      expect(Services.service_list).to have_key('puma')
+      expect(Services.service_list).not_to have_key('sentinel')
+    end
+  end
+
+  describe 'when using the gitlab-ee cookbook' do
+    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+    it 'returns the gitlab service list including gitlab-ee items' do
+      chef_run
+      expect(Services.service_list).to have_key('puma')
+      expect(Services.service_list).to have_key('sentinel')
+    end
+  end
+
+  it 'uses the default template when populating service information' do
+    expect(Services::Config.send(:service, ['test_service'])).to eq({ groups: [] })
+  end
+
+  describe 'service' do
+    let(:runner) { ChefSpec::SoloRunner.new }
+    let(:chef_run) { runner.converge('gitlab::config') }
+    let(:node) { runner.node }
+
+    before { Services.add_services('gitlab', Services::BaseServices.list) }
+
+    context 'when using user values that conflict with service settings' do
+      let!(:runner) { ChefSpec::SoloRunner.new { |node| Gitlab[:node] = node } }
+
+      it 'node service settings are overridden by gitlab.rb changes' do
+        stub_gitlab_rb(redis: { enable: true }, mattermost: { enable: false })
+        Services.disable('redis')
+        Services.enable('mattermost')
+        runner.converge('gitlab::config')
+
+        expect(node['redis']['enable']).to be true
+        expect(node['mattermost']['enable']).to be false
+      end
+    end
+
+    context 'when enable/disable is passed a single service' do
+      it 'sets the correct values' do
+        chef_run
+        Services.disable('redis')
+        expect(node['redis']['enable']).to be false
+
+        Services.enable('mattermost')
+        expect(node['mattermost']['enable']).to be true
+      end
+
+      it 'supports exceptions' do
+        chef_run
+        Services.disable('mattermost')
+        Services.enable('mattermost', except: 'mattermost')
+        expect(node['mattermost']['enable']).to be false
+
+        Services.enable('redis')
+        Services.disable('redis', except: 'redis')
+        expect(node['redis']['enable']).to be true
+      end
+    end
+
+    context 'when enable/disable is passed multiple services' do
+      before { chef_run }
+
+      it 'sets the correct values' do
+        Services.disable('redis', 'postgresql', 'gitaly')
+        expect(node['redis']['enable']).to be false
+        expect(node['postgresql']['enable']).to be false
+        expect(node['gitaly']['enable']).to be false
+
+        Services.enable('mattermost', 'registry', 'mailroom')
+        expect(node['mattermost']['enable']).to be true
+        expect(node['registry']['enable']).to be true
+        expect(node['gitlab']['mailroom']['enable']).to be true
+      end
+
+      it 'supports single exceptions' do
+        Services.disable('registry')
+        Services.enable('mattermost', 'registry', 'mailroom', except: 'registry')
+        expect(node['mattermost']['enable']).to be true
+        expect(node['registry']['enable']).to be false
+        expect(node['gitlab']['mailroom']['enable']).to be true
+
+        Services.enable('postgresql')
+        Services.disable('redis', 'postgresql', 'gitaly', except: 'postgresql')
+        expect(node['redis']['enable']).to be false
+        expect(node['postgresql']['enable']).to be true
+        expect(node['gitaly']['enable']).to be false
+      end
+
+      it 'supports multiple exceptions' do
+        Services.disable('registry', 'mailroom')
+        Services.enable('mattermost', 'registry', 'mailroom', except: %w(registry mailroom))
+        expect(node['mattermost']['enable']).to be true
+        expect(node['registry']['enable']).to be false
+        expect(node['gitlab']['mailroom']['enable']).to be false
+
+        Services.enable('postgresql', 'gitaly')
+        Services.disable('redis', 'postgresql', 'gitaly', except: %w(postgresql gitaly))
+        expect(node['redis']['enable']).to be false
+        expect(node['postgresql']['enable']).to be true
+        expect(node['gitaly']['enable']).to be true
+      end
+
+      it 'ignores disable on system services' do
+        Services.disable('node_exporter')
+        expect(node['monitoring']['node-exporter']['enable']).to be true
+      end
+
+      it 'allows forced disable on system services' do
+        Services.disable('node_exporter', include_system: true)
+        expect(node['monitoring']['node-exporter']['enable']).to be false
+      end
+    end
+
+    context 'when passed single exception' do
+      before { chef_run }
+
+      it 'enables all others' do
+        Services.disable('registry')
+        Services.enable(Services::ALL_SERVICES, except: 'registry')
+        expect(node['redis']['enable']).to be true
+        expect(node['postgresql']['enable']).to be true
+        expect(node['mattermost']['enable']).to be true
+        expect(node['registry']['enable']).to be false
+      end
+
+      it 'disables all others' do
+        Services.enable('redis')
+        Services.disable(Services::ALL_SERVICES, except: 'redis')
+        expect(node['redis']['enable']).to be true
+        expect(node['postgresql']['enable']).to be false
+        expect(node['mattermost']['enable']).to be false
+        expect(node['registry']['enable']).to be false
+      end
+    end
+
+    context 'when passed multiple exceptions' do
+      before { chef_run }
+
+      it 'enables all others' do
+        Services.disable('registry', 'mailroom')
+        Services.enable(Services::ALL_SERVICES, except: %w(registry mailroom))
+        expect(node['redis']['enable']).to be true
+        expect(node['postgresql']['enable']).to be true
+        expect(node['gitaly']['enable']).to be true
+        expect(node['mattermost']['enable']).to be true
+        expect(node['gitlab']['mailroom']['enable']).to be false
+        expect(node['registry']['enable']).to be false
+      end
+
+      it 'disables all others' do
+        Services.enable('postgresql', 'gitaly')
+        Services.disable(Services::ALL_SERVICES, except: %w(postgresql gitaly))
+        expect(node['redis']['enable']).to be false
+        expect(node['postgresql']['enable']).to be true
+        expect(node['gitaly']['enable']).to be true
+        expect(node['mattermost']['enable']).to be false
+        expect(node['gitlab']['mailroom']['enable']).to be false
+        expect(node['registry']['enable']).to be false
+      end
+    end
+  end
+
+  describe 'group' do
+    let(:runner) { ChefSpec::SoloRunner.new }
+    let(:chef_run) { runner.converge('gitlab::config') }
+    let(:node) { runner.node }
+
+    before { Services.add_services('gitlab', Services::BaseServices.list) }
+
+    context 'when using user values that conflict with service settings' do
+      cached!(:runner) { ChefSpec::SoloRunner.new { |node| Gitlab[:node] = node } }
+
+      it 'node service settings are overridden by gitlab.rb changes' do
+        stub_gitlab_rb(redis: { enable: true }, postgresql: { enable: false })
+        Services.disable_group('redis')
+        Services.enable_group('postgres')
+        runner.converge('gitlab::config')
+
+        expect(node['redis']['enable']).to be true
+        expect(node['mattermost']['enable']).to be false
+      end
+    end
+
+    context 'when enable_group/disable_group is passed a single group' do
+      before { chef_run }
+
+      it 'sets the correct values' do
+        Services.disable_group('redis')
+        expect(node['redis']['enable']).to be false
+        expect(node['monitoring']['redis-exporter']['enable']).to be false
+
+        Services.enable_group('rails')
+        expect(node['gitlab']['puma']['enable']).to be true
+        expect(node['monitoring']['gitlab-exporter']['enable']).to be true
+      end
+
+      it 'supports exceptions' do
+        Services.disable_group('monitoring')
+        Services.enable_group('rails', except: 'monitoring')
+        expect(node['monitoring']['gitlab-exporter']['enable']).to be false
+        expect(node['gitlab']['puma']['enable']).to be true
+
+        Services.enable_group('monitoring')
+        Services.disable_group('redis', except: 'monitoring')
+        expect(node['redis']['enable']).to be false
+        expect(node['monitoring']['redis-exporter']['enable']).to be true
+      end
+    end
+
+    context 'when enable/disable is passed multiple groups' do
+      before { chef_run }
+      it 'sets the correct values' do
+        Services.disable_group('redis', 'postgres')
+        expect(node['redis']['enable']).to be false
+        expect(node['postgresql']['enable']).to be false
+
+        Services.enable_group('rails', 'monitoring')
+        expect(node['monitoring']['redis-exporter']['enable']).to be true
+        expect(node['gitlab']['puma']['enable']).to be true
+      end
+
+      it 'supports single exceptions' do
+        Services.disable_group('monitoring')
+        Services.enable_group('redis', 'rails', except: 'monitoring')
+        expect(node['redis']['enable']).to be true
+        expect(node['gitlab']['puma']['enable']).to be true
+        expect(node['monitoring']['gitlab-exporter']['enable']).to be false
+        expect(node['monitoring']['redis-exporter']['enable']).to be false
+
+        Services.enable_group('postgres')
+        Services.disable_group('redis', 'monitoring', except: 'postgres')
+        expect(node['redis']['enable']).to be false
+        expect(node['monitoring']['postgres-exporter']['enable']).to be true
+        expect(node['monitoring']['prometheus']['enable']).to be false
+      end
+
+      it 'supports multiple exceptions' do
+        Services.disable_group('redis', Services::SYSTEM_GROUP, include_system: true)
+        Services.enable_group('rails', 'monitoring', except: ['redis', Services::SYSTEM_GROUP])
+        expect(node['monitoring']['redis-exporter']['enable']).to be false
+        expect(node['monitoring']['node-exporter']['enable']).to be false
+        expect(node['gitlab']['puma']['enable']).to be true
+
+        Services.enable_group('sidekiq', 'monitoring')
+        Services.disable_group('rails', 'postgres', except: %w(sidekiq monitoring))
+        expect(node['gitlab']['gitlab-workhorse']['enable']).to be false
+        expect(node['gitlab']['sidekiq']['enable']).to be true
+        expect(node['postgresql']['enable']).to be false
+        expect(node['monitoring']['postgres-exporter']['enable']).to be true
+      end
+
+      it 'ignores disable on system services' do
+        Services.enable_group(Services::SYSTEM_GROUP)
+        Services.disable_group(Services::SYSTEM_GROUP)
+        expect(node['logrotate']['enable']).to be true
+      end
+
+      it 'allows forced disable on system services' do
+        Services.enable_group(Services::SYSTEM_GROUP)
+        Services.disable_group(Services::SYSTEM_GROUP, include_system: true)
+        expect(node['logrotate']['enable']).to be false
+      end
+    end
+
+    context 'when passed single exception' do
+      before { chef_run }
+
+      it 'enables all others' do
+        Services.disable_group('monitoring')
+        Services.enable_group(Services::ALL_GROUPS, except: 'monitoring')
+        expect(node['gitlab']['puma']['enable']).to be true
+        expect(node['monitoring']['gitlab-exporter']['enable']).to be false
+      end
+
+      it 'disables all others' do
+        Services.enable_group('monitoring')
+        Services.disable_group(Services::ALL_GROUPS, except: 'monitoring')
+        expect(node['monitoring']['postgres-exporter']['enable']).to be true
+        expect(node['postgresql']['enable']).to be false
+      end
+    end
+
+    context 'when passed multiple exceptions' do
+      before { chef_run }
+
+      it 'enables all others' do
+        Services.disable_group('redis', 'rails')
+        Services.enable_group(Services::ALL_GROUPS, except: %w(redis rails))
+        expect(node['gitlab']['puma']['enable']).to be false
+        expect(node['monitoring']['node-exporter']['enable']).to be true
+        expect(node['monitoring']['redis-exporter']['enable']).to be false
+      end
+
+      it 'disables all others' do
+        Services.enable_group('redis', 'rails')
+        Services.disable_group(Services::ALL_GROUPS, except: %w(redis rails))
+        expect(node['monitoring']['prometheus']['enable']).to be false
+        expect(node['redis']['enable']).to be true
+        expect(node['gitlab']['sidekiq']['enable']).to be true
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/config_mash_spec.rb b/spec/chef/cookbooks/package/libraries/config_mash_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9jb25maWdfbWFzaF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/config_mash_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+RSpec.describe Gitlab::ConfigMash do
+  subject(:mash) { described_class.new }
+
+  describe '.auto_vivify?' do
+    subject { described_class }
+
+    it 'returns true when inside auto_vivify block' do
+      subject.auto_vivify do
+        expect(subject.auto_vivify?).to be_truthy
+      end
+    end
+
+    it 'returns false when not in auto_vivify block' do
+      expect(subject.auto_vivify?).to be_falsey
+    end
+  end
+
+  describe '#[]' do
+    it 'behaves like a normal Mash by default' do
+      mash['param1'] = 'value1'
+      expect(mash['param1']).to eq 'value1'
+      expect(mash['param2']).to be_nil
+      expect { mash['param2']['nested'] }.to raise_error(NoMethodError, /nil:NilClass/)
+    end
+
+    it 'allows nested undefined reads when in an auto-vivify block' do
+      mash['param1'] = 'value1'
+      expect(mash['param1']).to eq 'value1'
+      expect(mash['param2']).to be_nil
+
+      Gitlab::ConfigMash.auto_vivify do
+        expect(mash['param2']).not_to be_nil
+        expect { mash['param2']['nested'] }.not_to raise_error
+        mash['param3']['enable'] = true
+        expect(mash['param3']['enable']).to eq true
+      end
+
+      # confirm it is turned off after the block
+      expect(mash['param4']).to be_nil
+    end
+  end
+
+  describe 'convert_value' do
+    it 'returns passed value when its a ConfigMash' do
+      another_mash = Gitlab::ConfigMash.new
+
+      converted = mash.convert_value(another_mash)
+
+      expect(converted).to be_a(Gitlab::ConfigMash)
+      expect(converted).to eq(another_mash)
+    end
+
+    it 'returns a ConfigMash with the same data when passed a Hash' do
+      hash = { 'key' => 'value' }
+
+      converted = mash.convert_value(hash)
+
+      expect(converted).to be_a(Gitlab::ConfigMash)
+      expect(converted.keys).to match_array('key')
+      expect(converted.values).to match_array('value')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/deprecations_spec.rb b/spec/chef/cookbooks/package/libraries/deprecations_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9kZXByZWNhdGlvbnNfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/deprecations_spec.rb
@@ -0,0 +1,221 @@
+require 'spec_helper'
+
+RSpec.describe Gitlab::Deprecations do
+  let(:valid_config) { { gitlab: { nginx: { listen_addresses: "SomeRandomString" } } } }
+  let(:invalid_config) do
+    {
+      "gitlab" => {
+        "nginx" => {
+          "listen_address" => "SomeRandomString"
+        },
+        "gitlab-rails" => {
+          "stuck_ci_builds_worker_cron" => "5 * * * *"
+        }
+      },
+      "mattermost" => {
+        "system_read_timeout" => 50,
+        "log_file_directory" => "/my/random/path"
+      },
+      "monitoring" => {
+        "gitlab-monitor" => {
+          "enable" => false
+        }
+      }
+    }
+  end
+
+  let(:conf1) do
+    {
+      config_keys: %w(gitlab nginx listen_address),
+      deprecation: '8.10',
+      removal: '11.0',
+      note: "Use nginx['listen_addresses'] instead."
+    }
+  end
+
+  let(:conf2) do
+    {
+      config_keys: %w(gitlab gitlab-rails stuck_ci_builds_worker_cron),
+      deprecation: '9.0',
+      removal: '12.0',
+      note: "Use gitlab_rails['stuck_ci_jobs_worker_cron'] instead."
+    }
+  end
+
+  let(:conf3) do
+    {
+      config_keys: %w(gitlab gitlab-shell git_data_directories),
+      deprecation: '8.10',
+      removal: '11.0',
+      note: "Use git_data_dirs instead."
+    }
+  end
+
+  let(:conf4) do
+    {
+      config_keys: %w(monitoring gitlab-monitor enable),
+      deprecation: '12.0',
+      removal: '13.0',
+      note: "Use gitlab_exporter['enable'] instead."
+    }
+  end
+
+  let(:deprecation_list) do
+    [
+      conf1,
+      conf2,
+      conf3,
+      conf4,
+    ]
+  end
+
+  before(:each) do
+    allow(Gitlab::Deprecations).to receive(:list).and_return(deprecation_list)
+  end
+
+  describe '.applicable_deprecations' do
+    it 'detects valid configuration' do
+      expect(described_class.applicable_deprecations("11.0", valid_config, :deprecation)).to eq([])
+    end
+
+    it 'distinguishes from deprecated and removed configuration' do
+      expect(described_class.applicable_deprecations("11.0", invalid_config, :deprecation)).to include(conf1)
+      expect(described_class.applicable_deprecations("11.0", invalid_config, :deprecation)).to include(conf2)
+      expect(described_class.applicable_deprecations("12.0", invalid_config, :deprecation)).to include(conf1)
+      expect(described_class.applicable_deprecations("12.0", invalid_config, :deprecation)).to include(conf2)
+
+      expect(described_class.applicable_deprecations("11.0", invalid_config, :removal)).not_to include(conf2)
+      expect(described_class.applicable_deprecations("12.0", invalid_config, :removal)).to include(conf2)
+    end
+
+    it 'also detects deprecated falsey values' do
+      expect(described_class.applicable_deprecations("12.0", invalid_config, :deprecation)).to include(conf4)
+    end
+  end
+
+  describe '.check_config' do
+    it 'detects valid_config configuration' do
+      expect(described_class.check_config("11.0", valid_config)).to eq([])
+    end
+
+    it 'detects deprecated configuration for specified version and ignores not yet deprecated ones' do
+      message_1 = "* nginx['listen_address'] has been deprecated since 8.10 and was removed in 11.0. Use nginx['listen_addresses'] instead."
+      message_2 = "* gitlab_rails['stuck_ci_builds_worker_cron'] has been deprecated since 9.0 and was removed in 12.0. Use gitlab_rails['stuck_ci_jobs_worker_cron'] instead."
+      message_3 = "* gitlab_monitor['enable'] has been deprecated since 12.0 and will be removed in 13.0. Use gitlab_exporter['enable'] instead."
+
+      expect(described_class.check_config("11.0", invalid_config)).to include(message_1)
+      expect(described_class.check_config("11.0", invalid_config)).not_to include(message_2)
+      expect(described_class.check_config("12.0", invalid_config)).to include(message_2)
+      expect(described_class.check_config("12.0", invalid_config, :deprecation)).to include(message_3)
+    end
+  end
+
+  describe '.identify_deprecated_config' do
+    it 'detects deprecations correctly from list of supported keys' do
+      mattermost_supported_keys = %w(log_file_directory)
+      output = [
+        {
+          config_keys: %w(mattermost system_read_timeout),
+          deprecation: '10.2',
+          removal: '11.0',
+          note: nil
+        }
+      ]
+      expect(described_class.identify_deprecated_config(invalid_config, ["mattermost"], mattermost_supported_keys, "10.2", "11.0")).to eq(output)
+    end
+  end
+
+  describe '.deprecate_only_if_value' do
+    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+    before do
+      allow(Gitlab).to receive(:[]).and_call_original
+    end
+
+    context 'during removals' do
+      context 'when deprecated config is not set' do
+        it 'does not raise warning' do
+          expect(described_class.deprecate_only_if_value('14.0', chef_run.node.normal, :removal, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq([])
+        end
+      end
+
+      context 'when deprecated config is set to an acceptable value' do
+        before do
+          stub_gitlab_rb(
+            puma: { enable: false }
+          )
+        end
+        it 'does not raise warning' do
+          expect(described_class.deprecate_only_if_value('14.0', chef_run.node.normal, :removal, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq([])
+        end
+      end
+
+      context 'when deprecated config is set to an unacceptable value' do
+        before do
+          stub_gitlab_rb(
+            puma: { enable: true }
+          )
+        end
+        it 'raises warning' do
+          expect(described_class.deprecate_only_if_value('14.0', chef_run.node.normal, :removal, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq(["* puma[enable] has been deprecated since 13.10 and was removed in 14.0."])
+        end
+      end
+    end
+
+    context 'during deprecations' do
+      context 'when deprecated config is not set' do
+        it 'does not raise warning' do
+          expect(described_class.deprecate_only_if_value('13.12', chef_run.node.normal, :deprecation, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq([])
+        end
+      end
+
+      context 'when deprecated config is set to an acceptable value' do
+        before do
+          stub_gitlab_rb(
+            puma: { enable: false }
+          )
+        end
+        it 'raises warning' do
+          expect(described_class.deprecate_only_if_value('13.12', chef_run.node.normal, :deprecation, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq(["* puma[enable] has been deprecated since 13.10 and will be removed in 14.0."])
+        end
+      end
+
+      context 'when deprecated config is set to an unacceptable value' do
+        before do
+          stub_gitlab_rb(
+            puma: { enable: true }
+          )
+        end
+        it 'raises warning' do
+          expect(described_class.deprecate_only_if_value('13.12', chef_run.node.normal, :deprecation, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq(["* puma[enable] has been deprecated since 13.10 and will be removed in 14.0."])
+        end
+      end
+    end
+  end
+
+  describe 'NodeAttribute' do
+    before do
+      Gitlab::Deprecations::NodeAttribute.log_deprecations = true
+    end
+
+    after do
+      Gitlab::Deprecations::NodeAttribute.log_deprecations = false
+    end
+
+    it 'Logs deprecations for passed variables and proxies to new object' do
+      config = { 'monitoring' => { 'test' => 'test-value' } }
+      config['prometheus'] = Gitlab::Deprecations::NodeAttribute.new(config['monitoring'], "config['prometheus']", "config['monitoring']")
+
+      expect(config['prometheus']['test']).to eq('test-value')
+      expect_logged_deprecation(/Accessing config\['prometheus'\] is deprecated/)
+    end
+
+    it 'Logs deprecations for passed variables and proxies to new Proc if provided' do
+      config = { 'monitoring' => { 'test' => 'test-value' } }
+      config['prometheus'] = Gitlab::Deprecations::NodeAttribute.new(proc { config['monitoring'] }, "config['prometheus']", "config['monitoring']")
+
+      expect(config['prometheus']['test']).to eq('test-value')
+      expect_logged_deprecation(/Accessing config\['prometheus'\] is deprecated/)
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/gitlab_cluster_spec.rb b/spec/chef/cookbooks/package/libraries/gitlab_cluster_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9naXRsYWJfY2x1c3Rlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/gitlab_cluster_spec.rb
@@ -0,0 +1,163 @@
+require 'spec_helper'
+
+RSpec.describe GitlabCluster, :cluster_config do
+  let(:gitlab_cluster_config_path) { described_class::JSON_FILE }
+  subject { described_class.config }
+
+  describe '#all' do
+    context 'when the cluster configuration file does not exist' do
+      it 'returns an empty hash' do
+        expect(subject.all).to be_empty
+      end
+    end
+
+    context 'when the cluster configuration file exists' do
+      it 'parses the file content' do
+        stub_file_content(gitlab_cluster_config_path, foo: 'bar')
+
+        expect(subject.all).to eq('foo' => 'bar')
+      end
+    end
+  end
+
+  describe '#set' do
+    it 'set the value of a single config option' do
+      subject.set('primary', true)
+
+      expect(subject.all).to eq('primary' => true)
+    end
+
+    it 'sets the value of a nested config option' do
+      subject.set('patroni', 'standby_cluster', 'enable', true)
+
+      expect(subject.all).to eq('patroni' => { 'standby_cluster' => { 'enable' => true } })
+    end
+
+    it 'overrides the key value if it already set' do
+      stub_file_content(gitlab_cluster_config_path, patroni: { standby_cluster: { enable: false } })
+
+      subject.set('patroni', 'standby_cluster', 'enable', true)
+
+      expect(subject.all).to eq('patroni' => { 'standby_cluster' => { 'enable' => true } })
+    end
+  end
+
+  describe '#get' do
+    before do
+      stub_file_content(gitlab_cluster_config_path, primary: false, patroni: { standby_cluster: { enable: true } })
+    end
+
+    it 'get the value of a single config option if the key exists' do
+      expect(subject.get('primary')).to eq(false)
+    end
+
+    it 'get the value of a nested config option if the key exists' do
+      expect(subject.get('patroni', 'standby_cluster', 'enable')).to eq(true)
+    end
+
+    it 'returns nil if the key does not exist' do
+      expect(subject.get('foo', 'bar')).to be_nil
+    end
+  end
+
+  describe '#load_roles!' do
+    before do
+      stub_gitlab_rb(application_role: { enable: true }, geo_primary_role: { enable: nil }, geo_secondary_role: { enable: true })
+    end
+
+    it 'overrides roles defined in the configuration file' do
+      stub_file_content(gitlab_cluster_config_path, secondary: false)
+
+      subject.load_roles!
+
+      expect(Gitlab['application_role']['enable']).to eq(true)
+      expect(Gitlab['geo_secondary_role']['enable']).to eq(false)
+    end
+
+    it 'does not override roles not defined in the configuration file' do
+      stub_file_content(gitlab_cluster_config_path, {})
+
+      subject.load_roles!
+
+      expect(Gitlab['application_role']['enable']).to eq(true)
+      expect(Gitlab['geo_secondary_role']['enable']).to eq(true)
+    end
+
+    it 'prints a warning message for each enabled role defined in the configuration file' do
+      stub_file_content(gitlab_cluster_config_path, primary: true, secondary: false)
+
+      expect(LoggingHelper)
+        .to receive(:warning)
+        .with("The 'geo_primary_role' is defined in #{gitlab_cluster_config_path} as 'true' and overrides the setting in the /etc/gitlab/gitlab.rb")
+
+      expect(LoggingHelper)
+        .to receive(:warning)
+        .with("The 'geo_secondary_role' is defined in #{gitlab_cluster_config_path} as 'false' and overrides the setting in the /etc/gitlab/gitlab.rb")
+        .once
+
+      subject.load_roles!
+    end
+  end
+
+  describe '#write_to_file!' do
+    let(:config_path) { Dir.mktmpdir }
+    let(:gitlab_cluster_config_path) { File.join(config_path, 'gitlab-cluster.json') }
+
+    before do
+      stub_const('GitlabCluster::CONFIG_PATH', config_path)
+      stub_const('GitlabCluster::JSON_FILE', gitlab_cluster_config_path)
+    end
+
+    after do
+      FileUtils.rm_rf(config_path)
+    end
+
+    context 'when the config directory does not exist' do
+      it 'does not create the configuration file' do
+        FileUtils.rm_rf(config_path)
+
+        subject.save
+
+        expect(File.exist?(gitlab_cluster_config_path)).to eq(false)
+      end
+    end
+
+    context 'when the cluster configuration file does not exist' do
+      it 'creates the configuration file' do
+        FileUtils.rm_rf(gitlab_cluster_config_path)
+
+        subject.save
+
+        expect(File.exist?(gitlab_cluster_config_path)).to eq(true)
+        expect(read_file_content(gitlab_cluster_config_path)).to be_empty
+      end
+    end
+
+    context 'when the cluster configuration file exists' do
+      it 'overrides previous settings' do
+        write_file_content(gitlab_cluster_config_path, foo: 'bar', zoo: true)
+        subject.set('zoo', false)
+
+        subject.save
+
+        expect(read_file_content(gitlab_cluster_config_path)).to eq("foo" => "bar", "zoo" => false)
+      end
+    end
+  end
+
+  def stub_file_content(fullpath, content)
+    allow(File).to receive(:exist?).with(fullpath).and_return(true)
+    allow(IO).to receive(:read).with(fullpath).and_return(content.to_json)
+  end
+
+  def read_file_content(fullpath)
+    JSON.parse(File.read(fullpath))
+  end
+
+  def write_file_content(fullpath, content)
+    File.open(fullpath, 'w') do |f|
+      f.write(content.to_json)
+      f.chmod(0600)
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/helpers/logging_helper_spec.rb b/spec/chef/cookbooks/package/libraries/helpers/logging_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9oZWxwZXJzL2xvZ2dpbmdfaGVscGVyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/helpers/logging_helper_spec.rb
@@ -0,0 +1,172 @@
+require 'chef_helper'
+
+RSpec.describe LoggingHelper do
+  let(:log) { spy('Chef::Log') }
+
+  before do
+    subject.reset
+    stub_const('Chef::Log', log)
+  end
+
+  context '.messages' do
+    it 'starts with no messages' do
+      expect(subject.messages).to eq([])
+    end
+  end
+
+  context '.log' do
+    it 'records into .messages' do
+      subject.log('one')
+      expect(subject.messages).to contain_exactly(Hash)
+    end
+
+    it 'records message' do
+      subject.log('two')
+      expect(subject.messages).to contain_exactly(hash_including(message: 'two'))
+    end
+
+    it 'records two messages' do
+      subject.log('one')
+      subject.log('two')
+      expect(subject.messages).to contain_exactly(
+        hash_including(message: 'one'),
+        hash_including(message: 'two')
+      )
+    end
+
+    it 'optionally records kind' do
+      subject.log('three', kind: :fake)
+      expect(subject.messages).to contain_exactly(hash_including(kind: :fake))
+    end
+  end
+
+  context '.deprecation' do
+    it 'calls Chef::Log.warn' do
+      subject.deprecation('hello')
+      expect(log).to have_received(:warn).with('hello')
+    end
+
+    it 'adds the kind :deprecation' do
+      subject.deprecation('basic')
+      expect(subject.messages).to contain_exactly(hash_including(kind: :deprecation))
+    end
+  end
+
+  context '.warning' do
+    it 'calls Chef::Log.warn' do
+      subject.warning('hello')
+      expect(log).to have_received(:warn).with('hello')
+    end
+
+    it 'adds the kind :warning' do
+      subject.warning('basic')
+      expect(subject.messages).to contain_exactly(hash_including(kind: :warning))
+    end
+  end
+
+  context '.debug' do
+    it 'calls Chef::Log.debug' do
+      subject.debug('hello')
+      expect(log).to have_received(:debug).with('hello')
+    end
+
+    it 'adds the kind :debug' do
+      subject.debug('basic')
+      expect(subject.messages).to contain_exactly(hash_including(kind: :debug))
+    end
+  end
+
+  context '.note' do
+    it 'adds the kind :note' do
+      subject.note('basic')
+      expect(subject.messages).to contain_exactly(hash_including(kind: :note))
+    end
+  end
+
+  context '.report' do
+    it 'prints nothing if nothing happened' do
+      expect { subject.report }.not_to output.to_stdout
+    end
+
+    it 'prints a removal header, then removal' do
+      subject.removal('one')
+      expect { subject.report }.to output(/\nRemovals:\none/).to_stdout
+    end
+
+    it 'prints a removal header, then removals' do
+      subject.removal('one')
+      subject.removal('two')
+      expect { subject.report }.to output(/\nRemovals:\none\ntwo/).to_stdout
+    end
+
+    it 'prints a deprecation header, then deprecation' do
+      subject.deprecation('one')
+      expect { subject.report }.to output(/\nDeprecations:\none/).to_stdout
+    end
+
+    it 'prints a deprecation header, then deprecations' do
+      subject.deprecation('one')
+      subject.deprecation('two')
+      expect { subject.report }.to output(/\nDeprecations:\none\ntwo/).to_stdout
+    end
+
+    it 'prints a warning header, then warning' do
+      subject.warning('one')
+      expect { subject.report }. to output(/\nWarnings:\none/).to_stdout
+    end
+
+    it 'prints a warning header, then warnings' do
+      subject.warning('one')
+      subject.warning('two')
+      expect { subject.report }.to output(/\nWarnings:\none\ntwo/).to_stdout
+    end
+
+    it 'prints a note header, then note' do
+      subject.note('one')
+      expect { subject.report }. to output(/\nNotes:\none/).to_stdout
+    end
+
+    it 'prints a note header, then notes' do
+      subject.note('one')
+      subject.note('two')
+      expect { subject.report }.to output(/\nNotes:\none\ntwo/).to_stdout
+    end
+
+    it 'prints a removal header, removals, deprecation header, deprecations, warning header, warnings, note header then notes' do
+      subject.removal('one')
+      subject.removal('two')
+      subject.deprecation('three')
+      subject.deprecation('four')
+      subject.warning('five')
+      subject.warning('six')
+      subject.note('seven')
+      subject.note('eight')
+
+      output = <<~EOS
+
+        Removals:
+        one
+        two
+
+
+        Deprecations:
+        three
+        four
+
+
+        Warnings:
+        five
+        six
+
+
+        Notes:
+        seven
+        eight
+
+      EOS
+
+      expect { subject.report }
+        .to output(output).to_stdout
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/helpers/node_helper_spec.rb b/spec/chef/cookbooks/package/libraries/helpers/node_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9oZWxwZXJzL25vZGVfaGVscGVyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/helpers/node_helper_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'chef_helper'
+
+RSpec.describe NodeHelper do
+  let(:chef_run) { converge_config }
+  subject { described_class }
+
+  describe '.consume_cluster_attributes' do
+    it 'merges new attributes into node as override level' do
+      new_attrs = ::Gitlab::ConfigMash.new(geo_primary: true)
+
+      subject.consume_cluster_attributes(chef_run.node, new_attrs)
+
+      expect(chef_run.node.attributes).to include(geo_primary: true)
+      expect(chef_run.node.override).to include(geo_primary: true)
+    end
+
+    it 'merges overwritting existing attributes into node as override level' do
+      allow(Gitlab).to receive(:[]).and_call_original
+      stub_gitlab_rb(package: { custom: true })
+
+      expect(chef_run.node.attributes['package']['custom']).to be true
+
+      new_attrs = ::Gitlab::ConfigMash.new(package: { custom: false })
+      subject.consume_cluster_attributes(chef_run.node, new_attrs)
+
+      expect(chef_run.node.attributes['package']['custom']).to be false
+      expect(chef_run.node.override['package']['custom']).to be false
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/helpers/output_helper_spec.rb b/spec/chef/cookbooks/package/libraries/helpers/output_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9oZWxwZXJzL291dHB1dF9oZWxwZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/helpers/output_helper_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+RSpec.describe OutputHelper do
+  include OutputHelper
+
+  describe "#quote" do
+    context 'handling nil' do
+      it 'should return nil' do
+        expect(quote(nil)).to eq(nil)
+      end
+    end
+
+    context 'handling numbers' do
+      it 'should cooerce numbers to strings' do
+        result = quote(42)
+        expect(result).to be_instance_of(String)
+        expect(result).to eq('"42"')
+      end
+    end
+
+    context 'roundtripping values via YAML' do
+      values = [
+        "foo",
+        # single quotes
+        "foo'",
+        "'foo",
+        "fo'o",
+        "'foo'",
+        # double quotes
+        'foo"',
+        '"foo',
+        'fo"o',
+        '"foo"',
+        # newlines
+        "foo\n",
+        "\nfoo",
+        "fo\no",
+        "\nfoo\n",
+        # tabs
+        "foo\t",
+        "\tfoo",
+        "fo\to",
+        "\tfoo\t",
+        # spaces
+        "foo ",
+        " foo",
+        "fo o",
+        " foo ",
+        # unicode snowman
+        "foo☃",
+        "☃foo",
+        "fo☃o",
+        "☃foo☃",
+      ]
+      values.each do |value|
+        it "should YAML roundtrip #{value.inspect}" do
+          # create a document with structure { 'value' : quote(value) }
+          yaml_document = "---\nvalue: #{quote(value)}"
+          document = YAML.safe_load(yaml_document)
+          expect(document['value']).to eq(value)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/helpers/shell_out_helper_spec.rb b/spec/chef/cookbooks/package/libraries/helpers/shell_out_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9oZWxwZXJzL3NoZWxsX291dF9oZWxwZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/helpers/shell_out_helper_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ShellOutHelper do
+  let(:dummy_class) { Class.new { extend ShellOutHelper } }
+
+  before do
+    allow(Mixlib::ShellOut).to receive(:new).and_call_original
+  end
+
+  describe '.do_shell_out' do
+    context 'without optional arguments' do
+      it 'creates Mixlib::ShellOut object without optional arguments' do
+        expect(Mixlib::ShellOut).to receive(:new).with('ls', { user: nil, cwd: nil, environment: {} })
+
+        dummy_class.do_shell_out('ls')
+      end
+    end
+
+    context 'with optional arguments specified' do
+      it 'creates Mixlib::ShellOut object with specified optional arguments' do
+        expect(Mixlib::ShellOut).to receive(:new).with('ls', { user: 'foobar', cwd: '/tmp', environment: { PATH: '/tmp/bin' } })
+
+        dummy_class.do_shell_out('ls', 'foobar', '/tmp', env: { PATH: '/tmp/bin' })
+      end
+    end
+
+    context 'when command can not be executed' do
+      let(:shellout_object) { double('shellout', run_command: true) }
+
+      it 'logs relevant information' do
+        allow(Mixlib::ShellOut).to receive(:new).and_return(shellout_object)
+        allow(shellout_object).to receive(:run_command).and_raise(Errno::EACCES)
+
+        expect(Chef::Log).to receive(:info).with("Cannot execute ls.")
+
+        dummy_class.do_shell_out('ls')
+      end
+    end
+
+    context 'when command not found' do
+      let(:shellout_object) { double('shellout', run_command: true) }
+
+      it 'logs relevant information' do
+        allow(Mixlib::ShellOut).to receive(:new).and_return(shellout_object)
+        allow(shellout_object).to receive(:run_command).and_raise(Errno::ENOENT)
+
+        expect(Chef::Log).to receive(:info).with("ls does not exist.")
+
+        dummy_class.do_shell_out('ls')
+      end
+    end
+  end
+
+  describe '.do_shell_out_with_embedded_path' do
+    context 'without optional arguments' do
+      it 'calls do_shell_out with embedded bin path prependend to PATH' do
+        allow(ENV).to receive(:[]).with('PATH').and_return('/usr/local/bin')
+
+        expect(dummy_class).to receive(:do_shell_out).with('ls', nil, nil, env: { PATH: '/opt/gitlab/embedded/bin:/usr/local/bin' })
+
+        dummy_class.do_shell_out_with_embedded_path('ls')
+      end
+    end
+
+    context 'without PATH specified in optional env argument' do
+      it 'calls do_shell_out with embedded bin path prependend to PATH' do
+        allow(ENV).to receive(:[]).with('PATH').and_return('/usr/local/bin')
+
+        expect(dummy_class).to receive(:do_shell_out).with('ls', nil, nil, env: { PATH: '/opt/gitlab/embedded/bin:/tmp/bin:/usr/local/bin' })
+
+        dummy_class.do_shell_out_with_embedded_path('ls', env: { 'PATH' => '/tmp/bin' })
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/omnibus_helper_spec.rb b/spec/chef/cookbooks/package/libraries/omnibus_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9vbW5pYnVzX2hlbHBlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/omnibus_helper_spec.rb
@@ -0,0 +1,557 @@
+# frozen_string_literal: true
+
+require 'chef_helper'
+require 'time'
+
+RSpec.describe OmnibusHelper do
+  cached(:chef_run) { converge_config }
+  let(:node) { chef_run.node }
+  let(:file) { double(:file, write: true) }
+
+  subject { described_class.new(chef_run.node) }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+
+    allow(File).to receive(:open).and_call_original
+    allow(File).to receive(:open).with('/etc/gitlab/initial_root_password', 'w', 0600).and_yield(file).once
+  end
+
+  describe '#user_exists?' do
+    it 'returns true when user exists' do
+      allow_any_instance_of(ShellOutHelper).to receive(:success?).with("id -u root").and_return(true)
+      expect(subject.user_exists?('root')).to be_truthy
+    end
+
+    it 'returns false when user does not exist' do
+      allow_any_instance_of(ShellOutHelper).to receive(:success?).with("id -u nonexistentuser").and_return(false)
+      expect(subject.user_exists?('nonexistentuser')).to be_falsey
+    end
+  end
+
+  describe '#group_exists?' do
+    it 'returns true when group exists' do
+      allow_any_instance_of(ShellOutHelper).to receive(:success?).with("getent group root").and_return(true)
+      expect(subject.group_exists?('root')).to be_truthy
+    end
+
+    it 'returns false when group does not exist' do
+      allow_any_instance_of(ShellOutHelper).to receive(:success?).with("getent group nonexistentgroup").and_return(false)
+      expect(subject.group_exists?('nonexistentgroup')).to be_falsey
+    end
+  end
+
+  describe '#not_listening?' do
+    let(:chef_run) { converge_config }
+    context 'when Redis is disabled' do
+      before do
+        stub_gitlab_rb(
+          redis: { enable: false }
+        )
+      end
+
+      it 'returns true when service is disabled' do
+        expect(subject.not_listening?('redis')).to be_truthy
+      end
+    end
+
+    context 'when Redis is enabled' do
+      before do
+        stub_gitlab_rb(
+          redis: { enable: true }
+        )
+      end
+
+      it 'returns true when service is down' do
+        stub_service_failure_status('redis', true)
+
+        expect(subject.not_listening?('redis')).to be_truthy
+      end
+
+      it 'returns false when service is up' do
+        stub_service_failure_status('redis', false)
+
+        expect(subject.not_listening?('redis')).to be_falsey
+      end
+    end
+  end
+
+  describe '#service_enabled?' do
+    context 'services are enabled' do
+      before do
+        chef_run.node.normal['gitlab']['old_service']['enable'] = true
+        chef_run.node.normal['new_service']['enable'] = true
+        chef_run.node.normal['monitoring']['another_service']['enable'] = true
+      end
+
+      it 'should return true' do
+        expect(subject.service_enabled?('old_service')).to be_truthy
+        expect(subject.service_enabled?('new_service')).to be_truthy
+        expect(subject.service_enabled?('another_service')).to be_truthy
+      end
+    end
+
+    context 'services are disabled' do
+      before do
+        chef_run.node.normal['gitlab']['old_service']['enable'] = false
+        chef_run.node.normal['new_service']['enable'] = false
+        chef_run.node.normal['monitoring']['another_service']['enable'] = false
+      end
+
+      it 'should return false' do
+        expect(subject.service_enabled?('old_service')).to be_falsey
+        expect(subject.service_enabled?('new_service')).to be_falsey
+        expect(subject.service_enabled?('another_service')).to be_falsey
+      end
+    end
+  end
+
+  describe '#is_managed_and_offline?' do
+    context 'services are disabled' do
+      before do
+        chef_run.node.normal['gitlab']['old_service']['enable'] = false
+        chef_run.node.normal['new_service']['enable'] = false
+      end
+
+      it 'returns false' do
+        expect(subject.is_managed_and_offline?('old_service')).to be_falsey
+        expect(subject.is_managed_and_offline?('new_service')).to be_falsey
+      end
+    end
+
+    context 'services are enabled' do
+      before do
+        chef_run.node.normal['gitlab']['old_service']['enable'] = true
+        chef_run.node.normal['new_service']['enable'] = true
+      end
+
+      it 'returns true when services are offline' do
+        stub_service_failure_status('old_service', true)
+        stub_service_failure_status('new_service', true)
+
+        expect(subject.is_managed_and_offline?('old_service')).to be_truthy
+        expect(subject.is_managed_and_offline?('new_service')).to be_truthy
+      end
+
+      it 'returns false when services are online ' do
+        stub_service_failure_status('old_service', false)
+        stub_service_failure_status('new_service', false)
+
+        expect(subject.is_managed_and_offline?('old_service')).to be_falsey
+        expect(subject.is_managed_and_offline?('new_service')).to be_falsey
+      end
+    end
+  end
+
+  describe '#is_deprecated_os?' do
+    before do
+      allow(OmnibusHelper).to receive(:deprecated_os_list).and_return({ "raspbian-8.0" => "GitLab 11.8" })
+    end
+
+    it 'detects deprecated OS correctly' do
+      allow_any_instance_of(Ohai::System).to receive(:data).and_return({ "platform" => "raspbian", "platform_version" => "8.0" })
+
+      OmnibusHelper.is_deprecated_os?
+
+      expect_logged_deprecation(/Your OS, raspbian-8.0, will be deprecated soon/)
+    end
+
+    it 'does not detects valid OS as deprecated' do
+      allow_any_instance_of(Ohai::System).to receive(:data).and_return({ "platform" => "ubuntu", "platform_version" => "16.04.3" })
+      expect(LoggingHelper).not_to receive(:deprecation)
+      OmnibusHelper.is_deprecated_os?
+    end
+  end
+
+  describe '#is_deprecated_praefect_config?' do
+    before do
+      chef_run.node.normal['praefect'] = config
+    end
+
+    context 'deprecated config' do
+      let(:config) do
+        {
+          storage_nodes: [
+            { storage: 'praefect1', address: 'tcp://node1.internal' },
+            { storage: 'praefect2', address: 'tcp://node2.internal' }
+          ]
+        }
+      end
+
+      it 'detects deprecated config correctly' do
+        subject.is_deprecated_praefect_config?
+
+        expect_logged_deprecation(/Specifying Praefect storage nodes as an array is deprecated/)
+      end
+    end
+
+    context 'valid config' do
+      let(:config) do
+        {
+          storage_nodes: {
+            'praefect1' => { address: 'tcp://node1.internal' },
+            'praefect2' => { address: 'tcp://node2.internal' }
+          }
+        }
+      end
+
+      it 'does not detect a valid config as deprecated' do
+        expect(LoggingHelper).not_to receive(:deprecation)
+
+        subject.is_deprecated_praefect_config?
+      end
+    end
+  end
+
+  describe '#write_root_password' do
+    before do
+      stub_gitlab_rb(
+        gitlab_rails: {
+          initial_root_password: 'foobar',
+          store_initial_root_password: true
+        }
+      )
+    end
+
+    it 'stores root password to /etc/gitlab/initial_root_password' do
+      content = <<~EOS
+        # WARNING: This value is valid only in the following conditions
+        #          1. If provided manually (either via `GITLAB_ROOT_PASSWORD` environment variable or via `gitlab_rails['initial_root_password']` setting in `gitlab.rb`, it was provided before database was seeded for the first time (usually, the first reconfigure run).
+        #          2. Password hasn't been changed manually, either via UI or via command line.
+        #
+        #          If the password shown here doesn't work, you must reset the admin password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.
+
+        Password: foobar
+
+        # NOTE: This file will be automatically deleted in the first reconfigure run after 24 hours.
+      EOS
+
+      expect(file).to receive(:write).with(content)
+
+      described_class.new(converge_config.node).write_root_password
+    end
+  end
+
+  describe '.cleanup_root_password_file' do
+    context 'when /etc/gitlab/initial_root_password does not exist' do
+      before do
+        allow(File).to receive(:exist?).and_call_original
+        allow(File).to receive(:exist?).with('/etc/gitlab/initial_root_password').and_return(false)
+      end
+
+      it 'does not attempt to remove the file' do
+        expect(FileUtils).not_to receive(:rm_f).with('/etc/gitlab/initial_root_password')
+
+        described_class.cleanup_root_password_file
+      end
+    end
+
+    context 'when /etc/gitlab/initial_root_password exists' do
+      before do
+        allow(File).to receive(:exist?).and_call_original
+        allow(File).to receive(:exist?).with('/etc/gitlab/initial_root_password').and_return(true)
+        allow(Time).to receive(:now).and_return(Time.parse('2021-06-07 08:45:51.377926667 +0530'))
+      end
+
+      context 'when file is older than 24 hours' do
+        before do
+          allow(File).to receive(:mtime).with('/etc/gitlab/initial_root_password').and_return(Time.parse('2021-06-03 06:45:51.377926667 +0530'))
+        end
+
+        it 'attempts to remove the file' do
+          expect(FileUtils).to receive(:rm_f).with('/etc/gitlab/initial_root_password')
+
+          described_class.cleanup_root_password_file
+
+          expect_logged_note('Found old initial root password file at /etc/gitlab/initial_root_password and deleted it.')
+        end
+      end
+
+      context 'when file is younger than 24 hours' do
+        before do
+          allow(File).to receive(:mtime).with('/etc/gitlab/initial_root_password').and_return(Time.parse('2021-06-08 06:45:51.377926667 +0530'))
+        end
+
+        it 'does not attempt to remove the file' do
+          expect(FileUtils).not_to receive(:rm_f).with('/etc/gitlab/initial_root_password')
+
+          described_class.cleanup_root_password_file
+
+          expect_logged_note('Found old initial root password file at /etc/gitlab/initial_root_password and deleted it.')
+        end
+      end
+    end
+  end
+
+  describe '#print_root_account_details' do
+    context 'when not on first reconfigure after installation' do
+      before do
+        chef_run.node.normal['gitlab']['bootstrap']['enable'] = false
+      end
+
+      it 'does not add a note or write password to file' do
+        expect(LoggingHelper).not_to receive(:note)
+        expect(subject).not_to receive(:write_root_password)
+
+        subject.print_root_account_details
+      end
+    end
+
+    context 'when on first reconfigure after installation' do
+      before do
+        allow_any_instance_of(OmnibusHelper).to receive(:writ_root_password).and_return(true)
+      end
+
+      context 'when display_initial_root_password is true' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              initial_root_password: 'foobar',
+              display_initial_root_password: true,
+              store_initial_root_password: false
+            }
+          )
+        end
+
+        it 'displays root password at the end of reconfigure' do
+          msg = <<~EOS
+            Default admin account has been configured with following details:
+            Username: root
+            Password: foobar
+
+            NOTE: Because these credentials might be present in your log files in plain text, it is highly recommended to reset the password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.
+          EOS
+
+          described_class.new(converge_config.node).print_root_account_details
+
+          expect_logged_note(msg)
+        end
+      end
+
+      context 'when display_initial_root_password is false' do
+        before do
+          stub_gitlab_rb(
+            gitlab_rails: {
+              initial_root_password: 'foobar',
+              display_initial_root_password: false,
+              store_initial_root_password: false
+            }
+          )
+        end
+
+        it 'does not display root credentials at the end of reconfigure' do
+          msg = <<~EOS
+            Default admin account has been configured with following details:
+            Username: root
+            Password: You didn't opt-in to print initial root password to STDOUT.
+
+            NOTE: Because these credentials might be present in your log files in plain text, it is highly recommended to reset the password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.
+          EOS
+
+          described_class.new(converge_config.node).print_root_account_details
+
+          expect_logged_note(msg)
+        end
+      end
+
+      describe '#write_root_password' do
+        context 'when store_initial_root_password is true' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                initial_root_password: 'foobar',
+                display_initial_root_password: false,
+                store_initial_root_password: true
+              }
+            )
+          end
+
+          it 'writes initial root password to /etc/gitlab/initial_root_password' do
+            subject = described_class.new(converge_config.node)
+            expect(subject).to receive(:write_root_password)
+
+            subject.print_root_account_details
+
+            expect_logged_note(%r{Password stored to /etc/gitlab/initial_root_password})
+          end
+        end
+
+        context 'with default value for store_initial_root_password' do
+          context 'when password is auto-generated' do
+            before do
+              allow(ENV).to receive(:[]).and_call_original
+              allow(ENV).to receive(:[]).with('GITLAB_ROOT_PASSWORD').and_return(nil)
+            end
+
+            it 'writes initial root password to /etc/gitlab/initial_root_password' do
+              chef_run = ChefSpec::SoloRunner.converge('gitlab::default')
+              subject = described_class.new(chef_run.node)
+
+              expect(subject).to receive(:write_root_password)
+
+              subject.print_root_account_details
+
+              expect_logged_note(%r{Password stored to /etc/gitlab/initial_root_password})
+            end
+          end
+
+          context 'when password is specified by user' do
+            context 'via gitlab.rb' do
+              before do
+                stub_gitlab_rb(
+                  gitlab_rails: {
+                    initial_root_password: 'foobar',
+                  }
+                )
+              end
+
+              it 'does not write initial root password to /etc/gitlab/initial_root_password' do
+                subject = described_class.new(converge_config.node)
+
+                expect(subject).not_to receive(:write_root_password)
+
+                subject.print_root_account_details
+              end
+            end
+
+            context 'via env variable' do
+              before do
+                allow(ENV).to receive(:[]).and_call_original
+                allow(ENV).to receive(:[]).with('GITLAB_ROOT_PASSWORD').and_return('foobar')
+              end
+
+              it 'does not write initial root password to /etc/gitlab/initial_root_password' do
+                subject = described_class.new(converge_config.node)
+
+                expect(subject).not_to receive(:write_root_password)
+
+                subject.print_root_account_details
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+
+  describe '#check_locale' do
+    let(:error_message) { "Identified encoding is not UTF-8. GitLab requires UTF-8 encoding to function properly. Please check your locale settings." }
+
+    describe 'using LC_ALL variable' do
+      it 'does not raise a warning when set to a UTF-8 locale even if others are not' do
+        allow(ENV).to receive(:[]).and_call_original
+        allow(ENV).to receive(:[]).with('LC_ALL').and_return('en_US.UTF-8')
+        allow(ENV).to receive(:[]).with('LC_COLLATE').and_return('en_SG ISO-8859-1')
+        allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_SG ISO-8859-1')
+        allow(ENV).to receive(:[]).with('LANG').and_return('en_SG ISO-8859-1')
+
+        expect(LoggingHelper).not_to receive(:warning).with("Environment variable .* specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
+
+        described_class.check_locale
+      end
+
+      it 'raises warning when LC_ALL is non-UTF-8' do
+        allow(ENV).to receive(:[]).and_call_original
+        allow(ENV).to receive(:[]).with('LC_ALL').and_return('en_SG ISO-8859-1')
+
+        described_class.check_locale
+
+        expect_logged_warning("Environment variable LC_ALL specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
+      end
+    end
+
+    describe 'using LC_CTYPE variable' do
+      before do
+        allow(ENV).to receive(:[]).and_call_original
+        allow(ENV).to receive(:[]).with('LC_ALL').and_return(nil)
+      end
+
+      it 'raises warning when LC_CTYPE is non-UTF-8' do
+        allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_SG ISO-8859-1')
+
+        described_class.check_locale
+
+        expect_logged_warning("Environment variable LC_CTYPE specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
+      end
+    end
+
+    describe 'using LC_COLLATE variable' do
+      before do
+        allow(ENV).to receive(:[]).and_call_original
+        allow(ENV).to receive(:[]).with('LC_ALL').and_return(nil)
+        allow(ENV).to receive(:[]).with('LC_CTYPE').and_return(nil)
+      end
+
+      it 'raises warning when LC_COLLATE is non-UTF-8' do
+        allow(ENV).to receive(:[]).with('LC_COLLATE').and_return('en_SG ISO-8859-1')
+
+        described_class.check_locale
+
+        expect_logged_warning("Environment variable LC_COLLATE specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
+      end
+    end
+
+    describe 'using LANG variable' do
+      before do
+        allow(ENV).to receive(:[]).and_call_original
+        allow(ENV).to receive(:[]).with('LC_ALL').and_return(nil)
+      end
+
+      context 'without LC_CTYPE and LC_COLLATE' do
+        before do
+          allow(ENV).to receive(:[]).with('LC_CTYPE').and_return(nil)
+          allow(ENV).to receive(:[]).with('LC_COLLATE').and_return(nil)
+        end
+
+        it 'raises warning when LANG is non-UTF-8' do
+          allow(ENV).to receive(:[]).with('LANG').and_return('en_SG ISO-8859-1')
+
+          described_class.check_locale
+
+          expect_logged_warning("Environment variable LANG specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
+        end
+      end
+
+      context 'with only LC_CTYPE set to UTF-8' do
+        before do
+          allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_US.UTF-8')
+          allow(ENV).to receive(:[]).with('LC_COLLATE').and_return(nil)
+        end
+
+        it 'raises warning when LANG is non-UTF-8' do
+          allow(ENV).to receive(:[]).with('LANG').and_return('en_SG ISO-8859-1')
+
+          described_class.check_locale
+
+          expect_logged_warning("Environment variable LANG specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
+        end
+      end
+
+      context 'with both LC_CTYPE and LC_COLLATE set to UTF-8' do
+        before do
+          allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_US.UTF-8')
+          allow(ENV).to receive(:[]).with('LC_COLLATE').and_return('en_US.UTF-8')
+        end
+
+        it 'does not raise a warning even if LANG is not UTF-8' do
+          described_class.check_locale
+
+          expect_logged_warning("Environment variable LANG specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
+        end
+      end
+    end
+  end
+
+  describe '.resource_available?' do
+    cached(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+    subject(:omnibus_helper) { described_class.new(chef_run.node) }
+
+    it 'returns false for a resource that exists but has not been loaded in runtime' do
+      expect(omnibus_helper.resource_available?('runit_service[geo-logcursor]')).to be_falsey
+    end
+
+    it 'returns true for a resource that exists and is loaded in runtime' do
+      expect(omnibus_helper.resource_available?('runit_service[logrotated]'))
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb b/spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9zZWNyZXRzX2hlbHBlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb
@@ -0,0 +1,375 @@
+require 'chef_helper'
+require 'base64'
+
+RSpec.describe 'secrets' do
+  let(:chef_run) { ChefSpec::SoloRunner.new.converge('gitlab::default') }
+
+  HEX_KEY = /\h{128}/.freeze
+  RSA_KEY = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m.freeze
+
+  def stub_gitlab_secrets_json(secrets)
+    allow(File).to receive(:read).with('/etc/gitlab/gitlab-secrets.json').and_return(JSON.generate(secrets))
+  end
+
+  before do
+    allow(File).to receive(:directory?).and_call_original
+    allow(File).to receive(:exist?).and_call_original
+    allow(File).to receive(:read).and_call_original
+    allow(File).to receive(:open).and_call_original
+    allow(File).to receive(:open).with('/etc/gitlab/initial_root_password', 'w', 0600).and_yield(double(:file, write: true)).once
+  end
+
+  context 'when /etc/gitlab does not exist' do
+    it 'does not write secrets to the file' do
+      allow(File).to receive(:directory?).with('/etc/gitlab').and_return(false)
+      expect(File).not_to receive(:open).with('/etc/gitlab/gitlab-secrets.json', 'w')
+
+      chef_run
+    end
+  end
+
+  context 'when /etc/gitlab exists' do
+    let(:file) { double(:file) }
+    let(:new_secrets) { @new_secrets }
+    let(:gitlab_rb_ci_jwt_signing_key) { SecretsHelper.generate_rsa(4096).to_pem }
+
+    before do
+      allow(SecretsHelper).to receive(:system)
+      allow(File).to receive(:directory?).with('/etc/gitlab').and_return(true)
+      allow(File).to receive(:open).with('/etc/gitlab/gitlab-secrets.json', 'w', 0600).and_yield(file).once
+      allow(file).to receive(:puts) { |json| @new_secrets = JSON.parse(json) }
+      allow(file).to receive(:chmod).and_return(true)
+    end
+
+    context 'when there are no existing secrets' do
+      before do
+        allow(File).to receive(:exist?).with('/etc/gitlab/gitlab-secrets.json').and_return(false)
+
+        chef_run
+      end
+
+      it 'writes new secrets to the file, with different values for each' do
+        rails_keys = new_secrets['gitlab_rails']
+        hex_keys = rails_keys.values_at('db_key_base', 'otp_key_base', 'secret_key_base', 'encrypted_settings_key_base')
+        rsa_keys = rails_keys.values_at('openid_connect_signing_key', 'ci_jwt_signing_key')
+
+        expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a)
+        expect(hex_keys).to all(match(HEX_KEY))
+        expect(rsa_keys).to all(match(RSA_KEY))
+      end
+
+      it 'does not write legacy keys' do
+        expect(new_secrets).not_to have_key('gitlab_ci')
+        expect(new_secrets['gitlab_rails']).not_to have_key('jws_private_key')
+      end
+
+      it 'generates an appropriate secret for gitlab-workhorse' do
+        workhorse_secret = new_secrets['gitlab_workhorse']['secret_token']
+        expect(Base64.strict_decode64(workhorse_secret).length).to eq(32)
+      end
+
+      it 'generates an appropriate shared secret for gitlab-pages' do
+        pages_shared_secret = new_secrets['gitlab_pages']['api_secret_key']
+        expect(Base64.strict_decode64(pages_shared_secret).length).to eq(32)
+      end
+
+      it 'generates an appropriate shared secret for gitlab-kas' do
+        kas_shared_secret = new_secrets['gitlab_kas']['api_secret_key']
+        expect(Base64.strict_decode64(kas_shared_secret).length).to eq(32)
+      end
+    end
+
+    context 'gitlab.rb provided gitlab_pages.api_secret_key' do
+      before do
+        allow(Gitlab).to receive(:[]).and_call_original
+      end
+
+      it 'fails when provided gitlab_pages.shared_secret is not 32 bytes' do
+        stub_gitlab_rb(gitlab_pages: { api_secret_key: SecureRandom.base64(16) })
+
+        expect { chef_run }.to raise_error(RuntimeError, /gitlab_pages\['api_secret_key'\] should be exactly 32 bytes/)
+      end
+
+      it 'accepts provided gitlab_pages.api_secret_key when it is 32 bytes' do
+        api_secret_key = SecureRandom.base64(32)
+        stub_gitlab_rb(gitlab_pages: { api_secret_key: api_secret_key })
+
+        expect { chef_run }.not_to raise_error
+        expect(new_secrets['gitlab_pages']['api_secret_key']).to eq(api_secret_key)
+      end
+    end
+
+    context 'gitlab.rb provided gitlab_kas.api_secret_key' do
+      before do
+        allow(Gitlab).to receive(:[]).and_call_original
+      end
+
+      it 'fails when provided gitlab_kas.shared_secret is not 32 bytes' do
+        stub_gitlab_rb(gitlab_kas: { api_secret_key: SecureRandom.base64(16) })
+
+        expect { chef_run }.to raise_error(RuntimeError, /gitlab_kas\['api_secret_key'\] should be exactly 32 bytes/)
+      end
+
+      it 'accepts provided gitlab_kas.api_secret_key when it is 32 bytes' do
+        api_secret_key = SecureRandom.base64(32)
+        stub_gitlab_rb(gitlab_kas: { api_secret_key: api_secret_key })
+
+        expect { chef_run }.not_to raise_error
+        expect(new_secrets['gitlab_kas']['api_secret_key']).to eq(api_secret_key)
+      end
+    end
+
+    context 'when there are existing secrets in /etc/gitlab/gitlab-secrets.json' do
+      before do
+        allow(SecretsHelper).to receive(:system)
+        allow(File).to receive(:directory?).with('/etc/gitlab').and_return(true)
+        allow(File).to receive(:open).with('/etc/gitlab/gitlab-secrets.json', 'w').and_yield(file).once
+        allow(File).to receive(:exist?).with('/etc/gitlab/gitlab-secrets.json').and_return(true)
+      end
+
+      context 'when secrets are only partially present' do
+        before do
+          stub_gitlab_secrets_json(gitlab_ci: { db_key_base: 'json_ci_db_key_base' })
+          chef_run
+        end
+
+        it 'uses secrets from /etc/gitlab/gitlab-secrets.json where available' do
+          expect(new_secrets['gitlab_rails']['db_key_base']).to eq('json_ci_db_key_base')
+        end
+
+        it 'falls back further to generating new secrets' do
+          expect(new_secrets['gitlab_rails']['otp_key_base']).to match(HEX_KEY)
+        end
+      end
+
+      context 'when secrets exist under legacy keys' do
+        before do
+          stub_gitlab_secrets_json(
+            gitlab_ci: { db_key_base: 'json_ci_db_key_base', secret_token: 'json_ci_secret_token' },
+            gitlab_rails: {
+              secret_token: 'json_rails_secret_token',
+              jws_private_key: 'json_rails_jws_private_key'
+            }
+          )
+
+          chef_run
+        end
+
+        it 'moves gitlab_ci.db_key_base to gitlab_rails.db_key_base' do
+          expect(new_secrets['gitlab_rails']['db_key_base']).to eq('json_ci_db_key_base')
+        end
+
+        it 'moves gitlab_rails.secret_token to gitlab_rails.otp_key_base' do
+          expect(new_secrets['gitlab_rails']['otp_key_base']).to eq('json_rails_secret_token')
+        end
+
+        it 'moves gitlab_ci.db_key_base to gitlab_rails.secret_key_base' do
+          expect(new_secrets['gitlab_rails']['secret_key_base']).to eq('json_ci_db_key_base')
+        end
+
+        it 'moves gitlab_rails.jws_private_key to gitlab_rails.openid_connect_signing_key' do
+          expect(new_secrets['gitlab_rails']['openid_connect_signing_key']).to eq('json_rails_jws_private_key')
+        end
+
+        it 'ignores other, unused, secrets' do
+          expect(new_secrets.inspect).not_to include('json_ci_secret_token')
+        end
+      end
+    end
+
+    context 'when there are existing secrets in /etc/gitlab/gitlab.rb and /etc/gitlab/gitlab-secrets.json' do
+      before do
+        allow(Gitlab).to receive(:[]).and_call_original
+        allow(File).to receive(:exist?).with('/etc/gitlab/gitlab-secrets.json').and_return(true)
+      end
+
+      context 'when secrets are only partially present' do
+        before do
+          stub_gitlab_secrets_json(
+            gitlab_ci: { db_key_base: 'json_ci_db_key_base' },
+            gitlab_rails: {
+              secret_token: 'json_rails_secret_token',
+              jws_private_key: 'json_rails_jws_private_key'
+            }
+          )
+
+          stub_gitlab_rb(gitlab_ci: { db_key_base: 'rb_ci_db_key_base' })
+
+          chef_run
+        end
+
+        it 'uses secrets from /etc/gitlab/gitlab.rb when available' do
+          expect(new_secrets['gitlab_rails']['db_key_base']).to eq('rb_ci_db_key_base')
+        end
+
+        it 'falls back to secrets from /etc/gitlab/gitlab-secrets.json' do
+          expect(new_secrets['gitlab_rails']['otp_key_base']).to eq('json_rails_secret_token')
+        end
+
+        it 'falls back further to generating new secrets' do
+          expect(new_secrets['gitlab_shell']['secret_token']).to match(HEX_KEY)
+        end
+      end
+
+      context 'when secrets exist under legacy keys' do
+        before do
+          stub_gitlab_rb(gitlab_ci: { db_key_base: 'rb_ci_db_key_base',
+                                      secret_token: 'rb_ci_secret_token' })
+
+          stub_gitlab_secrets_json(
+            gitlab_rails: {
+              secret_token: 'json_rails_secret_token',
+              jws_private_key: 'json_rails_jws_private_key',
+              encrypted_settings_key_base: 'encrypted_settings_key_base',
+              ci_jwt_signing_key: gitlab_rb_ci_jwt_signing_key
+            }
+          )
+
+          chef_run
+        end
+
+        it 'moves gitlab_ci.db_key_base to gitlab_rails.db_key_base' do
+          expect(new_secrets['gitlab_rails']['db_key_base']).to eq('rb_ci_db_key_base')
+        end
+
+        it 'moves gitlab_rails.secret_token to gitlab_rails.otp_key_base' do
+          expect(new_secrets['gitlab_rails']['otp_key_base']).to eq('json_rails_secret_token')
+        end
+
+        it 'moves gitlab_ci.db_key_base to gitlab_rails.secret_key_base' do
+          expect(new_secrets['gitlab_rails']['secret_key_base']).to eq('rb_ci_db_key_base')
+        end
+
+        it 'moves gitlab_rails.jws_private_key to gitlab_rails.openid_connect_signing_key' do
+          expect(new_secrets['gitlab_rails']['openid_connect_signing_key']).to eq('json_rails_jws_private_key')
+        end
+
+        it 'ignores other, unused, secrets' do
+          expect(new_secrets.inspect).not_to include('rb_ci_secret_token')
+        end
+
+        it 'writes the correct data to secrets.yml' do
+          expect(chef_run).to create_templatesymlink('Create a secrets.yml and create a symlink to Rails root').with_variables(
+            'secrets' => {
+              'production' => {
+                'db_key_base' => 'rb_ci_db_key_base',
+                'secret_key_base' => 'rb_ci_db_key_base',
+                'otp_key_base' => 'json_rails_secret_token',
+                'encrypted_settings_key_base' => 'encrypted_settings_key_base',
+                'openid_connect_signing_key' => 'json_rails_jws_private_key',
+                'ci_jwt_signing_key' => gitlab_rb_ci_jwt_signing_key
+              }
+            }
+          )
+        end
+
+        it 'deletes the secret file' do
+          expect(chef_run).to delete_file('/var/opt/gitlab/gitlab-rails/etc/secret')
+          expect(chef_run).to delete_file('/opt/gitlab/embedded/service/gitlab-rails/.secret')
+        end
+      end
+
+      context 'when secrets are ambiguous and cannot be migrated automatically' do
+        before { stub_gitlab_secrets_json({}) }
+
+        it 'fails when gitlab_ci.db_key_base and gitlab_rails.db_key_base are different' do
+          stub_gitlab_rb(
+            gitlab_rails: { db_key_base: 'rb_rails_db_key_base' },
+            gitlab_ci: { db_key_base: 'rb_ci_db_key_base' }
+          )
+
+          expect(File).not_to receive(:open).with('/etc/gitlab/gitlab-secrets.json', 'w')
+          expect { chef_run }.to raise_error(RuntimeError, /db_key_base/)
+        end
+
+        it 'fails when the secret file does not match gitlab_rails.otp_key_base' do
+          secret_file = '/var/opt/gitlab/gitlab-rails/etc/secret'
+
+          stub_gitlab_rb(gitlab_rails: { otp_key_base: 'rb_rails_otp_key_base',
+                                         secret_key_base: 'rb_rails_secret_key_base' })
+
+          allow(File).to receive(:exist?).with(secret_file).and_return(true)
+          allow(File).to receive(:read).with(secret_file).and_return('secret_key_base')
+
+          expect { chef_run }.to raise_error(RuntimeError, /otp_key_base/)
+        end
+      end
+
+      context 'ci_jwt_signing_key' do
+        let(:secrets_json_ci_jwt_signing_key) { SecretsHelper.generate_rsa(4096).to_pem }
+
+        it 'uses the key from /etc/gitlab/gitlab.rb when available' do
+          stub_gitlab_secrets_json(
+            gitlab_rails: { ci_jwt_signing_key: secrets_json_ci_jwt_signing_key }
+          )
+
+          stub_gitlab_rb(
+            gitlab_rails: { ci_jwt_signing_key: gitlab_rb_ci_jwt_signing_key }
+          )
+
+          chef_run
+
+          expect(new_secrets['gitlab_rails']['ci_jwt_signing_key']).to eq(gitlab_rb_ci_jwt_signing_key)
+        end
+
+        it 'uses the key from /etc/gitlab/gitlab-secrets.json when available' do
+          stub_gitlab_secrets_json(
+            gitlab_rails: { ci_jwt_signing_key: secrets_json_ci_jwt_signing_key }
+          )
+
+          chef_run
+
+          expect(new_secrets['gitlab_rails']['ci_jwt_signing_key']).to eq(secrets_json_ci_jwt_signing_key)
+        end
+
+        it 'rejects invalid RSA keys' do
+          stub_gitlab_secrets_json({})
+          stub_gitlab_rb(
+            gitlab_rails: { ci_jwt_signing_key: 'invalid key' }
+          )
+
+          expect { chef_run }.to raise_error('ci_jwt_signing_key: The provided key is not valid RSA key')
+        end
+
+        it 'rejects RSA public keys' do
+          public_key = SecretsHelper.generate_rsa(4096).public_key.to_pem
+
+          stub_gitlab_secrets_json({})
+          stub_gitlab_rb(
+            gitlab_rails: { ci_jwt_signing_key: public_key }
+          )
+
+          expect { chef_run }.to raise_error('ci_jwt_signing_key: The provided key is not private RSA key')
+        end
+
+        it 'writes the correct data to secrets.yml' do
+          stub_gitlab_secrets_json({})
+
+          stub_gitlab_rb(
+            gitlab_rails: {
+              db_key_base: 'rb_ci_db_key_base',
+              secret_key_base: 'rb_ci_db_key_base',
+              otp_key_base: 'json_rails_secret_token',
+              encrypted_settings_key_base: 'encrypted_settings_key_base',
+              openid_connect_signing_key: 'json_rails_jws_private_key',
+              ci_jwt_signing_key: gitlab_rb_ci_jwt_signing_key
+            }
+          )
+
+          expect(chef_run).to create_templatesymlink('Create a secrets.yml and create a symlink to Rails root').with_variables(
+            'secrets' => {
+              'production' => {
+                'db_key_base' => 'rb_ci_db_key_base',
+                'secret_key_base' => 'rb_ci_db_key_base',
+                'otp_key_base' => 'json_rails_secret_token',
+                'encrypted_settings_key_base' => 'encrypted_settings_key_base',
+                'openid_connect_signing_key' => 'json_rails_jws_private_key',
+                'ci_jwt_signing_key' => gitlab_rb_ci_jwt_signing_key
+              }
+            }
+          )
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/services_helper_spec.rb b/spec/chef/cookbooks/package/libraries/services_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9zZXJ2aWNlc19oZWxwZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/services_helper_spec.rb
@@ -0,0 +1,229 @@
+require 'chef_helper'
+
+RSpec.describe Services do
+  let(:chef_run) { ChefSpec::SoloRunner.new }
+
+  subject(:services) { described_class }
+
+  describe '.enable_group' do
+    before do
+      chef_run.converge('gitlab::default')
+    end
+
+    it 'enables services in provide groups list' do
+      expect do
+        services.enable_group('pages_role')
+      end.to change { services.enabled?('gitlab_pages') }.from(false).to(true)
+    end
+
+    it 'skips services that dont `exist?`' do
+      expect do
+        services.enable_group('monitoring')
+      end.not_to change { services.enabled?('pgbouncer_exporter') }
+    end
+  end
+
+  describe '.disable_group' do
+    before do
+      chef_run.converge('gitlab::default')
+    end
+
+    it 'disables services in the provide groups list when setting to false' do
+      expect do
+        services.disable_group('redis')
+      end.to change { services.enabled?('redis') }.from(true).to(false)
+    end
+
+    it 'it allows disabling services in system group when setting to false and include_system: true' do
+      expect do
+        services.disable_group('system', include_system: true)
+      end.to change { services.enabled?('logrotate') }.from(true).to(false)
+    end
+  end
+
+  describe '.enable' do
+    before do
+      chef_run.converge('gitlab::default')
+    end
+
+    it 'enables provide service' do
+      expect do
+        services.enable('gitlab_pages')
+      end.to change { services.enabled?('gitlab_pages') }.from(false).to(true)
+    end
+
+    it 'skips services that dont `exist?`' do
+      expect do
+        services.enable('pgbouncer_exporter')
+      end.not_to change { services.enabled?('pgbouncer_exporter') }
+    end
+  end
+
+  describe '.disable' do
+    before do
+      chef_run.converge('gitlab::default')
+    end
+
+    it 'disables provide service' do
+      expect do
+        services.disable('puma')
+      end.to change { services.enabled?('puma') }.from(true).to(false)
+    end
+
+    it 'allows disabling service in system group when include_system: true' do
+      expect do
+        services.disable('logrotate', include_system: true)
+      end.to change { services.enabled?('logrotate') }.from(true).to(false)
+    end
+  end
+
+  describe '.set_status' do
+    before do
+      chef_run.converge('gitlab::default')
+    end
+
+    it 'enables provide service when setting to true' do
+      expect do
+        services.set_status('gitlab_pages', true)
+      end.to change { services.enabled?('gitlab_pages') }.from(false).to(true)
+    end
+
+    it 'disables provide service when setting to false' do
+      expect do
+        services.set_status('puma', false)
+      end.to change { services.enabled?('puma') }.from(true).to(false)
+    end
+
+    it 'allows disabling service in system group when setting to false and include_system: true' do
+      expect do
+        services.set_status('logrotate', false, include_system: true)
+      end.to change { services.enabled?('logrotate') }.from(true).to(false)
+    end
+  end
+
+  describe '.set_group_status' do
+    before do
+      chef_run.converge('gitlab::default')
+    end
+
+    it 'enables services in provide groups list when setting to true' do
+      expect do
+        services.set_group_status('pages_role', true)
+      end.to change { services.enabled?('gitlab_pages') }.from(false).to(true)
+    end
+
+    it 'disables services in the provide groups list when setting to false' do
+      expect do
+        services.set_group_status('redis', false)
+      end.to change { services.enabled?('redis') }.from(true).to(false)
+    end
+
+    it 'it allows disabling services in system group when setting to false and include_system: true' do
+      expect do
+        services.set_group_status('system', false, include_system: true)
+      end.to change { services.enabled?('logrotate') }.from(true).to(false)
+    end
+  end
+
+  describe '.system_services' do
+    it 'returns a list of services that are in the SYSTEM_GROUP' do
+      service_list = {
+        'redis' => { groups: %w[redis] },
+        'logrotate' => { groups: %w[system] }
+      }
+      services.add_services('test', service_list)
+
+      expect(services.system_services).to include('logrotate')
+    end
+  end
+
+  describe '.find_by_group' do
+    before do
+      service_list = {
+        'redis' => { groups: %w[redis] },
+        'redis_exporter' => { groups: %w[redis monitoring] }
+      }
+
+      services.add_services('test', service_list)
+    end
+
+    it 'returns a list of services that matches provided groups' do
+      expect(services.find_by_group('monitoring')).to eq(['redis_exporter'])
+    end
+
+    it 'returns an empty list when no provide groups match' do
+      expect(services.find_by_group('non_existent')).to eq([])
+    end
+  end
+
+  describe '.service_list' do
+    it 'returns a list of loaded known services by existing cookbooks' do
+      chef_run.converge('gitlab::default')
+
+      expect(services.service_list).to include(Services::BaseServices.list)
+    end
+  end
+
+  describe '.add_services' do
+    it 'adds provided services and register associated cookbook' do
+      services.add_services('gitlab', Services::BaseServices.list)
+
+      expect(services.service_list).to include(Services::BaseServices.list)
+      expect(services.send(:cookbook_services).keys).to include('gitlab')
+    end
+  end
+
+  describe '.enabled?' do
+    before do
+      chef_run.converge('gitlab::default')
+    end
+
+    context 'when status is set via configuration file' do
+      it 'returns true if set as enabled' do
+        Gitlab['mailroom']['enable'] = true
+
+        expect(services.enabled?('mailroom')).to be_truthy
+      end
+
+      it 'returns false if set as disabled' do
+        Gitlab['puma']['enable'] = false
+
+        expect(services.enabled?('puma')).to be_falsey
+      end
+    end
+
+    context 'when status is not set via configuration file' do
+      it 'returns true when its enabled via cookbooks' do
+        expect(services.enabled?('puma')).to be_truthy
+      end
+
+      it 'returns false when disabled via cookbooks' do
+        expect(services.enabled?('mailroom')).to be_falsey
+      end
+    end
+  end
+
+  describe '.exist?' do
+    it 'returns false for non existing service' do
+      expect(services.exist?('inexistent_service')).to be_falsey
+    end
+
+    context 'with only CE services registered' do
+      it 'returns true for CE services and false for EE services' do
+        chef_run.converge('gitlab::default')
+
+        expect(services.exist?('logrotate')).to be_truthy
+        expect(services.exist?('pgbouncer_exporter')).to be_falsey
+      end
+    end
+
+    context 'with CE and EE services registered' do
+      it 'returns true for EE services registered via add_service' do
+        chef_run.converge('gitlab::default', 'gitlab-ee::default')
+
+        expect(services.exist?('logrotate')).to be_truthy
+        expect(services.exist?('pgbouncer_exporter')).to be_truthy
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/storage_directory_helper_spec.rb b/spec/chef/cookbooks/package/libraries/storage_directory_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9zdG9yYWdlX2RpcmVjdG9yeV9oZWxwZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/storage_directory_helper_spec.rb
@@ -0,0 +1,109 @@
+require 'chef_helper'
+
+RSpec.describe StorageDirectoryHelper do
+  let(:success_shell) do
+    shell = instance_double(Mixlib::ShellOut)
+    allow(shell).to receive(:exitstatus).and_return(0)
+    shell
+  end
+
+  let(:fail_shell) do
+    shell = instance_double(Mixlib::ShellOut)
+    allow(shell).to receive(:exitstatus).and_return(1)
+    shell
+  end
+
+  before { allow(Gitlab).to receive(:[]).and_call_original }
+
+  describe :validate do
+    context 'owner provided' do
+      subject { ::StorageDirectoryHelper.new('git', nil, nil) }
+
+      it 'checks directory and owner and succeeds' do
+        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
+        expect(subject).to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args).and_return(success_shell)
+        expect(subject.validate('/tmp/validate')).to eq(true)
+      end
+
+      it 'fails when path is not a directory' do
+        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(fail_shell)
+        expect(subject).not_to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args)
+        expect(subject.validate('/tmp/validate')).to eq(false)
+      end
+
+      it 'fails when owner does not match' do
+        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
+        expect(subject).to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args).and_return(fail_shell)
+        expect(subject.validate('/tmp/validate')).to eq(false)
+      end
+    end
+
+    context 'owner and group provided' do
+      subject { ::StorageDirectoryHelper.new('git', 'root', nil) }
+
+      it 'checks directory, owner and group and succeeds' do
+        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
+        expect(subject).to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%U:%G' $(readlink -f /tmp/validate))\" = 'git:root' ]", any_args).and_return(success_shell)
+        expect(subject.validate('/tmp/validate')).to eq(true)
+      end
+
+      it 'fails when path is not a directory' do
+        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(fail_shell)
+        expect(subject).not_to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%U:%G' $(readlink -f /tmp/validate))\" = 'git:root' ]", any_args)
+        expect(subject.validate('/tmp/validate')).to eq(false)
+      end
+
+      it 'fails when group does not match' do
+        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
+        expect(subject).to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%U:%G' $(readlink -f /tmp/validate))\" = 'git:root' ]", any_args).and_return(fail_shell)
+        expect(subject.validate('/tmp/validate')).to eq(false)
+      end
+    end
+
+    context 'owner and permission mode provided' do
+      subject { ::StorageDirectoryHelper.new('git', nil, '700') }
+
+      it 'checks directory, owner and permissions and succeeds' do
+        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
+        expect(subject).to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args).and_return(success_shell)
+        expect(subject).to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%04a' $(readlink -f /tmp/validate) | grep -o '...$')\" = '700' ]", any_args).and_return(success_shell)
+        expect(subject.validate('/tmp/validate')).to eq(true)
+      end
+
+      it 'fails when path is not a directory' do
+        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(fail_shell)
+        expect(subject).not_to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%U:%G' $(readlink -f /tmp/validate))\" = 'git:root' ]", any_args)
+        expect(subject).not_to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%04a' $(readlink -f /tmp/validate) | grep -o '...$')\" = '700' ]", any_args)
+        expect(subject.validate('/tmp/validate')).to eq(false)
+      end
+
+      it 'fails when owner does not match' do
+        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
+        expect(subject).to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args).and_return(fail_shell)
+        expect(subject).not_to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%04a' $(readlink -f /tmp/validate) | grep -o '...$')\" = '700' ]", any_args)
+        expect(subject.validate('/tmp/validate')).to eq(false)
+      end
+
+      it 'fails when permissions do not match' do
+        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
+        expect(subject).to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args).and_return(success_shell)
+        expect(subject).to receive(:run_command)
+          .with("set -x && [ \"$(stat --printf='%04a' $(readlink -f /tmp/validate) | grep -o '...$')\" = '700' ]", any_args).and_return(fail_shell)
+        expect(subject.validate('/tmp/validate')).to eq(false)
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/libraries/systemd_helper_spec.rb b/spec/chef/cookbooks/package/libraries/systemd_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9zeXN0ZW1kX2hlbHBlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/package/libraries/systemd_helper_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+RSpec.describe SystemdHelper do
+  describe '#systemd_version' do
+    context 'when systemctl returns valid output' do
+      before do
+        valid_output = <<~OUTPUT
+          systemd 242 (242)
+          +PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT
+        OUTPUT
+
+        allow(IO).to receive(:popen).with(%w[systemctl --version]).and_return(valid_output)
+      end
+
+      it 'returns correct version' do
+        expect(described_class.systemd_version).to eq(242)
+      end
+    end
+
+    context 'when systemctl returns invalid output' do
+      before do
+        invalid_output = <<~OUTPUT
+          some invalid output
+        OUTPUT
+
+        allow(IO).to receive(:popen).with(%w[systemctl --version]).and_return(invalid_output)
+      end
+
+      it 'returns an insanely low version' do
+        expect(described_class.systemd_version).to eq(-999)
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/recipes/runit_spec.rb b/spec/chef/cookbooks/package/recipes/runit_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL3JlY2lwZXMvcnVuaXRfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/package/recipes/runit_spec.rb
@@ -0,0 +1,85 @@
+require 'chef_helper'
+
+RSpec.describe 'package::runit' do
+  before do
+    allow(Chef::Log).to receive(:info).and_call_original
+    allow(Chef::Log).to receive(:warn).and_call_original
+    allow(File).to receive(:directory?).and_call_original
+    allow(File).to receive(:directory?).with('/run/systemd/system').and_return(false)
+    allow(File).to receive(:exist?).and_call_original
+    allow(File).to receive(:exist?).with('/.dockerenv').and_return(false)
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'default values' do
+    let(:chef_run) { ChefSpec::SoloRunner.converge('package::runit') }
+
+    before do
+      allow_any_instance_of(Chef::Recipe).to receive(:include_recipe)
+    end
+
+    it 'detects systemd correctly and informs user' do
+      allow(File).to receive(:directory?).with('/run/systemd/system').and_return(true)
+
+      expect_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('package::runit_systemd')
+
+      chef_run
+    end
+
+    it 'detects docker container correctly' do
+      allow(File).to receive(:exist?).with('/.dockerenv').and_return(true)
+
+      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_systemd')
+      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_upstart')
+      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_sysvinit')
+
+      chef_run
+    end
+
+    it 'detects upstart correctly and informs user' do
+      allow(Open3).to receive(:capture3).with('/sbin/init --version | grep upstart').and_return(['', '', double(success?: true)])
+
+      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_systemd')
+      expect_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('package::runit_upstart')
+      expect(Chef::Log).to receive(:warn).with("Selected upstart because /sbin/init --version is showing upstart.")
+
+      chef_run
+    end
+
+    it 'fall backs to sysvinit correctly and informs user' do
+      allow(Open3).to receive(:capture3).with('/sbin/init --version | grep upstart').and_return(['', '', double(success?: false)])
+
+      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_systemd')
+      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_upstart')
+      expect_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('package::runit_sysvinit')
+
+      chef_run
+    end
+  end
+
+  context 'explicitly disabled' do
+    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+    before do
+      stub_gitlab_rb(
+        package: {
+          detect_init: false
+        }
+      )
+    end
+
+    it 'does not run any recipe' do
+      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_upstart')
+      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_systemd')
+      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_sysvinit')
+
+      chef_run
+    end
+
+    it 'informs the user about skipping init detection' do
+      expect(Chef::Log).to receive(:info).with('Skipped selecting an init system because it was explicitly disabled')
+
+      chef_run
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/recipes/runit_systemd_spec.rb b/spec/chef/cookbooks/package/recipes/runit_systemd_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL3JlY2lwZXMvcnVuaXRfc3lzdGVtZF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/package/recipes/runit_systemd_spec.rb
@@ -0,0 +1,92 @@
+require 'chef_helper'
+
+RSpec.describe 'package::runit_systemd' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::config', 'package::runit_systemd') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe 'setting TasksMax value' do
+    context 'when systemd version < 227' do
+      before do
+        allow(SystemdHelper).to receive(:systemd_version).and_return(200)
+      end
+
+      it 'does not include TasksMax setting in unit file' do
+        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service')
+        expect(chef_run).not_to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('TasksMax=4915')
+      end
+    end
+
+    context 'when systemd version >= 227' do
+      before do
+        allow(SystemdHelper).to receive(:systemd_version).and_return(235)
+      end
+
+      it 'include TasksMax setting in unit file' do
+        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('TasksMax=4915')
+      end
+    end
+
+    context 'with user provided value for TasksMax' do
+      before do
+        stub_gitlab_rb(
+          package: { systemd_tasks_max: 10000 }
+        )
+        allow(SystemdHelper).to receive(:systemd_version).and_return(235)
+      end
+
+      it 'sets correct value for TasksMax in unit file' do
+        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('TasksMax=10000')
+      end
+    end
+  end
+
+  describe 'setting WantedBy and After settings for unit file' do
+    context 'by default' do
+      it 'uses "multi-user.target" for WantedBy' do
+        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('WantedBy=multi-user.target')
+      end
+
+      it 'uses "multi-user.target" for WantedBy' do
+        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('After=multi-user.target')
+      end
+    end
+
+    context 'when WantedBy is specified' do
+      before do
+        stub_gitlab_rb(
+          package: { systemd_wanted_by: 'basic.target' }
+        )
+      end
+
+      it 'uses specified value for WantedBy' do
+        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('WantedBy=basic.target')
+      end
+
+      it 'uses value specified for WantedBy for After also' do
+        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('After=basic.target')
+      end
+    end
+
+    context 'when both WantedBy and After are specified' do
+      before do
+        stub_gitlab_rb(
+          package: {
+            systemd_wanted_by: 'foo',
+            systemd_after: 'bar'
+          }
+        )
+      end
+
+      it 'uses specified value for WantedBy' do
+        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('WantedBy=foo')
+      end
+
+      it 'uses specified value for After, not value given for WantedBy' do
+        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('After=bar')
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/resources/account_spec.rb b/spec/chef/cookbooks/package/resources/account_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL3Jlc291cmNlcy9hY2NvdW50X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/package/resources/account_spec.rb
@@ -0,0 +1,25 @@
+require 'chef_helper'
+
+RSpec.describe 'account' do
+  let(:runner) do
+    ChefSpec::SoloRunner.new(step_into: %w(account))
+  end
+
+  context 'create' do
+    let(:chef_run) { runner.converge('test_package::account_create') }
+
+    it 'creates user' do
+      expect(chef_run).to create_user('foobar').with_username('foo')
+      expect(chef_run).to create_group('foobar').with_group_name('bar')
+    end
+  end
+
+  context 'remove' do
+    let(:chef_run) { runner.converge('test_package::account_remove') }
+
+    it 'remove user' do
+      expect(chef_run).to remove_user('foo')
+      expect(chef_run).to remove_group('bar')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/resources/env_dir_spec.rb b/spec/chef/cookbooks/package/resources/env_dir_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL3Jlc291cmNlcy9lbnZfZGlyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/package/resources/env_dir_spec.rb
@@ -0,0 +1,26 @@
+require 'chef_helper'
+
+RSpec.describe 'env_dir' do
+  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(env_dir)) }
+  let(:chef_run) { runner.converge("test_package::env_dir_create") }
+
+  after do
+    FileUtils.rm_rf('/tmp/env')
+  end
+
+  it 'creates env directory' do
+    expect(chef_run).to create_directory('/tmp/env')
+  end
+
+  it 'creates env variable' do
+    expect(chef_run).to create_file('/tmp/env/FOO').with_content('Lorem')
+    expect(chef_run).to create_file('/tmp/env/BAR').with_content('Ipsum')
+  end
+
+  it 'deletes extraneous files' do
+    FileUtils.mkdir_p('/tmp/env')
+    FileUtils.touch('/tmp/env/erase-me')
+
+    expect(chef_run).to delete_file('/tmp/env/erase-me')
+  end
+end
diff --git a/spec/chef/cookbooks/package/resources/gitlab_sysctl_spec.rb b/spec/chef/cookbooks/package/resources/gitlab_sysctl_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL3Jlc291cmNlcy9naXRsYWJfc3lzY3RsX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/package/resources/gitlab_sysctl_spec.rb
@@ -0,0 +1,56 @@
+require 'chef_helper'
+
+RSpec.describe 'sysctl' do
+  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(gitlab_sysctl)) }
+  let(:chef_run) { runner.converge("package::sysctl", "test_package::gitlab_sysctl_create") }
+  let(:conf) { '90-omnibus-gitlab-foo.conf' }
+
+  context 'with default values' do
+    it 'creates sysctl.d directory for the service' do
+      expect(chef_run).to create_directory('/etc/sysctl.d').with(mode: '0755', recursive: true)
+    end
+
+    it 'creates conf file for the service and loads it' do
+      expect(chef_run).to create_file("create /opt/gitlab/embedded/etc/#{conf} foo").with_content("foo = 15000\n")
+
+      resource = chef_run.file("create /opt/gitlab/embedded/etc/#{conf} foo")
+      expect(resource).to notify("execute[load sysctl conf foo]").to(:run).delayed
+    end
+
+    it 'links conf file to /etc/sysctl.d location' do
+      expect(chef_run).to create_link("/etc/sysctl.d/#{conf}")
+    end
+
+    it 'loaded the settings' do
+      resource = chef_run.execute('load sysctl conf foo')
+      expect(resource.command).to eq("sysctl -e -p /opt/gitlab/embedded/etc/#{conf}")
+      expect(resource).to do_nothing
+    end
+  end
+
+  context 'when modify_kernel_parameters is false' do
+    let(:chef_run) { runner.converge("gitlab::config", "package::sysctl", "test_package::gitlab_sysctl_create") }
+
+    before do
+      allow(Gitlab).to receive(:[]).and_call_original
+
+      stub_gitlab_rb(
+        package: {
+          modify_kernel_parameters: false
+        }
+      )
+    end
+
+    it 'do not create sysctl.d directory' do
+      expect(chef_run).not_to create_directory('/etc/sysctl.d')
+    end
+
+    it 'does not create conf file' do
+      expect(chef_run).not_to create_file("create /opt/gitlab/embedded/etc/#{conf} foo")
+    end
+
+    it 'do not link conf file to /etc/sysctl.d location' do
+      expect(chef_run).not_to create_link("/etc/sysctl.d/#{conf}")
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/resources/storage_directory_spec.rb b/spec/chef/cookbooks/package/resources/storage_directory_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL3Jlc291cmNlcy9zdG9yYWdlX2RpcmVjdG9yeV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/package/resources/storage_directory_spec.rb
@@ -0,0 +1,22 @@
+require 'chef_helper'
+
+RSpec.describe 'storage_directory' do
+  let(:runner) do
+    ChefSpec::SoloRunner.new(step_into: %w(storage_directory)) do |node|
+      node.normal['gitlab']['manage-storage-directories']['enable'] = true
+    end
+  end
+  let(:chef_run) { runner.converge("test_package::storage_directory_create") }
+
+  it 'executes correct ruby block' do
+    allow_any_instance_of(StorageDirectoryHelper).to receive(:ensure_directory_exists).and_return(true)
+    allow_any_instance_of(StorageDirectoryHelper).to receive(:ensure_permissions_set).and_return(true)
+    allow_any_instance_of(StorageDirectoryHelper).to receive(:validate!).and_return(true)
+
+    expect(chef_run).to run_ruby_block('directory resource: /tmp/bar')
+    expect_any_instance_of(StorageDirectoryHelper).to receive(:ensure_directory_exists)
+
+    ruby_block = chef_run.ruby_block('directory resource: /tmp/bar')
+    ruby_block.block.call
+  end
+end
diff --git a/spec/chef/cookbooks/package/resources/templatesymlink_spec.rb b/spec/chef/cookbooks/package/resources/templatesymlink_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL3Jlc291cmNlcy90ZW1wbGF0ZXN5bWxpbmtfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/package/resources/templatesymlink_spec.rb
@@ -0,0 +1,26 @@
+require 'chef_helper'
+
+RSpec.describe 'templatesymlink' do
+  let(:runner) do
+    ChefSpec::SoloRunner.new(step_into: %w(templatesymlink))
+  end
+
+  context 'create' do
+    let(:chef_run) { runner.converge('test_package::templatesymlink_create') }
+
+    it 'creates symlinks' do
+      expect(chef_run).to create_link('/opt/gitlab/embedded/service/gitlab-rails/config/database.yml')
+    end
+
+    it 'populates conf file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/database.yml').with_content('value: 500')
+    end
+  end
+  context 'delete' do
+    let(:chef_run) { runner.converge('test_package::templatesymlink_delete') }
+
+    it 'deletes symlinks' do
+      expect(chef_run).to delete_link('/opt/gitlab/embedded/service/gitlab-rails/config/database.yml')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/package/resources/version_file_spec.rb b/spec/chef/cookbooks/package/resources/version_file_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL3Jlc291cmNlcy92ZXJzaW9uX2ZpbGVfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/package/resources/version_file_spec.rb
@@ -0,0 +1,24 @@
+require 'chef_helper'
+
+version_file_path = '/tmp/VERSION_TEST'
+version_check_cmd = 'echo 1.0.0-test'
+
+# Collect version as the custom resource would
+version = VersionHelper.version(version_check_cmd)
+
+RSpec.describe 'version_file' do
+  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(version_file)) }
+  let(:chef_run) { runner.converge("test_package::version_file_create") }
+
+  after do
+    FileUtils.rm_rf(version_file_path)
+  end
+
+  it 'creates version file' do
+    expect(chef_run).to create_file(version_file_path).with_content(version)
+  end
+
+  it 'restarts service' do
+    expect(chef_run.version_file('Test version file creation')).to notify('runit_service[foo]').to(:hup)
+  end
+end
diff --git a/spec/chef/cookbooks/patroni/libraries/patroni_helper_spec.rb b/spec/chef/cookbooks/patroni/libraries/patroni_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYXRyb25pL2xpYnJhcmllcy9wYXRyb25pX2hlbHBlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/patroni/libraries/patroni_helper_spec.rb
@@ -0,0 +1,144 @@
+require 'chef_helper'
+
+RSpec.describe PatroniHelper do
+  let(:chef_run) do
+    ChefSpec::SoloRunner.new(step_into: %w(patroni)).converge('gitlab-ee::default')
+  end
+
+  subject(:helper) { PatroniHelper.new(chef_run.node) }
+  let(:pg_helper) { PgHelper.new(chef_run.node) }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe '#ctl_command' do
+    it 'returns a full path to the ctl_command' do
+      expect(helper.ctl_command).to eq('/opt/gitlab/embedded/bin/patronictl')
+    end
+  end
+
+  describe '#bootstrapped?' do
+    before do
+      allow(File).to receive(:exist?).and_call_original
+    end
+
+    it 'returns true when patroni.dynamic.json exists in postgresql data directory' do
+      allow(File).to receive(:exist?).with('/var/opt/gitlab/postgresql/data/patroni.dynamic.json').and_return(true)
+
+      expect(helper.bootstrapped?).to eq(true)
+    end
+
+    it 'returns false when patroni.dynamic.json does not exist in postgresql data directory' do
+      allow(File).to receive(:exist?).with('/var/opt/gitlab/postgresql/data/patroni.dynamic.json').and_return(false)
+
+      expect(helper.bootstrapped?).to eq(false)
+    end
+  end
+
+  describe '#dynamic_settings' do
+    it 'returns a hash with required keys' do
+      expected_root_keys = PatroniHelper::DCS_ATTRIBUTES + %w[postgresql slots]
+
+      expect(helper.dynamic_settings(pg_helper).keys).to match_array(expected_root_keys)
+    end
+
+    context 'with standby cluster enabled' do
+      it 'includes standby cluster attributes' do
+        stub_gitlab_rb(
+          patroni: {
+            enable: true,
+            standby_cluster: {
+              enable: true
+            }
+          }
+        )
+
+        expected_root_keys = PatroniHelper::DCS_ATTRIBUTES + %w[postgresql slots standby_cluster]
+
+        expect(helper.dynamic_settings(pg_helper).keys).to match_array(expected_root_keys)
+      end
+    end
+  end
+
+  describe '#node_status' do
+    context 'when Patroni service is down' do
+      before do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('patroni').and_return(false)
+      end
+
+      it 'returns not running' do
+        expect(helper.node_status).to eq 'not running'
+      end
+    end
+
+    context 'when Patroni service is up' do
+      before do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
+        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('patroni').and_return(true)
+
+        result = spy('shellout')
+        allow(helper).to receive(:do_shell_out).and_return(result)
+        allow(result).to receive(:stdout).and_return(' running')
+      end
+
+      it 'returns running current node state' do
+        expect(helper.node_status).to eq 'running'
+      end
+    end
+  end
+
+  describe '#public_attributes' do
+    context 'when patroni is enabled' do
+      it 'returns a hash with required keys' do
+        stub_gitlab_rb(
+          patroni: {
+            enable: true
+          }
+        )
+
+        expected_patroni_keys = %w(config_dir data_dir log_dir api_address)
+
+        expect(helper.public_attributes.keys).to match_array('patroni')
+        expect(helper.public_attributes['patroni'].keys).to match_array(expected_patroni_keys)
+      end
+    end
+
+    context 'when patroni is enabled with tls api' do
+      it 'returns a hash with required keys' do
+        stub_gitlab_rb(
+          patroni: {
+            enable: true,
+            tls_certificate_file: '/path/to/crt.pem',
+            tls_key_file: '/path/to/key.pem',
+            tls_key_password: 'fakepassword',
+            tls_ca_file: '/path/to/ca.pem',
+            tls_ciphers: 'CIPHERS LIST',
+            tls_client_mode: 'optional',
+            tls_client_certificate_file: '/path/to/client.pem',
+            tls_client_key_file: '/path/to/client.key'
+          }
+        )
+
+        expected_tls_cfg = {
+          'tls_verify' => true,
+          'ca_file' => '/path/to/ca.pem',
+          'verify_client' => true,
+          'client_cert' => '/path/to/client.pem',
+          'client_key' => '/path/to/client.key'
+        }
+
+        expect(helper.public_attributes.keys).to match_array('patroni')
+        expect(helper.public_attributes['patroni']).to include(expected_tls_cfg)
+        expect(helper.public_attributes['patroni']['api_address']).to start_with('https://')
+      end
+    end
+
+    context 'when patroni is disabled' do
+      it 'returns an empty hash' do
+        expect(helper.public_attributes).to be_empty
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/patroni/recipes/patroni_spec.rb b/spec/chef/cookbooks/patroni/recipes/patroni_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wYXRyb25pL3JlY2lwZXMvcGF0cm9uaV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/patroni/recipes/patroni_spec.rb
@@ -0,0 +1,734 @@
+require 'chef_helper'
+require 'yaml'
+
+RSpec.describe 'patroni cookbook' do
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  let(:chef_run) do
+    ChefSpec::SoloRunner.new(step_into: %w(database_objects)).converge('gitlab-ee::default')
+  end
+
+  it 'should be disabled by default' do
+    expect(chef_run).to include_recipe('patroni::disable')
+  end
+
+  context 'when postgres_role is enabled' do
+    before do
+      stub_gitlab_rb(roles: %w(postgres_role))
+    end
+
+    it 'should be disabled' do
+      expect(chef_run).to include_recipe('patroni::disable')
+    end
+  end
+
+  context 'when patroni_role is configured' do
+    before do
+      stub_gitlab_rb(roles: %w(patroni_role))
+    end
+
+    it 'should be enabled' do
+      expect(chef_run).to include_recipe('patroni::enable')
+    end
+  end
+
+  context 'when patroni_role and postgres_role are configured' do
+    before do
+      stub_gitlab_rb(roles: %w(postgres_role patroni_role))
+    end
+
+    it 'should be enabled' do
+      expect(chef_run).to include_recipe('patroni::enable')
+    end
+  end
+
+  context 'when enabled with default config' do
+    before do
+      stub_gitlab_rb(
+        roles: %w(patroni_role),
+        postgresql: {
+          pgbouncer_user_password: ''
+        }
+      )
+      allow_any_instance_of(OmnibusHelper).to receive(:service_dir_enabled?).and_return(true)
+      allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
+      allow_any_instance_of(PgHelper).to receive(:bootstrapped?).and_return(false)
+      allow_any_instance_of(PgHelper).to receive(:is_replica?).and_return(false)
+    end
+
+    let(:default_patroni_config_pg13) do
+      {
+        name: 'fauxhai.local',
+        scope: 'postgresql-ha',
+        log: {
+          level: 'INFO'
+        },
+        consul: {
+          url: 'http://127.0.0.1:8500',
+          service_check_interval: '10s',
+          register_service: true,
+          checks: [],
+        },
+        postgresql: {
+          bin_dir: '/opt/gitlab/embedded/bin',
+          data_dir: '/var/opt/gitlab/postgresql/data',
+          config_dir: '/var/opt/gitlab/postgresql/data',
+          listen: :'5432',
+          connect_address: "#{Patroni.private_ipv4}:5432",
+          use_unix_socket: true,
+          parameters: {
+            unix_socket_directories: '/var/opt/gitlab/postgresql'
+          },
+          authentication: {
+            superuser: {
+              username: 'gitlab-psql'
+            },
+            replication: {
+              username: 'gitlab_replicator'
+            },
+          },
+          remove_data_directory_on_diverged_timelines: false,
+          remove_data_directory_on_rewind_failure: false,
+          basebackup: [
+            'no-password'
+          ],
+        },
+        bootstrap: {
+          dcs: {
+            loop_wait: 10,
+            ttl: 30,
+            retry_timeout: 10,
+            maximum_lag_on_failover: 1_048_576,
+            max_timelines_history: 0,
+            master_start_timeout: 300,
+            postgresql: {
+              use_pg_rewind: true,
+              use_slots: true,
+              parameters: {
+                wal_level: 'replica',
+                hot_standby: 'on',
+                wal_keep_size: 160,
+                max_replication_slots: 5,
+                max_connections: 200,
+                max_locks_per_transaction: 128,
+                max_worker_processes: 8,
+                max_wal_senders: 5,
+                checkpoint_timeout: 30,
+                max_prepared_transactions: 0,
+                track_commit_timestamp: 'off',
+                wal_log_hints: 'off'
+              },
+            },
+            slots: {},
+          },
+          method: 'gitlab_ctl',
+          gitlab_ctl: {
+            command: '/opt/gitlab/bin/gitlab-ctl patroni bootstrap --srcdir=/var/opt/gitlab/patroni/data'
+          }
+        },
+        restapi: {
+          listen: :'8008',
+          connect_address: "#{Patroni.private_ipv4}:8008",
+          allowlist_include_members: false
+        },
+      }
+    end
+
+    let(:default_patroni_config) do
+      {
+        name: 'fauxhai.local',
+        scope: 'postgresql-ha',
+        log: {
+          level: 'INFO'
+        },
+        consul: {
+          url: 'http://127.0.0.1:8500',
+          service_check_interval: '10s',
+          register_service: true,
+          checks: [],
+        },
+        postgresql: {
+          bin_dir: '/opt/gitlab/embedded/bin',
+          data_dir: '/var/opt/gitlab/postgresql/data',
+          config_dir: '/var/opt/gitlab/postgresql/data',
+          listen: :'5432',
+          connect_address: "#{Patroni.private_ipv4}:5432",
+          use_unix_socket: true,
+          parameters: {
+            unix_socket_directories: '/var/opt/gitlab/postgresql'
+          },
+          authentication: {
+            superuser: {
+              username: 'gitlab-psql'
+            },
+            replication: {
+              username: 'gitlab_replicator'
+            },
+          },
+          remove_data_directory_on_diverged_timelines: false,
+          remove_data_directory_on_rewind_failure: false,
+          basebackup: [
+            'no-password'
+          ],
+        },
+        bootstrap: {
+          dcs: {
+            loop_wait: 10,
+            ttl: 30,
+            retry_timeout: 10,
+            maximum_lag_on_failover: 1_048_576,
+            max_timelines_history: 0,
+            master_start_timeout: 300,
+            postgresql: {
+              use_pg_rewind: true,
+              use_slots: true,
+              parameters: {
+                wal_level: 'replica',
+                hot_standby: 'on',
+                wal_keep_segments: 10,
+                max_replication_slots: 5,
+                max_connections: 200,
+                max_locks_per_transaction: 128,
+                max_worker_processes: 8,
+                max_wal_senders: 5,
+                checkpoint_timeout: 30,
+                max_prepared_transactions: 0,
+                track_commit_timestamp: 'off',
+                wal_log_hints: 'off'
+              },
+            },
+            slots: {},
+          },
+          method: 'gitlab_ctl',
+          gitlab_ctl: {
+            command: '/opt/gitlab/bin/gitlab-ctl patroni bootstrap --srcdir=/var/opt/gitlab/patroni/data'
+          }
+        },
+        restapi: {
+          listen: :'8008',
+          connect_address: "#{Patroni.private_ipv4}:8008",
+          allowlist_include_members: false
+        },
+      }
+    end
+
+    it 'should enable patroni service and disable postgresql runit service' do
+      expect(chef_run).to enable_runit_service('patroni')
+      expect(chef_run).to disable_runit_service('postgresql')
+    end
+
+    it 'should notify patroni service to hup' do
+      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).and_call_original
+      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).with('patroni').and_return(true)
+
+      expect(chef_run.template('/var/opt/gitlab/patroni/patroni.yaml')).to notify('runit_service[patroni]').to(:hup)
+    end
+
+    it 'should skip standalone postgresql configuration' do
+      expect(chef_run).to create_postgresql_config('gitlab')
+      expect(chef_run.postgresql_config('gitlab')).not_to notify('execute[start postgresql]').to(:run)
+      expect(chef_run).not_to run_execute(/(start|reload) postgresql/)
+    end
+
+    it 'should create database objects (roles, databses, extension)' do
+      expect(chef_run).not_to run_execute('/opt/gitlab/embedded/bin/initdb -D /var/opt/gitlab/postgresql/data -E UTF8')
+      expect(chef_run).to create_postgresql_user('gitlab')
+      expect(chef_run).to create_postgresql_user('gitlab_replicator')
+      expect(chef_run).to create_pgbouncer_user('rails')
+      expect(chef_run).to create_postgresql_database('gitlabhq_production')
+      expect(chef_run).to enable_postgresql_extension('pg_trgm')
+      expect(chef_run).to enable_postgresql_extension('btree_gist')
+    end
+
+    it 'should create patroni configuration file for PostgreSQL 12' do
+      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
+        expect(YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)).to eq(default_patroni_config)
+      }
+    end
+
+    it 'should create patroni configuration file for PostgreSQL 13' do
+      allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('13.0'))
+      allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('13.0'))
+
+      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
+        expect(YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)).to eq(default_patroni_config_pg13)
+      }
+    end
+  end
+
+  context 'when enabled with specific config' do
+    before do
+      stub_gitlab_rb(
+        roles: %w(postgres_role),
+        postgresql: {
+          username: 'test_psql_user',
+          sql_user: 'test_sql_user',
+          sql_user_password: 'dbda601b8d4dc3d1697ef84dbbb8e61b',
+          sql_replication_user: 'test_sql_replication_user',
+          sql_replication_password: '48e84afb4b268128ac14f7c66fc7af42',
+          pgbouncer_user: 'test_pgbouncer_user',
+          pgbouncer_user_password: '2bc94731612abb74aea7805a41dfcb09',
+          connect_port: 15432,
+        },
+        patroni: {
+          enable: true,
+          scope: 'test-scope',
+          name: 'test-node-name',
+          log_level: 'DEBUG',
+          loop_wait: 20,
+          ttl: 60,
+          master_start_timeout: 600,
+          use_slots: false,
+          use_pg_rewind: true,
+          connect_address: '1.2.3.4',
+          connect_port: 18008,
+          username: 'gitlab',
+          password: 'restapipassword',
+          replication_password: 'fakepassword',
+          allowlist: ['1.2.3.4/32', '127.0.0.1/32'],
+          allowlist_include_members: false,
+          remove_data_directory_on_diverged_timelines: true,
+          remove_data_directory_on_rewind_failure: true,
+          replication_slots: {
+            'geo_secondary' => { 'type' => 'physical' }
+          },
+          consul: {
+            service_check_interval: '20s'
+          },
+          postgresql: {
+            wal_keep_segments: 16,
+            max_wal_senders: 4,
+            max_replication_slots: 4
+          },
+          tags: {
+            nofailover: true
+          },
+          callbacks: {
+            on_role_change: "/patroni/scripts/post-failover-maintenance.sh"
+          },
+          recovery_conf: {
+            restore_command: "/opt/wal-g/bin/wal-g wal-fetch %f %p"
+          }
+        }
+      )
+    end
+
+    it 'should be reflected in patroni configuration file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
+        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
+
+        expect(cfg).to include(
+          name: 'test-node-name',
+          scope: 'test-scope',
+          log: {
+            level: 'DEBUG'
+          },
+          tags: {
+            nofailover: true
+          }
+        )
+        expect(cfg[:consul][:service_check_interval]).to eq('20s')
+        expect(cfg[:postgresql][:connect_address]).to eq('1.2.3.4:15432')
+        expect(cfg[:postgresql][:authentication]).to eq(
+          superuser: {
+            username: 'test_psql_user'
+          },
+          replication: {
+            username: 'test_sql_replication_user',
+            password: 'fakepassword'
+          }
+        )
+        expect(cfg[:postgresql][:callbacks]).to eq(
+          on_role_change: "/patroni/scripts/post-failover-maintenance.sh"
+        )
+        expect(cfg[:postgresql][:recovery_conf]).to eq(
+          restore_command: "/opt/wal-g/bin/wal-g wal-fetch %f %p"
+        )
+        expect(cfg[:restapi]).to include(
+          connect_address: '1.2.3.4:18008',
+          authentication: {
+            username: 'gitlab',
+            password: 'restapipassword'
+          },
+          allowlist: ['1.2.3.4/32', '127.0.0.1/32'],
+          allowlist_include_members: false
+        )
+        expect(cfg[:bootstrap][:dcs]).to include(
+          loop_wait: 20,
+          ttl: 60,
+          master_start_timeout: 600,
+          slots: {
+            geo_secondary: { type: 'physical' }
+          }
+        )
+        expect(cfg[:bootstrap][:dcs][:postgresql]).to include(
+          use_slots: false,
+          use_pg_rewind: true
+        )
+        expect(cfg[:bootstrap][:dcs][:postgresql][:parameters]).to include(
+          wal_keep_segments: 16,
+          max_wal_senders: 4,
+          max_replication_slots: 4
+        )
+        expect(cfg[:postgresql][:remove_data_directory_on_rewind_failure]).to be true
+        expect(cfg[:postgresql][:remove_data_directory_on_diverged_timelines]).to be true
+      }
+    end
+
+    it 'should reflect into dcs config file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
+        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
+
+        expect(cfg).to include(
+          loop_wait: 20,
+          ttl: 60,
+          master_start_timeout: 600,
+          slots: {
+            geo_secondary: { type: 'physical' }
+          }
+        )
+        expect(cfg[:postgresql]).to include(
+          use_slots: false,
+          use_pg_rewind: true
+        )
+        expect(cfg[:postgresql][:parameters]).to include(
+          wal_keep_segments: 16,
+          max_wal_senders: 4,
+          max_replication_slots: 4
+        )
+      }
+    end
+  end
+
+  context 'when standby cluster is enabled' do
+    before do
+      stub_gitlab_rb(
+        roles: %w(postgres_role),
+        patroni: {
+          enable: true,
+          use_pg_rewind: true,
+          replication_password: 'fakepassword',
+          standby_cluster: {
+            enable: true,
+            host: '1.2.3.4',
+            port: 5432,
+            primary_slot_name: 'geo_secondary'
+          }
+        },
+        postgresql: {
+          sql_user_password: 'a4125c87ce2572ce271cd77e0de9a0ad',
+          sql_replication_password: 'e64b415e9b9a34ac7ac6e53ae16ccacb',
+          md5_auth_cidr_addresses: '1.2.3.4/32'
+        }
+      )
+    end
+
+    it 'should be reflected in patroni configuration file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
+        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
+
+        expect(cfg[:postgresql][:authentication]).to include(
+          replication: {
+            username: 'gitlab_replicator',
+            password: 'fakepassword'
+          }
+        )
+        expect(cfg[:bootstrap][:dcs]).to include(
+          standby_cluster: {
+            host: '1.2.3.4',
+            port: 5432,
+            primary_slot_name: 'geo_secondary'
+          }
+        )
+        expect(cfg[:bootstrap][:dcs][:postgresql]).to include(
+          use_pg_rewind: true
+        )
+      }
+    end
+
+    it 'should reflect into dcs config file' do
+      expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
+        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
+
+        expect(cfg).to include(
+          standby_cluster: {
+            host: '1.2.3.4',
+            port: 5432,
+            primary_slot_name: 'geo_secondary'
+          }
+        )
+        expect(cfg[:postgresql]).to include(
+          use_pg_rewind: true
+        )
+      }
+    end
+  end
+
+  context 'when building a cluster' do
+    before do
+      stub_gitlab_rb(
+        roles: %w(postgres_role),
+        patroni: {
+          enable: true
+        }
+      )
+    end
+
+    context 'from scratch' do
+      before do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_dir_enabled?).and_return(false)
+      end
+
+      it 'should enable patroni service and disable postgresql runit service' do
+        expect(chef_run).to enable_runit_service('patroni')
+        expect(chef_run).to disable_runit_service('postgresql')
+      end
+    end
+
+    context 'converting a standalone instance to a cluster member' do
+      before do
+        allow_any_instance_of(OmnibusHelper).to receive(:service_dir_enabled?).and_return(true)
+        allow_any_instance_of(PatroniHelper).to receive(:node_status).and_return('running')
+      end
+
+      it 'should signal to node to restart postgresql and disable its runit service' do
+        expect(chef_run).to enable_runit_service('patroni')
+        expect(chef_run).to disable_runit_service('postgresql')
+        expect(chef_run).to run_execute('signal to restart postgresql')
+      end
+    end
+
+    context 'on a replica' do
+      before do
+        allow_any_instance_of(PgHelper).to receive(:replica?).and_return(true)
+      end
+
+      it 'should not create database objects' do
+        expect(chef_run).not_to create_postgresql_user('gitlab')
+        expect(chef_run).not_to create_postgresql_user('gitlab_replicator')
+        expect(chef_run).not_to create_pgbouncer_user('patroni')
+        expect(chef_run).not_to run_execute('create gitlabhq_production database')
+        expect(chef_run).not_to enable_postgresql_extension('pg_trgm')
+        expect(chef_run).not_to enable_postgresql_extension('btree_gist')
+      end
+    end
+  end
+
+  context 'postgresql dynamic configuration' do
+    before do
+      allow(Gitlab).to receive(:[]).and_call_original
+    end
+
+    context 'with no explicit override' do
+      before do
+        stub_gitlab_rb(
+          roles: %w(postgres_role),
+          patroni: {
+            enable: true
+          }
+        )
+      end
+
+      it 'should use default values from postgresql cookbook and handle corner cases' do
+        expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
+          cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
+
+          expect(cfg[:postgresql][:use_pg_rewind]).to be(true)
+          expect(cfg[:postgresql][:parameters]).to include(
+            max_connections: 200,
+            max_locks_per_transaction: 128,
+            max_worker_processes: 8,
+            max_prepared_transactions: 0,
+            track_commit_timestamp: 'off',
+            wal_log_hints: 'off',
+            max_wal_senders: 5,
+            max_replication_slots: 5,
+            wal_keep_segments: 10,
+            checkpoint_timeout: 30
+          )
+        }
+      end
+    end
+
+    context 'with no explicit override and non-default postgresql settings' do
+      before do
+        stub_gitlab_rb(
+          roles: %w(postgres_role),
+          patroni: {
+            enable: true,
+          },
+          postgresql: {
+            max_connections: 123,
+            max_locks_per_transaction: 321,
+            max_worker_processes: 12,
+            wal_log_hints: 'foo',
+            max_wal_senders: 11,
+            max_replication_slots: 13,
+          }
+        )
+      end
+
+      it 'should use default values from postgresql cookbook' do
+        expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
+          cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
+
+          expect(cfg[:postgresql][:use_pg_rewind]).to be(true)
+          expect(cfg[:postgresql][:parameters]).to include(
+            max_connections: 123,
+            max_locks_per_transaction: 321,
+            max_worker_processes: 12,
+            max_prepared_transactions: 0,
+            track_commit_timestamp: 'off',
+            wal_log_hints: 'foo',
+            max_wal_senders: 11,
+            max_replication_slots: 13,
+            wal_keep_segments: 10,
+            checkpoint_timeout: 30
+          )
+        }
+      end
+    end
+
+    context 'with explicit override' do
+      before do
+        stub_gitlab_rb(
+          roles: %w(postgres_role),
+          patroni: {
+            enable: true,
+            postgresql: {
+              max_connections: 100,
+              max_locks_per_transaction: 64,
+              max_worker_processes: 4,
+              wal_log_hints: 'on',
+              max_wal_senders: 0,
+              max_replication_slots: 0,
+              checkpoint_timeout: '5min'
+            }
+          }
+        )
+      end
+
+      it 'should use default values from postgresql cookbook' do
+        expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
+          cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
+
+          expect(cfg[:postgresql][:use_pg_rewind]).to be(true)
+          expect(cfg[:postgresql][:parameters]).to include(
+            max_connections: 100,
+            max_locks_per_transaction: 64,
+            max_worker_processes: 4,
+            max_wal_senders: 0,
+            max_replication_slots: 0,
+            wal_log_hints: 'on'
+          )
+        }
+      end
+    end
+
+    context 'when pg_rewind is enabled' do
+      before do
+        stub_gitlab_rb(
+          roles: %w(postgres_role),
+          patroni: {
+            enable: true,
+            use_pg_rewind: true,
+            remove_data_directory_on_diverged_timelines: true,
+            remove_data_directory_on_rewind_failure: true
+          }
+        )
+      end
+
+      it 'should use default values from postgresql cookbook' do
+        expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
+          cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
+
+          expect(cfg[:postgresql][:use_pg_rewind]).to be(true)
+          expect(cfg[:postgresql][:parameters]).to include(
+            max_connections: 200,
+            max_locks_per_transaction: 128,
+            max_worker_processes: 8,
+            wal_log_hints: 'on'
+          )
+        }
+      end
+    end
+  end
+
+  context 'when patroni is enabled but consul is not' do
+    let(:chef_run) do
+      converge_config('gitlab-ee::default', is_ee: true)
+    end
+
+    before do
+      stub_gitlab_rb(
+        patroni: {
+          enable: true
+        }
+      )
+    end
+
+    it 'expects a warning to be printed' do
+      chef_run
+
+      expect_logged_warning(/Patroni is enabled but Consul seems to be disabled/)
+    end
+  end
+
+  context 'when tls is enabled' do
+    it 'should only set the path to the tls certificate and key' do
+      stub_gitlab_rb(
+        roles: %w(postgres_role),
+        patroni: {
+          enable: true,
+          tls_certificate_file: '/path/to/crt.pem',
+          tls_key_file: '/path/to/key.pem'
+        }
+      )
+
+      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
+        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
+
+        expect(cfg[:restapi][:certfile]).to eq('/path/to/crt.pem')
+        expect(cfg[:restapi][:keyfile]).to eq('/path/to/key.pem')
+        expect(cfg[:restapi][:keyfile_password]).to be(nil)
+        expect(cfg[:restapi][:cafile]).to be(nil)
+        expect(cfg[:restapi][:ciphers]).to be(nil)
+        expect(cfg[:restapi][:verify_client]).to be(nil)
+        expect(cfg[:ctl]).to be(nil)
+      }
+    end
+
+    it 'should set all available tls configuration including tls certificate and key paths' do
+      stub_gitlab_rb(
+        roles: %w(postgres_role),
+        patroni: {
+          enable: true,
+          tls_certificate_file: '/path/to/crt.pem',
+          tls_key_file: '/path/to/key.pem',
+          tls_key_password: 'fakepassword',
+          tls_ca_file: '/path/to/ca.pem',
+          tls_ciphers: 'CIPHERS LIST',
+          tls_client_mode: 'optional',
+          tls_client_certificate_file: '/path/to/client.pem',
+          tls_client_key_file: '/path/to/client.key'
+        }
+      )
+
+      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
+        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
+
+        expect(cfg[:restapi][:certfile]).to eq('/path/to/crt.pem')
+        expect(cfg[:restapi][:keyfile]).to eq('/path/to/key.pem')
+        expect(cfg[:restapi][:keyfile_password]).to eq('fakepassword')
+        expect(cfg[:restapi][:cafile]).to eq('/path/to/ca.pem')
+        expect(cfg[:restapi][:ciphers]).to eq('CIPHERS LIST')
+        expect(cfg[:restapi][:verify_client]).to eq('optional')
+        expect(cfg[:ctl][:insecure]).to eq(false)
+        expect(cfg[:ctl][:certfile]).to eq('/path/to/client.pem')
+        expect(cfg[:ctl][:keyfile]).to eq('/path/to/client.key')
+      }
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/pgbouncer/libraries/pgbouncer_helper_spec.rb b/spec/chef/cookbooks/pgbouncer/libraries/pgbouncer_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wZ2JvdW5jZXIvbGlicmFyaWVzL3BnYm91bmNlcl9oZWxwZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/pgbouncer/libraries/pgbouncer_helper_spec.rb
@@ -0,0 +1,165 @@
+require 'chef_helper'
+
+RSpec.describe PgbouncerHelper do
+  let(:chef_run) { converge_config(is_ee: true) }
+  subject { described_class.new(chef_run.node) }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe '#running?' do
+    cached(:chef_run) { converge_config(is_ee: true) }
+
+    it 'returns true when pgbouncer is running' do
+      stub_service_success_status('pgbouncer', true)
+
+      expect(subject.running?).to be_truthy
+    end
+
+    it 'returns false when pgbouncer is not running' do
+      stub_service_success_status('pgbouncer', false)
+
+      expect(subject.running?).to be_falsey
+    end
+  end
+
+  describe '#pg_auth_type_prefix' do
+    using RSpec::Parameterized::TableSyntax
+    where(:type, :prefix) do
+      'md5' | 'md5'
+      'scram-sha-256' | 'SCRAM-SHA-256$'
+      'MD5' | 'md5'
+      'SCRAM-SHA-256' | 'SCRAM-SHA-256$'
+      'plain' | nil
+      'ScRaM-ShA-256' | 'SCRAM-SHA-256$'
+    end
+
+    with_them do
+      it 'responds to default values' do
+        expect(subject.pg_auth_type_prefix(type)).to eq(prefix)
+      end
+    end
+  end
+
+  describe '#pgbouncer_admin_config' do
+    context 'by default' do
+      it 'by default' do
+        expect(subject.pgbouncer_admin_config).to eq('user=pgbouncer dbname=pgbouncer sslmode=disable port=6432 host=/var/opt/gitlab/pgbouncer')
+      end
+    end
+
+    context 'with custom sttings' do
+      before do
+        stub_gitlab_rb(
+          postgresql: {
+            pgbouncer_user: 'tester',
+          },
+          pgbouncer: {
+            listen_port: 1234,
+            data_directory: '/tmp'
+          }
+        )
+      end
+
+      it 'uses custom settings' do
+        expect(subject.pgbouncer_admin_config).to eq('user=tester dbname=pgbouncer sslmode=disable port=1234 host=/tmp')
+      end
+    end
+  end
+
+  describe '#pg_auth_users' do
+    context 'by default' do
+      it 'by default' do
+        expect(subject.pg_auth_users).to be_empty
+      end
+    end
+
+    context 'with users' do
+      before do
+        stub_gitlab_rb(
+          pgbouncer: {
+            users: {
+              fakeuser: {
+                password: 'fakepassword'
+              }
+            }
+          }
+        )
+      end
+
+      it 'returns a hash' do
+        expect(subject.pg_auth_users).to eq 'fakeuser' => { 'password' => 'fakepassword' }
+      end
+    end
+
+    context 'with databases' do
+      before do
+        stub_gitlab_rb(
+          pgbouncer: {
+            databases: {
+              fakedb: {
+                user: 'fakeuser',
+                password: 'fakepassword'
+              }
+            }
+          }
+        )
+      end
+
+      it 'returns a hash' do
+        expect(subject.pg_auth_users).to eq 'fakeuser' => { 'password' => 'fakepassword' }
+      end
+    end
+
+    context 'with both' do
+      before do
+        stub_gitlab_rb(
+          pgbouncer: {
+            databases: {
+              fakedb: {
+                user: 'fakedbuser',
+                password: 'fakepassword'
+              }
+            },
+            users: {
+              fakeusersuser: {
+                password: 'fakepassword'
+              }
+            }
+          }
+        )
+      end
+
+      it 'should merge both' do
+        expected = {
+          'fakeusersuser' => { 'password' => 'fakepassword' }, 'fakedbuser' => { 'password' => 'fakepassword' }
+        }
+        expect(subject.pg_auth_users).to eq expected
+      end
+    end
+
+    context 'with multiple databases' do
+      before do
+        stub_gitlab_rb(
+          pgbouncer: {
+            databases: {
+              fakedb_one: {
+                user: 'fakeuser',
+                password: 'fakepassword'
+              },
+              fakedb_two: {
+                user: 'fakeuser',
+                password: 'fakepassword'
+              }
+            }
+          }
+        )
+      end
+
+      it 'should only have one entry' do
+        expect(subject.pg_auth_users).to eq 'fakeuser' => { 'password' => 'fakepassword' }
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/pgbouncer/recipes/pgbouncer_spec.rb b/spec/chef/cookbooks/pgbouncer/recipes/pgbouncer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wZ2JvdW5jZXIvcmVjaXBlcy9wZ2JvdW5jZXJfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/pgbouncer/recipes/pgbouncer_spec.rb
@@ -0,0 +1,395 @@
+#
+# Copyright:: Copyright (c) 2017 GitLab Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'chef_helper'
+
+RSpec.describe 'pgbouncer' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
+  let(:pgbouncer_ini) { '/var/opt/gitlab/pgbouncer/pgbouncer.ini' }
+  let(:databases_json) { '/var/opt/gitlab/pgbouncer/databases.json' }
+  let(:default_vars) do
+    {
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe 'when enabled' do
+    before do
+      stub_gitlab_rb(
+        pgbouncer: {
+          enable: true,
+          databases: {
+            gitlabhq_production: {
+              host: '1.2.3.4'
+            }
+          }
+        },
+        postgresql: {
+          pgbouncer_user: 'fakeuser',
+          pgbouncer_user_password: 'fakeuserpassword'
+        }
+      )
+    end
+
+    it 'includes the pgbouncer recipe' do
+      expect(chef_run).to include_recipe('pgbouncer::enable')
+    end
+
+    it 'includes the postgresql user recipe' do
+      expect(chef_run).to include_recipe('postgresql::user')
+    end
+
+    it_behaves_like 'enabled runit service', 'pgbouncer', 'root', 'root'
+
+    it 'creates necessary env variable files' do
+      expect(chef_run).to create_env_dir('/opt/gitlab/etc/pgbouncer/env').with_variables(default_vars)
+    end
+
+    it 'creates the appropriate directories' do
+      expect(chef_run).to create_directory('/var/log/gitlab/pgbouncer')
+      expect(chef_run).to create_directory('/var/opt/gitlab/pgbouncer')
+    end
+
+    it 'installs pgbouncer.ini with default values' do
+      # Default values are pulled from:
+      # https://github.com/pgbouncer/pgbouncer/blob/6ef66f0139b9c8a5c0747f2a6157d008b87bf0c5/etc/pgbouncer.ini
+      expect(chef_run).to render_file(pgbouncer_ini).with_content { |content|
+        expect(content).to match(/^listen_addr = 0\.0\.0\.0$/)
+        expect(content).to match(/^listen_port = 6432$/)
+        expect(content).to match(/^pool_mode = transaction$/)
+        expect(content).to match(/^server_reset_query = DISCARD ALL$/)
+        expect(content).to match(/^application_name_add_host = 1$/)
+        expect(content).to match(/^max_client_conn = 2048$/)
+        expect(content).to match(/^default_pool_size = 100$/)
+        expect(content).to match(/^min_pool_size = 0$/)
+        expect(content).to match(/^reserve_pool_size = 5$/)
+        expect(content).to match(/^reserve_pool_timeout = 5.0$/)
+        expect(content).to match(/^server_round_robin = 0$/)
+        expect(content).to match(/^auth_type = md5$/)
+        expect(content).to match(/^log_connections = 0/)
+        expect(content).to match(/^server_idle_timeout = 30.0$/)
+        expect(content).to match(/^dns_max_ttl = 15.0$/)
+        expect(content).to match(/^dns_zone_check_period = 0$/)
+        expect(content).to match(/^dns_nxdomain_ttl = 15.0$/)
+        expect(content).to match(%r{^auth_file = /var/opt/gitlab/pgbouncer/pg_auth$})
+        expect(content).to match(/^admin_users = gitlab-psql, postgres, pgbouncer$/)
+        expect(content).to match(/^stats_users = gitlab-psql, postgres, pgbouncer$/)
+        expect(content).to match(/^ignore_startup_parameters = extra_float_digits$/)
+        expect(content).to match(%r{^unix_socket_dir = /var/opt/gitlab/pgbouncer$})
+        expect(content).to match(%r{^%include /var/opt/gitlab/pgbouncer/databases.ini})
+        expect(content).to match(/^unix_socket_mode = 0777$/)
+        expect(content).to match(/^client_tls_sslmode = disable$/)
+        expect(content).to match(/^client_tls_protocols = all$/)
+        expect(content).to match(/^client_tls_dheparams = auto$/)
+        expect(content).to match(/^client_tls_ecdhcurve = auto$/)
+        expect(content).to match(/^server_tls_sslmode = disable$/)
+        expect(content).to match(/^server_tls_protocols = all$/)
+        expect(content).to match(/^server_tls_ciphers = fast$/)
+        expect(content).to match(/^server_reset_query_always = 0$/)
+        expect(content).to match(/^server_check_query = select 1$/)
+        expect(content).to match(/^server_check_delay = 30$/)
+        expect(content).to match(/^syslog = 0$/)
+        expect(content).to match(/^syslog_facility = daemon$/)
+        expect(content).to match(/^syslog_ident = pgbouncer$/)
+        expect(content).to match(/^log_disconnections = 1$/)
+        expect(content).to match(/^log_pooler_errors = 1$/)
+        expect(content).to match(/^stats_period = 60$/)
+        expect(content).to match(/^verbose = 0$/)
+        expect(content).to match(/^server_lifetime = 3600$/)
+        expect(content).to match(/^server_connect_timeout = 15$/)
+        expect(content).to match(/^server_login_retry = 15$/)
+        expect(content).to match(/^query_timeout = 0$/)
+        expect(content).to match(/^query_wait_timeout = 120$/)
+        expect(content).to match(/^client_idle_timeout = 0$/)
+        expect(content).to match(/^client_login_timeout = 60$/)
+        expect(content).to match(/^autodb_idle_timeout = 3600$/)
+        expect(content).to match(/^suspend_timeout = 10$/)
+        expect(content).to match(/^idle_transaction_timeout = 0$/)
+        expect(content).to match(/^pkt_buf = 4096$/)
+        expect(content).to match(/^listen_backlog = 128$/)
+        expect(content).to match(/^sbuf_loopcnt = 5$/)
+        expect(content).to match(/^max_packet_size = 2147483647$/)
+        expect(content).to match(/^tcp_defer_accept = 0$/)
+        expect(content).to match(/^tcp_socket_buffer = 0$/)
+        expect(content).to match(/^tcp_keepalive = 1$/)
+        expect(content).to match(/^tcp_keepcnt = 0$/)
+        expect(content).to match(/^tcp_keepidle = 0$/)
+        expect(content).to match(/^tcp_keepintvl = 0$/)
+        expect(content).to match(/^disable_pqexec = 0$/)
+        expect(content).not_to match(/^logfile =/)
+        expect(content).not_to match(/^pidfile =/)
+        expect(content).not_to match(%r{^unix_socket_group =})
+        expect(content).not_to match(%r{^client_tls_ca_file =})
+        expect(content).not_to match(%r{^client_tls_cert_file =})
+        expect(content).not_to match(%r{^server_tls_ca_file =})
+        expect(content).not_to match(%r{^server_tls_key_file =})
+        expect(content).not_to match(%r{^server_tls_cert_file =})
+        expect(content).not_to match(%r{^max_db_connections =})
+        expect(content).not_to match(%r{^max_user_connections =})
+      }
+    end
+
+    context 'pgbouncer.ini template changes' do
+      let(:template) { chef_run.template(pgbouncer_ini) }
+
+      it 'stores the socket directory in a different location when set' do
+        stub_gitlab_rb(
+          pgbouncer: {
+            enable: true,
+            unix_socket_dir: '/fake/dir',
+            unix_socket_group: 'fakegroup',
+            client_tls_ca_file: '/fakecafile',
+            client_tls_cert_file: '/fakecertfile',
+            server_tls_ca_file: '/fakeservercafile',
+            server_tls_key_file: '/fakeserverkeyfile',
+            server_tls_cert_file: '/fakeservercertfile',
+            max_db_connections: 99999,
+            max_user_connections: 88888
+          }
+        )
+        expect(chef_run).to render_file(pgbouncer_ini).with_content { |content|
+          expect(content).to match(%r{^unix_socket_dir = /fake/dir$})
+          expect(content).to match(%r{^unix_socket_group = fakegroup$})
+          expect(content).to match(%r{^client_tls_ca_file = /fakecafile$})
+          expect(content).to match(%r{^client_tls_cert_file = /fakecertfile$})
+          expect(content).to match(%r{^server_tls_ca_file = /fakeservercafile$})
+          expect(content).to match(%r{^server_tls_key_file = /fakeserverkeyfile$})
+          expect(content).to match(%r{^server_tls_cert_file = /fakeservercertfile$})
+          expect(content).to match(%r{^max_db_connections = 99999$})
+          expect(content).to match(%r{^max_user_connections = 88888$})
+        }
+      end
+
+      it 'reloads pgbouncer and starts pgbouncer if it is not running' do
+        allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).and_call_original
+        allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).with('pgbouncer').and_return(true)
+        expect(template).to notify('execute[reload pgbouncer]').to(:run).immediately
+      end
+    end
+
+    context 'databases.json' do
+      it 'creates databases.json' do
+        expect(chef_run).to create_file(databases_json)
+          .with_content("{\"gitlabhq_production\":{\"host\":\"1.2.3.4\"}}")
+          .with(user: 'root', group: 'gitlab-psql')
+      end
+
+      it 'notifies pgb-notify to generate databases.ini' do
+        json_resource = chef_run.file(databases_json)
+        expect(json_resource).to notify('execute[generate databases.ini]').to(:run).immediately
+      end
+
+      it 'does not run pgb-notify when databases.ini exists' do
+        allow(File).to receive(:exist?).and_call_original
+        allow(File).to receive(:exist?).with('/var/opt/gitlab/pgbouncer/databases.ini').and_return(true)
+        expect(chef_run).not_to run_execute('generate databases.ini')
+      end
+
+      it 'stores in a different location when attribute is set' do
+        stub_gitlab_rb(
+          pgbouncer: {
+            enable: true,
+            databases_json: '/fakepath/fakedatabases.json'
+          }
+        )
+        expect(chef_run).to create_file('databases.json')
+          .with(path: '/fakepath/fakedatabases.json')
+      end
+
+      it 'changes the user when the attribute is changed' do
+        stub_gitlab_rb(
+          pgbouncer: {
+            enable: true,
+            databases_ini_user: 'fakeuser'
+          }
+        )
+        expect(chef_run).to create_file('databases.json')
+          .with(user: 'fakeuser', group: 'gitlab-psql')
+      end
+    end
+  end
+
+  context 'authentication' do
+    let(:pg_auth) { '/var/opt/gitlab/pgbouncer/pg_auth' }
+
+    it 'sets up auth_hba when attributes are set' do
+      stub_gitlab_rb(
+        {
+          pgbouncer: {
+            enable: true,
+            auth_hba_file: '/fake/hba_file',
+            auth_query: 'SELECT * FROM FAKETABLE'
+          }
+        }
+      )
+      expect(chef_run).to render_file(pgbouncer_ini).with_content { |content|
+        expect(content).to match(%r{^auth_hba_file = /fake/hba_file$})
+        expect(content).to match(/^auth_query = SELECT \* FROM FAKETABLE$/)
+      }
+    end
+
+    it 'does not create the user file by default' do
+      expect(chef_run).not_to render_file(pg_auth)
+    end
+
+    it 'creates the user file when the attributes are set' do
+      stub_gitlab_rb(
+        {
+          pgbouncer: {
+            enable: true,
+            databases: {
+              gitlabhq_production: {
+                password: 'fakemd5password',
+                user: 'fakeuser',
+                host: '127.0.0.1',
+                port: 5432
+              }
+            }
+          }
+        }
+      )
+      expect(chef_run).to render_file(pg_auth)
+        .with_content(%r{^"fakeuser" "md5fakemd5password"$})
+    end
+
+    it 'creates arbitrary user' do
+      stub_gitlab_rb(
+        {
+          pgbouncer: {
+            enable: true,
+            users: {
+              'fakeuser': {
+                'password': 'fakehash'
+              }
+            }
+          }
+        }
+      )
+      expect(chef_run).to render_file(pg_auth)
+        .with_content(%r{^"fakeuser" "md5fakehash"})
+    end
+
+    it 'supports SCRAM secrets' do
+      stub_gitlab_rb(
+        pgbouncer: {
+          enable: true,
+          auth_type: 'scram-sha-256',
+          users: {
+            'fakeuser': {
+              'password': 'REALLYFAKEHASH'
+            }
+          }
+        }
+      )
+      expect(chef_run).to render_file(pg_auth)
+        .with_content(%r{^"fakeuser" "SCRAM-SHA-256\$REALLYFAKEHASH"})
+    end
+
+    it 'supports a default auth type' do
+      stub_gitlab_rb(
+        pgbouncer: {
+          enable: true,
+          auth_type: 'scram-sha-256',
+          users: {
+            'firstfakeuser': {
+              'password': 'AREALLYFAKEHASH'
+            },
+            'secondfakeuser': {
+              'password': 'ANOTHERREALLYFAKEHASH'
+            }
+          },
+          databases: {
+            fakedb: {
+              user: 'databasefakeuser',
+              password: 'DATABASEHASH'
+            }
+          }
+        }
+      )
+      expect(chef_run).to render_file(pg_auth).with_content { |content|
+        expect(content).to match(%r{^"firstfakeuser" "SCRAM-SHA-256\$AREALLYFAKEHASH"})
+        expect(content).to match(%r{^"secondfakeuser" "SCRAM-SHA-256\$ANOTHERREALLYFAKEHASH"})
+        expect(content).to match(%r{^"databasefakeuser" "SCRAM-SHA-256\$DATABASEHASH"})
+      }
+    end
+
+    it 'supports per user auth types' do
+      stub_gitlab_rb(
+        pgbouncer: {
+          enable: true,
+          users: {
+            'firstfakeuser': {
+              'password': 'AREALLYFAKEHASH'
+            },
+            'secondfakeuser': {
+              'password': 'ANOTHERREALLYFAKEHASH',
+              'auth_type': 'scram-sha-256'
+            }
+          },
+          databases: {
+            fakedb: {
+              user: 'databasefakeuser',
+              auth_type: 'plain',
+              password: 'DATABASEHASH'
+            }
+          }
+        }
+      )
+      expect(chef_run).to render_file(pg_auth).with_content { |content|
+        expect(content).to match(%r{^"firstfakeuser" "md5AREALLYFAKEHASH"})
+        expect(content).to match(%r{^"secondfakeuser" "SCRAM-SHA-256\$ANOTHERREALLYFAKEHASH"})
+        expect(content).to match(%r{^"databasefakeuser" "DATABASEHASH"})
+      }
+    end
+
+    context 'when disabled by default' do
+      it_behaves_like 'disabled runit service', 'pgbouncer'
+
+      it 'includes the pgbouncer_disable recipe' do
+        expect(chef_run).to include_recipe('pgbouncer::disable')
+      end
+    end
+  end
+end
+
+RSpec.describe 'gitlab-ee::default' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    stub_gitlab_rb(
+      {
+        pgbouncer: {
+          db_user_password: 'fakeuserpassword'
+        },
+        postgresql: {
+          pgbouncer_user: 'fakeuser',
+          pgbouncer_user_password: 'fakeuserpassword'
+        }
+      }
+    )
+  end
+
+  it 'should create the pgbouncer user on the database' do
+    expect(chef_run).to include_recipe('pgbouncer::user')
+    expect(chef_run).to create_pgbouncer_user('rails').with(
+      password: 'fakeuserpassword'
+    )
+  end
+end
diff --git a/spec/chef/cookbooks/pgbouncer/recipes/pgbouncer_user_spec.rb b/spec/chef/cookbooks/pgbouncer/recipes/pgbouncer_user_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wZ2JvdW5jZXIvcmVjaXBlcy9wZ2JvdW5jZXJfdXNlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/pgbouncer/recipes/pgbouncer_user_spec.rb
@@ -0,0 +1,118 @@
+#
+# Copyright:: Copyright (c) 2017 GitLab Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef_helper'
+
+RSpec.describe 'gitlab-ee::pgbouncer_user' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'create the rails pgbouncer user' do
+    before do
+      allow(Gitlab).to receive(:[]).and_call_original
+      stub_gitlab_rb(
+        {
+          postgresql: {
+            enable: true,
+            pgbouncer_user: 'pgbouncer-rails',
+            pgbouncer_user_password: 'fakepassword'
+          }
+        }
+      )
+    end
+
+    it 'should call pgbouncer_user with the correct values for rails' do
+      expect(chef_run).to create_pgbouncer_user('rails').with(
+        database: 'gitlabhq_production',
+        password: 'fakepassword',
+        user: 'pgbouncer-rails',
+        add_auth_function: true
+      )
+      expect(chef_run).not_to create_pgbouncer_user('geo')
+    end
+  end
+
+  context 'auth_query is not the default value' do
+    before do
+      stub_gitlab_rb(
+        {
+          pgbouncer: {
+            auth_query: 'SELECT * FROM FAKETABLE'
+          },
+          postgresql: {
+            enable: true,
+            pgbouncer_user: 'pgbouncer-rails',
+            pgbouncer_user_password: 'fakepassword'
+          }
+        }
+      )
+    end
+
+    it 'should not create the pg_shadow_lookup function' do
+      expect(chef_run).to create_pgbouncer_user('rails').with(
+        add_auth_function: false
+      )
+    end
+  end
+
+  context 'no pgbouncer user' do
+    before do
+      stub_gitlab_rb(
+        {
+          postgresql: {
+            enable: true,
+          },
+          geo_postgresql: {
+            enable: true,
+          }
+        }
+      )
+    end
+
+    it 'should not create the pgbouncer user' do
+      expect(chef_run).not_to create_pgbouncer_user('rails')
+      expect(chef_run).not_to create_pgbouncer_user('geo')
+    end
+  end
+
+  context 'create the geo pgbouncer user' do
+    before do
+      stub_gitlab_rb(
+        {
+          geo_postgresql: {
+            enable: true,
+            pgbouncer_user: 'pgbouncer-geo',
+            pgbouncer_user_password: 'fakepassword'
+          }
+        }
+      )
+      allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
+      allow_any_instance_of(PgHelper).to receive(:user_exists?).with('pgbouncer-geo').and_return(false)
+    end
+
+    it 'should call pgbouncer_user with the correct values for geo' do
+      expect(chef_run).to create_pgbouncer_user('geo').with(
+        database: 'gitlabhq_geo_production',
+        password: 'fakepassword',
+        user: 'pgbouncer-geo',
+        add_auth_function: true
+      )
+      expect(chef_run).not_to create_pgbouncer_user('rails')
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/postgresql/recipes/postgresql_spec.rb b/spec/chef/cookbooks/postgresql/recipes/postgresql_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/postgresql/recipes/postgresql_spec.rb
@@ -0,0 +1,880 @@
+require 'chef_helper'
+
+RSpec.describe 'postgresql' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service postgresql_config database_objects)).converge('gitlab::default') }
+  let(:postgresql_data_dir) { '/var/opt/gitlab/postgresql/data' }
+  let(:postgresql_ssl_cert) { File.join(postgresql_data_dir, 'server.crt') }
+  let(:postgresql_ssl_key) { File.join(postgresql_data_dir, 'server.key') }
+  let(:postgresql_conf) { File.join(postgresql_data_dir, 'postgresql.conf') }
+  let(:runtime_conf) { '/var/opt/gitlab/postgresql/data/runtime.conf' }
+  let(:pg_hba_conf) { '/var/opt/gitlab/postgresql/data/pg_hba.conf' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('best_version'))
+    allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('best_version'))
+    allow_any_instance_of(PgHelper).to receive(:running_version).and_return(PGVersion.new('best_version'))
+  end
+
+  it 'includes the postgresql::bin recipe' do
+    expect(chef_run).to include_recipe('postgresql::bin')
+  end
+
+  it 'includes the postgresql::user recipe' do
+    expect(chef_run).to include_recipe('postgresql::user')
+  end
+
+  it 'includes postgresql::sysctl recipe' do
+    expect(chef_run).to include_recipe('postgresql::sysctl')
+  end
+
+  it 'does not warn the user that a restart is needed by default' do
+    allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
+    expect(chef_run).not_to run_ruby_block('warn pending postgresql restart')
+  end
+
+  it 'includes runtime.conf in postgresql.conf' do
+    expect(chef_run).to render_file(postgresql_conf)
+      .with_content(/include 'runtime.conf'/)
+  end
+
+  context 'with default settings' do
+    it_behaves_like 'enabled runit service', 'postgresql', 'root', 'root', 'gitlab-psql', 'gitlab-psql'
+
+    context 'when rendering postgresql.conf' do
+      it 'correctly sets the shared_preload_libraries default setting' do
+        expect(chef_run.node['postgresql']['shared_preload_libraries'])
+          .to be_nil
+
+        expect(chef_run).to render_file(postgresql_conf)
+          .with_content(/shared_preload_libraries = ''/)
+      end
+
+      it 'disables archive mode' do
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/archive_mode = off/)
+      end
+
+      it 'enables SSL by default' do
+        expect(chef_run.node['postgresql']['ssl'])
+          .to eq('on')
+
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/ssl = on/)
+      end
+
+      it 'sets the default SSL cipher list' do
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(%r{ssl_ciphers = 'HIGH:MEDIUM:\+3DES:!aNULL:!SSLv3:!TLSv1'})
+      end
+
+      it 'sets the default locations of SSL certificates' do
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/ssl_cert_file = 'server.crt'/)
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/ssl_key_file = 'server.key'/)
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(%r{ssl_ca_file = '/opt/gitlab/embedded/ssl/certs/cacert.pem'})
+      end
+
+      it 'leaves synchronous_standby_names empty' do
+        expect(chef_run.node['postgresql']['synchronous_standby_names'])
+          .to eq('')
+
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/synchronous_standby_names = ''/)
+      end
+
+      it 'disables wal_log_hints setting' do
+        expect(chef_run.node['postgresql']['wal_log_hints']).to eq('off')
+
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/wal_log_hints = off/)
+      end
+
+      it 'does not set dynamic_shared_memory_type by default' do
+        expect(chef_run).not_to render_file(
+          postgresql_conf
+        ).with_content(/^dynamic_shared_memory_type = /)
+      end
+
+      it 'sets the max_locks_per_transaction setting' do
+        expect(chef_run.node['postgresql']['max_locks_per_transaction'])
+          .to eq(128)
+
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/max_locks_per_transaction = 128/)
+      end
+
+      it 'does not include gitlab-geo.conf' do
+        expect(chef_run).to render_file(postgresql_conf)
+          .with_content { |content|
+            expect(content).not_to match(/include_if_exists 'gitlab-geo.conf'/)
+          }
+      end
+    end
+
+    it 'generates a self-signed SSL certificate and key' do
+      stub_gitlab_rb(postgresql: { ssl_cert_file: 'certfile', ssl_key_file: 'keyfile' })
+
+      absolute_cert_path = File.join(postgresql_data_dir, 'certfile')
+      absolute_key_path = File.join(postgresql_data_dir, 'keyfile')
+
+      expect(chef_run).to create_file(absolute_cert_path).with(
+        user: 'gitlab-psql',
+        group: 'gitlab-psql',
+        mode: 0400
+      )
+
+      expect(chef_run).to create_file(absolute_key_path).with(
+        user: 'gitlab-psql',
+        group: 'gitlab-psql',
+        mode: 0400
+      )
+
+      expect(chef_run).to render_file(absolute_cert_path)
+        .with_content(/-----BEGIN CERTIFICATE-----/)
+      expect(chef_run).to render_file(absolute_key_path)
+        .with_content(/-----BEGIN RSA PRIVATE KEY-----/)
+    end
+
+    context 'when rendering runtime.conf' do
+      it 'correctly sets the log_line_prefix default setting' do
+        expect(chef_run.node['postgresql']['log_line_prefix'])
+          .to be_nil
+
+        expect(chef_run).to render_file(runtime_conf)
+          .with_content(/log_line_prefix = ''/)
+      end
+
+      it 'does not include log_statement by default' do
+        expect(chef_run).not_to render_file(runtime_conf)
+          .with_content(/log_statement = /)
+      end
+
+      it 'sets max_standby settings' do
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/max_standby_archive_delay = 30s/)
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/max_standby_streaming_delay = 30s/)
+      end
+
+      it 'sets archive settings' do
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/archive_command = ''/)
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/archive_timeout = 0/)
+      end
+
+      it 'sets logging directory' do
+        expect(chef_run.node['postgresql']['log_directory'])
+          .to eq('/var/log/gitlab/postgresql')
+
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(%r(^log_directory = '/var/log/gitlab/postgresql'))
+      end
+
+      it 'disables hot_standby_feedback' do
+        expect(chef_run.node['postgresql']['hot_standby_feedback'])
+          .to eq('off')
+
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/hot_standby_feedback = off/)
+      end
+
+      it 'sets the random_page_cost setting' do
+        expect(chef_run.node['postgresql']['random_page_cost'])
+          .to eq(2.0)
+
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/random_page_cost = 2\.0/)
+      end
+
+      it 'sets the log_temp_files setting' do
+        expect(chef_run.node['postgresql']['log_temp_files'])
+          .to eq(-1)
+
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/log_temp_files = -1/)
+      end
+
+      it 'disables the log_checkpoints setting' do
+        expect(chef_run.node['postgresql']['log_checkpoints'])
+          .to eq('off')
+
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/log_checkpoints = off/)
+      end
+
+      it 'sets idle_in_transaction_session_timeout' do
+        expect(chef_run.node['postgresql']['idle_in_transaction_session_timeout'])
+          .to eq('60000')
+
+        expect(chef_run).to render_file(runtime_conf)
+          .with_content(/idle_in_transaction_session_timeout = 60000/)
+      end
+
+      it 'sets effective_io_concurrency' do
+        expect(chef_run.node['postgresql']['effective_io_concurrency'])
+          .to eq(1)
+
+        expect(chef_run).to render_file(runtime_conf)
+          .with_content(/effective_io_concurrency = 1/)
+      end
+
+      it 'sets max_worker_processes' do
+        expect(chef_run.node['postgresql']['max_worker_processes'])
+          .to eq(8)
+
+        expect(chef_run).to render_file(runtime_conf)
+          .with_content(/max_worker_processes = 8/)
+      end
+
+      it 'sets max_parallel_workers_per_gather' do
+        expect(chef_run.node['postgresql']['max_parallel_workers_per_gather'])
+          .to eq(0)
+
+        expect(chef_run).to render_file(runtime_conf)
+          .with_content(/max_parallel_workers_per_gather = 0/)
+      end
+
+      it 'sets log_lock_waits' do
+        expect(chef_run.node['postgresql']['log_lock_waits'])
+          .to eq(1)
+
+        expect(chef_run).to render_file(runtime_conf)
+          .with_content(/log_lock_waits = 1/)
+      end
+
+      it 'sets deadlock_timeout' do
+        expect(chef_run.node['postgresql']['deadlock_timeout'])
+          .to eq('5s')
+
+        expect(chef_run).to render_file(runtime_conf)
+          .with_content(/deadlock_timeout = '5s'/)
+      end
+
+      it 'disables track_io_timing' do
+        expect(chef_run.node['postgresql']['track_io_timing'])
+          .to eq('off')
+
+        expect(chef_run).to render_file(runtime_conf)
+          .with_content(/track_io_timing = 'off'/)
+      end
+
+      it 'sets default_statistics_target' do
+        expect(chef_run.node['postgresql']['default_statistics_target'])
+          .to eq(1000)
+
+        expect(chef_run).to render_file(runtime_conf)
+          .with_content(/default_statistics_target = 1000/)
+      end
+
+      it 'enables the synchronous_commit setting' do
+        expect(chef_run.node['postgresql']['synchronous_commit'])
+          .to eq('on')
+
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/synchronous_commit = on/)
+      end
+    end
+
+    context 'when rendering pg_hba.conf' do
+      it 'creates a standard pg_hba.conf' do
+        expect(chef_run).to render_file(pg_hba_conf)
+          .with_content('local   all         all                               peer map=gitlab')
+      end
+
+      it 'cert authentication is disabled by default' do
+        expect(chef_run).to render_file(pg_hba_conf).with_content { |content|
+          expect(content).to_not match(/cert$/)
+        }
+      end
+    end
+  end
+
+  context 'with user specified settings' do
+    before do
+      stub_gitlab_rb(postgresql: {
+                       shared_preload_libraries: 'pg_stat_statements',
+                       archive_mode: 'on',
+                       username: 'foo',
+                       group: 'bar',
+                       ssl: 'off',
+                       ssl_crl_file: 'revoke.crl',
+                       ssl_ciphers: 'ALL',
+                       log_destination: 'csvlog',
+                       logging_collector: 'on',
+                       log_filename: 'test.log',
+                       log_file_mode: '0600',
+                       log_truncate_on_rotation: 'on',
+                       log_rotation_age: '1d',
+                       log_rotation_size: '10MB',
+                       dynamic_shared_memory_type: 'none',
+                       wal_log_hints: 'on'
+                     },
+                     geo_secondary_role: {
+                       enable: true
+                     })
+    end
+
+    it_behaves_like 'enabled runit service', 'postgresql', 'root', 'root', 'foo', 'bar'
+
+    context 'when rendering postgresql.conf' do
+      it 'correctly sets the shared_preload_libraries setting' do
+        expect(chef_run.node['postgresql']['shared_preload_libraries'])
+          .to eql('pg_stat_statements')
+
+        expect(chef_run).to render_file(postgresql_conf)
+          .with_content(/shared_preload_libraries = 'pg_stat_statements'/)
+      end
+
+      it 'enables archive mode' do
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/archive_mode = on/)
+      end
+
+      it 'disables SSL' do
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/ssl = off/)
+
+        expect(chef_run).not_to render_file(postgresql_ssl_cert)
+        expect(chef_run).not_to render_file(postgresql_ssl_key)
+      end
+
+      it 'sets the certificate revocation list' do
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/ssl_crl_file = 'revoke.crl'/)
+      end
+
+      it 'sets the SSL cipher list' do
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/ssl_ciphers = 'ALL'/)
+      end
+
+      it 'includes gitlab-geo.conf in postgresql.conf' do
+        expect(chef_run).to render_file(postgresql_conf)
+          .with_content(/include_if_exists 'gitlab-geo.conf'/)
+      end
+
+      it 'sets user specified logging parameters' do
+        expect(chef_run).to render_file(postgresql_conf).with_content { |content|
+          expect(content).to match(/logging_collector = on/)
+        }
+
+        expect(chef_run).to render_file(runtime_conf).with_content { |content|
+          expect(content).to match(/log_destination = 'csvlog'/)
+          expect(content).to match(/log_filename = 'test.log'/)
+          expect(content).to match(/log_file_mode = 0600/)
+          expect(content).to match(/log_truncate_on_rotation = on/)
+          expect(content).to match(/log_rotation_age = 1d/)
+          expect(content).to match(/log_rotation_size = 10MB/)
+        }
+      end
+
+      it 'sets the dynamic_shared_memory_type' do
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/^dynamic_shared_memory_type = none/)
+      end
+
+      it 'enables wal_log_hints' do
+        expect(chef_run).to render_file(
+          postgresql_conf
+        ).with_content(/^wal_log_hints = on/)
+      end
+    end
+
+    context 'when rendering runtime.conf' do
+      before do
+        stub_gitlab_rb(postgresql: {
+                         log_line_prefix: '%a',
+                         log_statement: 'all',
+                         max_standby_archive_delay: '60s',
+                         max_standby_streaming_delay: '120s',
+                         archive_command: 'command',
+                         archive_timeout: '120',
+                       })
+      end
+
+      it 'correctly sets the log_line_prefix setting' do
+        expect(chef_run.node['postgresql']['log_line_prefix'])
+          .to eql('%a')
+
+        expect(chef_run).to render_file(runtime_conf)
+          .with_content(/log_line_prefix = '%a'/)
+      end
+
+      it 'correctly sets the log_statement setting' do
+        expect(chef_run.node['postgresql']['log_statement'])
+          .to eql('all')
+
+        expect(chef_run).to render_file(runtime_conf)
+          .with_content(/log_statement = 'all'/)
+      end
+
+      it 'sets max_standby settings' do
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/max_standby_archive_delay = 60s/)
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/max_standby_streaming_delay = 120s/)
+      end
+
+      it 'sets archive settings' do
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/archive_command = 'command'/)
+        expect(chef_run).to render_file(
+          runtime_conf
+        ).with_content(/archive_timeout = 120/)
+      end
+    end
+
+    context 'when rendering pg_hba.conf' do
+      before do
+        stub_gitlab_rb(
+          postgresql: {
+            hostssl: true,
+            trust_auth_cidr_addresses: ['127.0.0.1/32'],
+            custom_pg_hba_entries: {
+              foo: [
+                {
+                  type: 'host',
+                  database: 'foo',
+                  user: 'bar',
+                  cidr: '127.0.0.1/32',
+                  method: 'trust'
+                }
+              ]
+            },
+            cert_auth_addresses: {
+              '1.2.3.4/32' => {
+                database: 'fakedatabase',
+                user: 'fakeuser'
+              },
+              'fakehostname' => {
+                database: 'anotherfakedatabase',
+                user: 'anotherfakeuser'
+              },
+            }
+          }
+        )
+      end
+
+      it 'prefers hostssl when configured in pg_hba.conf' do
+        expect(chef_run).to render_file(pg_hba_conf)
+          .with_content('hostssl    all         all         127.0.0.1/32           trust')
+      end
+
+      it 'adds users custom entries to pg_hba.conf' do
+        expect(chef_run).to render_file(pg_hba_conf)
+          .with_content('host foo bar 127.0.0.1/32 trust')
+      end
+
+      it 'allows cert authentication to be enabled' do
+        expect(chef_run).to render_file(pg_hba_conf).with_content('hostssl fakedatabase fakeuser 1.2.3.4/32 cert')
+        expect(chef_run).to render_file(pg_hba_conf).with_content('hostssl anotherfakedatabase anotherfakeuser fakehostname cert')
+      end
+    end
+  end
+
+  context 'when postgresql.conf changes' do
+    before do
+      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).and_call_original
+      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).with('postgresql').and_return(true)
+      allow_any_instance_of(OmnibusHelper).to receive(:service_dir_enabled?).and_call_original
+      allow_any_instance_of(OmnibusHelper).to receive(:service_dir_enabled?).with('postgresql').and_return(true)
+    end
+
+    it 'notifies reload postgresql task' do
+      expect(chef_run).to create_postgresql_config('gitlab')
+      postgresql_config = chef_run.postgresql_config('gitlab')
+      expect(postgresql_config).to notify('execute[reload postgresql]').to(:run).immediately
+      expect(postgresql_config).to notify('execute[start postgresql]').to(:run).immediately
+    end
+  end
+
+  context 'when enabling extensions' do
+    it 'creates the pg_trgm extension when it is possible' do
+      allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('pg_trgm', 'gitlabhq_production').and_return(true)
+      expect(chef_run).to enable_postgresql_extension('pg_trgm')
+    end
+
+    it 'does not create the pg_trgm extension if it is not possible' do
+      allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('pg_trgm', 'gitlabhq_production').and_return(false)
+      expect(chef_run).not_to run_execute('enable pg_trgm extension')
+    end
+
+    context 'when on a secondary database node' do
+      before do
+        allow_any_instance_of(PgHelper).to receive(:is_standby?).and_return(true)
+        allow_any_instance_of(PgHelper).to receive(:replica?).and_return(true)
+      end
+
+      it 'should not activate pg_trgm' do
+        expect(chef_run).not_to run_execute('enable pg_trgm extension')
+      end
+    end
+
+    it 'creates the btree_gist extension when it is possible' do
+      allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('btree_gist', 'gitlabhq_production').and_return(true)
+      expect(chef_run).to enable_postgresql_extension('btree_gist')
+    end
+
+    it 'does not create the btree_gist extension if it is not possible' do
+      allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('btree_gist', 'gitlabhq_production').and_return(false)
+      expect(chef_run).not_to run_execute('enable btree_gist extension')
+    end
+  end
+
+  context 'when configuring postgresql_user resources' do
+    before do
+      stub_gitlab_rb(
+        {
+          postgresql: {
+            sql_user_password: 'fakepassword',
+            sql_replication_password: 'fakepassword'
+          }
+        }
+      )
+    end
+
+    context 'when on the primary database node' do
+      before do
+        allow_any_instance_of(PgHelper).to receive(:is_standby?).and_return(false)
+      end
+
+      it 'should set a password for sql_user when sql_user_password is set' do
+        expect(chef_run).to create_postgresql_user('gitlab').with(password: 'md5fakepassword')
+      end
+
+      it 'should create the gitlab_replicator user with replication permissions' do
+        expect(chef_run).to create_postgresql_user('gitlab_replicator').with(
+          options: %w(replication),
+          password: 'md5fakepassword'
+        )
+      end
+    end
+
+    context 'when on a secondary database node' do
+      before do
+        allow_any_instance_of(PgHelper).to receive(:is_standby?).and_return(true)
+        allow_any_instance_of(PgHelper).to receive(:replica?).and_return(true)
+      end
+
+      it 'should not create users' do
+        expect(chef_run).not_to create_postgresql_user('gitlab')
+        expect(chef_run).not_to create_postgresql_user('gitlab_replicator')
+      end
+    end
+  end
+end
+
+RSpec.describe 'postgresql 13' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service postgresql_config)).converge('gitlab::default') }
+  let(:postgresql_conf) { File.join(postgresql_data_dir, 'postgresql.conf') }
+  let(:runtime_conf) { '/var/opt/gitlab/postgresql/data/runtime.conf' }
+
+  before do
+    allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('13.0'))
+    allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('13.0'))
+  end
+
+  it 'configures wal_keep_size instead of wal_keep_segments' do
+    expect(chef_run).to render_file(runtime_conf).with_content { |content|
+      expect(content).to include("wal_keep_size")
+      expect(content).not_to include("wal_keep_segments")
+    }
+  end
+end
+
+RSpec.describe 'postgresql 12' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service postgresql_config)).converge('gitlab::default') }
+  let(:postgresql_conf) { File.join(postgresql_data_dir, 'postgresql.conf') }
+  let(:runtime_conf) { '/var/opt/gitlab/postgresql/data/runtime.conf' }
+
+  before do
+    allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('12.0'))
+    allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('12.0'))
+  end
+
+  it 'configures wal_keep_segments instead of wal_keep_size' do
+    expect(chef_run).to render_file(runtime_conf).with_content { |content|
+      expect(content).to include("wal_keep_segments")
+      expect(content).to_not include("wal_keep_size")
+    }
+  end
+end
+
+RSpec.describe 'postgres when version mismatches occur' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service postgresql_config)).converge('gitlab::default') }
+  let(:postgresql_conf) { File.join(postgresql_data_dir, 'postgresql.conf') }
+  let(:runtime_conf) { '/var/opt/gitlab/postgresql/data/runtime.conf' }
+
+  context 'when data and binary versions differ' do
+    before do
+      allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('expectation'))
+      allow_any_instance_of(PgHelper).to receive(:running_version).and_return(PGVersion.new('expectation'))
+      allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('reality'))
+      allow(File).to receive(:exists?).and_call_original
+      allow(File).to receive(:exists?).with("/var/opt/gitlab/postgresql/data/PG_VERSION").and_return(true)
+      allow(Dir).to receive(:glob).and_call_original
+      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/reality*").and_return(
+        ['/opt/gitlab/embedded/postgresql/reality']
+      )
+      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/reality/bin/*").and_return(
+        %w(
+          /opt/gitlab/embedded/postgresql/reality/bin/foo_one
+          /opt/gitlab/embedded/postgresql/reality/bin/foo_two
+          /opt/gitlab/embedded/postgresql/reality/bin/foo_three
+        )
+      )
+    end
+
+    it 'corrects symlinks to the correct location' do
+      allow(FileUtils).to receive(:ln_sf).and_return(true)
+      %w(foo_one foo_two foo_three).each do |pg_bin|
+        expect(FileUtils).to receive(:ln_sf).with(
+          "/opt/gitlab/embedded/postgresql/reality/bin/#{pg_bin}",
+          "/opt/gitlab/embedded/bin/#{pg_bin}"
+        )
+      end
+      chef_run.ruby_block('Link postgresql bin files to the correct version').block.call
+    end
+
+    it 'does not warn the user that a restart is needed by default' do
+      allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
+      expect(chef_run).not_to run_ruby_block('warn pending postgresql restart')
+    end
+  end
+
+  context 'when running version and installed version differ' do
+    before do
+      allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('expectation'))
+      allow_any_instance_of(PgHelper).to receive(:running_version).and_return(PGVersion.new('reality'))
+    end
+
+    it 'warns the user that a restart is needed' do
+      allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
+      expect(chef_run).to run_ruby_block('warn pending postgresql restart')
+    end
+
+    it 'does not warns the user that a restart is needed when postgres is stopped' do
+      expect(chef_run).not_to run_ruby_block('warn pending postgresql restart')
+    end
+  end
+
+  context 'when an older data version is present and no longer used' do
+    before do
+      allow(Gitlab).to receive(:[]).and_call_original
+      allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('new_shiny'))
+      allow_any_instance_of(PGVersion).to receive(:major).and_return('new_shiny')
+      allow_any_instance_of(PgHelper).to receive(:running_version).and_return(PGVersion.new('new_shiny'))
+      allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('ancient_history'))
+      allow(File).to receive(:exists?).and_call_original
+      allow(File).to receive(:exists?).with("/var/opt/gitlab/postgresql/data/PG_VERSION").and_return(true)
+      allow(Dir).to receive(:glob).and_call_original
+      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/ancient_history*").and_return([])
+      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/new_shiny*").and_return(
+        ['/opt/gitlab/embedded/postgresql/new_shiny']
+      )
+      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/new_shiny/bin/*").and_return(
+        %w(
+          /opt/gitlab/embedded/postgresql/new_shiny/bin/foo_one
+          /opt/gitlab/embedded/postgresql/new_shiny/bin/foo_two
+          /opt/gitlab/embedded/postgresql/new_shiny/bin/foo_three
+        )
+      )
+    end
+
+    it 'corrects symlinks to the correct location' do
+      allow(FileUtils).to receive(:ln_sf).and_return(true)
+      %w(foo_one foo_two foo_three).each do |pg_bin|
+        expect(FileUtils).to receive(:ln_sf).with(
+          "/opt/gitlab/embedded/postgresql/new_shiny/bin/#{pg_bin}",
+          "/opt/gitlab/embedded/bin/#{pg_bin}"
+        )
+      end
+      chef_run.ruby_block('Link postgresql bin files to the correct version').block.call
+    end
+  end
+
+  context 'when the expected postgres version is missing' do
+    before do
+      allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('how_it_started'))
+      allow(File).to receive(:exists?).and_call_original
+      allow(File).to receive(:exists?).with("/var/opt/gitlab/postgresql/data/PG_VERSION").and_return(true)
+      allow(Dir).to receive(:glob).and_call_original
+      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/how_it_started*").and_return([])
+      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/how_it_is_going*").and_return([])
+    end
+
+    it 'throws an error' do
+      expect do
+        chef_run.ruby_block('Link postgresql bin files to the correct version').block.call
+      end.to raise_error(RuntimeError, /Could not find PostgreSQL binaries/)
+    end
+  end
+end
+
+RSpec.describe 'postgresql::bin' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+  let(:gitlab_psql_rc) do
+    <<-EOF
+psql_user='gitlab-psql'
+psql_group='gitlab-psql'
+psql_host='/var/opt/gitlab/postgresql'
+psql_port='5432'
+    EOF
+  end
+
+  before do
+    allow(Gitlab). to receive(:[]).and_call_original
+  end
+
+  context 'when bundled postgresql is disabled' do
+    before do
+      stub_gitlab_rb(
+        postgresql: {
+          enable: false
+        }
+      )
+
+      allow(File).to receive(:exist?).and_call_original
+      allow(File).to receive(:exist?).with('/var/opt/gitlab/postgresql/data/PG_VERSION').and_return(false)
+
+      allow_any_instance_of(PgHelper).to receive(:database_version).and_return(nil)
+      version = double("PgHelper", major: 10, minor: 9)
+      allow_any_instance_of(PgHelper).to receive(:version).and_return(version)
+    end
+
+    it 'still includes the postgresql::bin recipe' do
+      expect(chef_run).to include_recipe('postgresql::bin')
+    end
+
+    it 'includes postgresql::directory_locations' do
+      expect(chef_run).to include_recipe('postgresql::directory_locations')
+    end
+
+    it 'creates gitlab-psql-rc' do
+      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-psql-rc')
+        .with_content(gitlab_psql_rc)
+    end
+
+    # We do expect the ruby block to run, but nothing to be found
+    it "doesn't link any files by default" do
+      expect(FileUtils).to_not receive(:ln_sf)
+    end
+
+    context "with postgresql['version'] set" do
+      before do
+        stub_gitlab_rb(
+          postgresql: {
+            enable: false,
+            version: '999'
+          }
+        )
+        allow(Dir).to receive(:glob).and_call_original
+        allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/999*").and_return(
+          %w(
+            /opt/gitlab/embedded/postgresql/999
+          )
+        )
+        allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/999/bin/*").and_return(
+          %w(
+            /opt/gitlab/embedded/postgresql/999/bin/foo_one
+            /opt/gitlab/embedded/postgresql/999/bin/foo_two
+            /opt/gitlab/embedded/postgresql/999/bin/foo_three
+          )
+        )
+      end
+
+      it "doesn't print a warning with a valid postgresql version" do
+        expect(chef_run).to_not run_ruby_block('check_postgresql_version')
+      end
+
+      it 'links the specified version' do
+        allow(FileUtils).to receive(:ln_sf).and_return(true)
+        %w(foo_one foo_two foo_three).each do |pg_bin|
+          expect(FileUtils).to receive(:ln_sf).with(
+            "/opt/gitlab/embedded/postgresql/999/bin/#{pg_bin}",
+            "/opt/gitlab/embedded/bin/#{pg_bin}"
+          )
+        end
+        chef_run.ruby_block('Link postgresql bin files to the correct version').block.call
+      end
+    end
+
+    context "with an invalid version in postgresql['version']" do
+      before do
+        stub_gitlab_rb(
+          postgresql: {
+            enable: false,
+            version: '888'
+          }
+        )
+        allow(Dir).to receive(:glob).and_call_original
+        allow(Dir).to receive(:glob).with('/opt/gitlab/embedded/postgresql/888*').and_return([])
+      end
+
+      it 'should print a warning' do
+        expect(chef_run).to run_ruby_block('check_postgresql_version')
+      end
+    end
+  end
+end
+
+RSpec.describe 'default directories' do
+  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'postgresql directory' do
+    context 'with default settings' do
+      it 'creates postgresql directory' do
+        expect(chef_run).to create_directory('/var/opt/gitlab/postgresql').with(owner: 'gitlab-psql', mode: '0755', recursive: true)
+      end
+    end
+
+    context 'with custom settings' do
+      before do
+        stub_gitlab_rb(
+          postgresql: {
+            dir: '/mypgdir',
+            home: '/mypghomedir'
+          })
+      end
+
+      it 'creates postgresql directory with custom path' do
+        expect(chef_run).to create_directory('/mypgdir').with(owner: 'gitlab-psql', mode: '0755', recursive: true)
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/postgresql/recipes/sysctl_spec.rb b/spec/chef/cookbooks/postgresql/recipes/sysctl_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wb3N0Z3Jlc3FsL3JlY2lwZXMvc3lzY3RsX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/postgresql/recipes/sysctl_spec.rb
@@ -0,0 +1,10 @@
+require 'chef_helper'
+
+RSpec.describe 'postgresql::user' do
+  cached(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::config', 'postgresql::sysctl') }
+
+  it 'creates sysctl files' do
+    expect(chef_run).to create_gitlab_sysctl('kernel.shmmax').with_value(17179869184)
+    expect(chef_run).to create_gitlab_sysctl('kernel.shmall').with_value(4194304)
+  end
+end
diff --git a/spec/chef/cookbooks/postgresql/recipes/user_spec.rb b/spec/chef/cookbooks/postgresql/recipes/user_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wb3N0Z3Jlc3FsL3JlY2lwZXMvdXNlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/postgresql/recipes/user_spec.rb
@@ -0,0 +1,79 @@
+require 'chef_helper'
+
+RSpec.describe 'postgresql::user' do
+  cached(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::config', 'postgresql::user') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  it 'includes postgresql::directory_locations recipe' do
+    expect(chef_run).to include_recipe('postgresql::directory_locations')
+  end
+
+  context 'with default attributes' do
+    it 'creates postgresql user and groups' do
+      account_params = {
+        username: 'gitlab-psql',
+        groupname: 'gitlab-psql',
+        home: '/var/opt/gitlab/postgresql',
+        shell: '/bin/sh'
+      }
+
+      expect(chef_run).to create_account('Postgresql user and group').with(account_params)
+    end
+
+    it 'creates postgresql base directory' do
+      directory_params = {
+        owner: 'gitlab-psql',
+        mode: '0755',
+        recursive: true
+      }
+      expect(chef_run).to create_directory('/var/opt/gitlab/postgresql').with(directory_params)
+    end
+
+    it 'creates a .profile with bundled PostgreSQL PATH' do
+      file_params = {
+        owner: 'gitlab-psql',
+        content: %r{PATH=/opt/gitlab/embedded/bin:/opt/gitlab/bin:\$PATH}
+      }
+
+      expect(chef_run).to create_file('/var/opt/gitlab/postgresql/.profile').with(file_params)
+    end
+  end
+
+  context 'when using custom values for directories' do
+    cached(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::config', 'postgresql::user') }
+
+    before do
+      stub_gitlab_rb(
+        postgresql: {
+          home: '/mypghomedir'
+        })
+    end
+
+    it 'creates postgresql user and groups' do
+      account_params = {
+        username: 'gitlab-psql',
+        groupname: 'gitlab-psql',
+        home: '/mypghomedir',
+        shell: '/bin/sh'
+      }
+
+      expect(chef_run).to create_account('Postgresql user and group').with(account_params)
+    end
+
+    it 'creates postgresql base directory' do
+      directory_params = {
+        owner: 'gitlab-psql',
+        mode: '0755',
+        recursive: true
+      }
+      expect(chef_run).to create_directory('/mypghomedir').with(directory_params)
+    end
+
+    it 'creates necessary directories' do
+      expect(chef_run).to create_directory('/mypghomedir').with(owner: 'gitlab-psql', mode: '0755', recursive: true)
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/postgresql/resources/config_spec.rb b/spec/chef/cookbooks/postgresql/resources/config_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wb3N0Z3Jlc3FsL3Jlc291cmNlcy9jb25maWdfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/postgresql/resources/config_spec.rb
@@ -0,0 +1,45 @@
+require 'chef_helper'
+
+RSpec.describe 'postgresql_config' do
+  let(:runner) do
+    ChefSpec::SoloRunner.new(step_into: %w(postgresql_config)) do |node|
+      node.normal['postgresql']['dir'] = '/fakedir'
+      node.normal['patroni']['dir'] = '/patronifakedir'
+    end
+  end
+
+  let(:chef_run) { runner.converge('gitlab::config', 'test_postgresql::postgresql_config') }
+
+  before do
+    allow_any_instance_of(PgHelper).to receive(:postgresql_user).and_return('fakeuser')
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'create' do
+    context 'default settings' do
+      it 'creates the files' do
+        expect(chef_run).to render_file('/fakedir/data/postgresql.conf')
+        expect(chef_run).to render_file('/fakedir/data/runtime.conf')
+        expect(chef_run).to render_file('/fakedir/data/pg_hba.conf')
+        expect(chef_run).to render_file('/fakedir/data/pg_ident.conf')
+      end
+    end
+
+    context 'when patroni is enabled' do
+      before do
+        stub_gitlab_rb(
+          patroni: {
+            enable: true
+          }
+        )
+      end
+
+      it 'creates the files' do
+        expect(chef_run).to render_file('/patronifakedir/data/postgresql.base.conf')
+        expect(chef_run).to render_file('/patronifakedir/data/runtime.conf')
+        expect(chef_run).to render_file('/patronifakedir/data/pg_hba.conf')
+        expect(chef_run).to render_file('/patronifakedir/data/pg_ident.conf')
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/postgresql/resources/database_spec.rb b/spec/chef/cookbooks/postgresql/resources/database_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wb3N0Z3Jlc3FsL3Jlc291cmNlcy9kYXRhYmFzZV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/postgresql/resources/database_spec.rb
@@ -0,0 +1,64 @@
+require 'chef_helper'
+
+RSpec.describe 'postgresql_database' do
+  let(:runner) do
+    ChefSpec::SoloRunner.new(step_into: %w(postgresql_database)) do |node|
+      # unix_socket_directory is normally conditionally set in postgresql::enable
+      # which is not executed as part of this spec
+      node.normal['postgresql']['unix_socket_directory'] = '/var/opt/gitlab/postgresql'
+    end
+  end
+
+  context 'create' do
+    let(:chef_run) { runner.converge('test_postgresql::postgresql_database_create') }
+
+    context 'server is running' do
+      before do
+        allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
+      end
+
+      context 'database does not already exist' do
+        before do
+          allow_any_instance_of(PgHelper).to receive(:database_exists?).and_return(false)
+        end
+
+        it 'creates a database' do
+          expect(chef_run).to run_execute('create database example').with(
+            command: %(/opt/gitlab/embedded/bin/createdb --port 5432 -h /var/opt/gitlab/postgresql -O gitlab example),
+            user: 'gitlab-psql'
+          )
+        end
+
+        context 'with non-default options' do
+          let(:chef_run) { runner.converge('test_postgresql::postgresql_database_create_with_options') }
+
+          it 'creates with the correct options set' do
+            expect(chef_run).to run_execute('create database fakedb').with(
+              command: %(/opt/gitlab/embedded/bin/createdb --port 9999 -h /fake/dir -O fakeuser fakedb)
+            )
+          end
+        end
+      end
+
+      context 'database already exists' do
+        before do
+          allow_any_instance_of(PgHelper).to receive(:database_exists?).with('example').and_return(true)
+        end
+
+        it 'does nothing' do
+          expect(chef_run).not_to run_execute('create database example')
+        end
+      end
+    end
+
+    context 'server is not running' do
+      before do
+        allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(false)
+      end
+
+      it 'does nothing' do
+        expect(chef_run).not_to run_execute('create database example')
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/postgresql/resources/extension_spec.rb b/spec/chef/cookbooks/postgresql/resources/extension_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wb3N0Z3Jlc3FsL3Jlc291cmNlcy9leHRlbnNpb25fc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/postgresql/resources/extension_spec.rb
@@ -0,0 +1,32 @@
+require 'chef_helper'
+
+RSpec.describe 'postgresql_query' do
+  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(postgresql_extension)) }
+
+  context 'run' do
+    let(:chef_run) { runner.converge('test_postgresql::postgresql_extension_enable') }
+
+    context 'when extension can be enabled' do
+      before do
+        allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('foobar', 'lorem').and_return(true)
+      end
+
+      it 'runs the query to enable extension' do
+        expect(chef_run).to run_postgresql_query('enable foobar extension').with(
+          query: %(CREATE EXTENSION IF NOT EXISTS foobar),
+          db_name: 'lorem'
+        )
+      end
+    end
+
+    context 'when extension can not be enabled' do
+      before do
+        allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('foobar', 'lorem').and_return(false)
+      end
+
+      it 'does not run the query to enable extension' do
+        expect(chef_run).not_to run_postgresql_query('enable foobar extension')
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/postgresql/resources/query_spec.rb b/spec/chef/cookbooks/postgresql/resources/query_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wb3N0Z3Jlc3FsL3Jlc291cmNlcy9xdWVyeV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/cookbooks/postgresql/resources/query_spec.rb
@@ -0,0 +1,43 @@
+require 'chef_helper'
+
+RSpec.describe 'postgresql_query' do
+  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(postgresql_query)) }
+
+  before do
+    allow_any_instance_of(PgHelper).to receive(:is_ready?).and_return(true)
+    allow_any_instance_of(GeoPgHelper).to receive(:is_ready?).and_return(true)
+  end
+
+  context 'run' do
+    let(:chef_run) { runner.converge('test_postgresql::postgresql_query_run') }
+
+    context 'when service is up' do
+      before do
+        allow_any_instance_of(PgHelper).to receive(:is_offline_or_readonly?).and_return(false)
+        allow_any_instance_of(GeoPgHelper).to receive(:is_offline_or_readonly?).and_return(false)
+      end
+
+      it 'runs the query with correct service' do
+        expect(chef_run).to run_execute('create schema (postgresql)').with(
+          command: %(/opt/gitlab/bin/gitlab-psql -d omnibus_test -c "CREATE SCHEMA example AUTHORIZATION foobar;")
+        )
+
+        expect(chef_run).to run_execute('create schema (geo-postgresql)').with(
+          command: %(/opt/gitlab/bin/gitlab-geo-psql -d omnibus_test -c "CREATE SCHEMA example AUTHORIZATION foobar;")
+        )
+      end
+    end
+
+    context 'when service is offline' do
+      before do
+        allow_any_instance_of(PgHelper).to receive(:is_offline_or_readonly?).and_return(true)
+        allow_any_instance_of(GeoPgHelper).to receive(:is_offline_or_readonly?).and_return(true)
+      end
+
+      it 'does not run the query' do
+        expect(chef_run).not_to run_execute('create example schema (postgresql)')
+        expect(chef_run).not_to run_execute('create example schema (geo-postgresql)')
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/postgresql/resources/schema_spec.rb b/spec/chef/cookbooks/postgresql/resources/schema_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wb3N0Z3Jlc3FsL3Jlc291cmNlcy9zY2hlbWFfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/postgresql/resources/schema_spec.rb
@@ -0,0 +1,52 @@
+require 'chef_helper'
+
+RSpec.describe 'postgresql_schema' do
+  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(postgresql_schema)) }
+
+  context 'create' do
+    let(:chef_run) { runner.converge('test_postgresql::postgresql_schema_create') }
+
+    context 'server is running' do
+      before do
+        allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
+        allow_any_instance_of(PgHelper).to receive(:is_standby?).and_return(false)
+      end
+
+      it 'creates schema' do
+        expect(chef_run).to run_postgresql_query('create example schema on omnibus_gitlab_test').with(
+          db_name: 'omnibus_gitlab_test'
+        )
+      end
+
+      context 'schema already exists' do
+        before do
+          allow_any_instance_of(PgHelper).to receive(:schema_exists?).and_return(true)
+        end
+
+        it 'does not try to create schema again' do
+          expect(chef_run).not_to run_postgresql_query('create example schema on omnibus_gitlab_test')
+        end
+
+        context 'when schema owner is different' do
+          before do
+            allow_any_instance_of(PgHelper).to receive(:schema_owner?).and_return(false)
+          end
+
+          it 'tries to update schema' do
+            expect(chef_run).to run_postgresql_query('modify example schema owner on omnibus_gitlab_test')
+          end
+        end
+
+        context 'when schema owner is the same' do
+          before do
+            allow_any_instance_of(PgHelper).to receive(:schema_owner?).and_return(true)
+          end
+
+          it 'tries to update schema' do
+            expect(chef_run).not_to run_postgresql_query('modify example schema owner on omnibus_gitlab_test')
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/postgresql/resources/user_spec.rb b/spec/chef/cookbooks/postgresql/resources/user_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wb3N0Z3Jlc3FsL3Jlc291cmNlcy91c2VyX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/postgresql/resources/user_spec.rb
@@ -0,0 +1,78 @@
+require 'chef_helper'
+
+RSpec.describe 'postgresql_user' do
+  before do
+    allow_any_instance_of(PgHelper).to receive(:is_offline_or_readonly?).and_return(false)
+    allow_any_instance_of(PgHelper).to receive(:user_exists?).and_return(false, true)
+    allow_any_instance_of(PgHelper).to receive(:user_password_match?).and_return(false)
+    allow_any_instance_of(PgHelper).to receive(:user_options_set?).and_return(false)
+    allow_any_instance_of(PgHelper).to receive(:is_ready?).and_return(true)
+    allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
+  end
+
+  let(:runner) { ChefSpec::SoloRunner.new(step_into: ['postgresql_user']) }
+
+  context 'create' do
+    let(:chef_run) { runner.converge('test_postgresql::postgresql_user_create') }
+
+    it 'creates a user' do
+      expect(chef_run).to run_execute('create example postgresql user')
+    end
+  end
+
+  context 'password' do
+    context 'not specified' do
+      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_password_unspecified') }
+
+      it 'does not set the password of the no_password user' do
+        expect(chef_run).not_to run_execute('set password for no_password postgresql user')
+      end
+    end
+
+    context 'nil' do
+      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_password_nil') }
+
+      it 'does set the password of the nil_password user' do
+        expect(chef_run).to run_execute('set password for nil_password postgresql user')
+          .with(command: /PASSWORD NULL/)
+      end
+    end
+
+    context 'md5' do
+      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_password_md5') }
+
+      it 'does set the password of the md5_password user' do
+        expect(chef_run).to run_execute('set password for md5_password postgresql user')
+          .with(command: /PASSWORD 'e99b79fbdf9b997e6918df2385e60f5c'/)
+      end
+    end
+
+    context 'empty' do
+      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_password_empty') }
+
+      it 'does set the password of the empty_password user' do
+        expect(chef_run).to run_execute('set password for empty_password postgresql user')
+          .with(command: /PASSWORD ''/)
+      end
+    end
+  end
+
+  context 'options' do
+    context 'unspecified' do
+      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_options_unspecified') }
+
+      it 'does not set options' do
+        expect(chef_run).not_to run_execute('set options for example postgresql user')
+      end
+    end
+
+    context 'SUPERUSER' do
+      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_options_superuser') }
+
+      it 'does set SUPERUSER' do
+        expect(chef_run).to run_execute('set options for example postgresql user')
+          .with(command: /\bSUPERUSER\b/)
+      end
+    end
+  end
+end
diff --git a/spec/chef/cookbooks/praefect/recipes/praefect_spec.rb b/spec/chef/cookbooks/praefect/recipes/praefect_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9wcmFlZmVjdC9yZWNpcGVzL3ByYWVmZWN0X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/praefect/recipes/praefect_spec.rb
@@ -0,0 +1,303 @@
+require 'chef_helper'
+RSpec.describe 'praefect' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service env_dir)).converge('gitlab::default') }
+  let(:prometheus_grpc_latency_buckets) do
+    '[0.001, 0.005, 0.025, 0.1, 0.5, 1.0, 10.0, 30.0, 60.0, 300.0, 1500.0]'
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when the defaults are used' do
+    it_behaves_like 'disabled runit service', 'praefect'
+  end
+
+  context 'when praefect is enabled' do
+    let(:config_path) { '/var/opt/gitlab/praefect/config.toml' }
+    let(:env_dir) { '/opt/gitlab/etc/praefect/env' }
+    let(:auth_transitioning) { false }
+
+    before do
+      stub_gitlab_rb(praefect: {
+                       enable: true,
+                       auth_transitioning: auth_transitioning,
+                     })
+    end
+
+    it 'creates expected directories with correct permissions' do
+      expect(chef_run).to create_directory('/var/opt/gitlab/praefect').with(user: 'git', mode: '0700')
+    end
+
+    it 'creates a default VERSION file and sends hup to service' do
+      expect(chef_run).to create_version_file('Create Praefect version file').with(
+        version_file_path: '/var/opt/gitlab/praefect/VERSION',
+        version_check_cmd: '/opt/gitlab/embedded/bin/praefect --version'
+      )
+
+      expect(chef_run.version_file('Create Praefect version file')).to notify('runit_service[praefect]').to(:hup)
+    end
+
+    it 'renders the config.toml' do
+      rendered = {
+        'auth' => { 'token' => '', 'transitioning' => false },
+        'listen_addr' => 'localhost:2305',
+        'logging' => { 'format' => 'json' },
+        'prometheus_listen_addr' => 'localhost:9652',
+        'prometheus_exclude_database_from_default_metrics' => true,
+        'sentry' => {},
+        'database' => {
+          'session_pooled' => {},
+        },
+        'reconciliation' => {},
+        'failover' => { 'enabled' => true }
+      }
+
+      expect(chef_run).to render_file(config_path).with_content { |content|
+        expect(Tomlrb.parse(content)).to eq(rendered)
+      }
+      expect(chef_run).not_to render_file(config_path)
+      .with_content(%r{\[prometheus\]\s+grpc_latency_buckets =})
+    end
+
+    it 'renders the env dir files' do
+      expect(chef_run).to render_file(File.join(env_dir, "GITALY_PID_FILE"))
+        .with_content('/var/opt/gitlab/praefect/praefect.pid')
+      expect(chef_run).to render_file(File.join(env_dir, "WRAPPER_JSON_LOGGING"))
+        .with_content('true')
+      expect(chef_run).to render_file(File.join(env_dir, "SSL_CERT_DIR"))
+        .with_content('/opt/gitlab/embedded/ssl/certs/')
+    end
+
+    it 'renders the service run file with wrapper' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/praefect/run')
+        .with_content('/opt/gitlab/embedded/bin/gitaly-wrapper /opt/gitlab/embedded/bin/praefect')
+        .with_content('exec chpst -e /opt/gitlab/etc/praefect/env')
+    end
+
+    context 'with custom settings' do
+      let(:dir) { nil }
+      let(:socket_path) { '/var/opt/gitlab/praefect/praefect.socket' }
+      let(:auth_token) { 'secrettoken123' }
+      let(:auth_transitioning) { false }
+      let(:sentry_dsn) { 'https://my_key:my_secret@sentry.io/test_project' }
+      let(:sentry_environment) { 'production' }
+      let(:listen_addr) { 'localhost:4444' }
+      let(:tls_listen_addr) { 'localhost:5555' }
+      let(:certificate_path) { '/path/to/cert.pem' }
+      let(:key_path) { '/path/to/key.pem' }
+      let(:prom_addr) { 'localhost:1234' }
+      let(:separate_database_metrics) { false }
+      let(:log_level) { 'debug' }
+      let(:log_format) { 'text' }
+      let(:primaries) { %w[praefect1 praefect2] }
+      let(:virtual_storages) do
+        {
+          'default' => {
+            'default_replication_factor' => 2,
+            'nodes' => {
+              'praefect1' => { address: 'tcp://node1.internal', token: "praefect1-token" },
+              'praefect2' => { address: 'tcp://node2.internal', token: "praefect2-token" },
+              'praefect3' => { address: 'tcp://node3.internal', token: "praefect3-token" },
+              'praefect4' => { address: 'tcp://node4.internal', token: "praefect4-token" }
+            },
+            'praefect5' => { address: 'tcp://node5.internal', token: "praefect5-token" }
+          }
+        }
+      end
+      let(:failover_enabled) { true }
+      let(:database_host) { 'pg.external' }
+      let(:database_port) { 2234 }
+      let(:database_user) { 'praefect-pg' }
+      let(:database_password) { 'praefect-pg-pass' }
+      let(:database_dbname) { 'praefect_production' }
+      let(:database_sslmode) { 'require' }
+      let(:database_sslcert) { '/path/to/client-cert' }
+      let(:database_sslkey) { '/path/to/client-key' }
+      let(:database_sslrootcert) { '/path/to/rootcert' }
+      let(:database_sslrootcert) { '/path/to/rootcert' }
+      let(:database_direct_host) { 'pg.internal' }
+      let(:database_direct_port) { 1234 }
+      let(:reconciliation_scheduling_interval) { '1m' }
+      let(:reconciliation_histogram_buckets) { '[1.0, 2.0]' }
+
+      before do
+        stub_gitlab_rb(praefect: {
+                         enable: true,
+                         dir: dir,
+                         socket_path: socket_path,
+                         auth_token: auth_token,
+                         auth_transitioning: auth_transitioning,
+                         sentry_dsn: sentry_dsn,
+                         sentry_environment: sentry_environment,
+                         listen_addr: listen_addr,
+                         tls_listen_addr: tls_listen_addr,
+                         certificate_path: certificate_path,
+                         key_path: key_path,
+                         prometheus_listen_addr: prom_addr,
+                         prometheus_grpc_latency_buckets: prometheus_grpc_latency_buckets,
+                         separate_database_metrics: separate_database_metrics,
+                         logging_level: log_level,
+                         logging_format: log_format,
+                         failover_enabled: failover_enabled,
+                         virtual_storages: virtual_storages,
+                         database_host: database_host,
+                         database_port: database_port,
+                         database_user: database_user,
+                         database_password: database_password,
+                         database_dbname: database_dbname,
+                         database_sslmode: database_sslmode,
+                         database_sslcert: database_sslcert,
+                         database_sslkey: database_sslkey,
+                         database_sslrootcert: database_sslrootcert,
+                         database_direct_host: database_direct_host,
+                         database_direct_port: database_direct_port,
+                         reconciliation_scheduling_interval: reconciliation_scheduling_interval,
+                         reconciliation_histogram_buckets: reconciliation_histogram_buckets,
+                       })
+      end
+
+      it 'renders the config.toml' do
+        expect(chef_run).to render_file(config_path).with_content { |content|
+          expect(Tomlrb.parse(content)).to eq(
+            {
+              'auth' => {
+                'token' => 'secrettoken123',
+                'transitioning' => false
+              },
+              'database' => {
+                'dbname' => 'praefect_production',
+                'host' => 'pg.external',
+                'password' => 'praefect-pg-pass',
+                'port' => 2234,
+                'sslcert' => '/path/to/client-cert',
+                'sslkey' => '/path/to/client-key',
+                'sslmode' => 'require',
+                'sslrootcert' => '/path/to/rootcert',
+                'user' => 'praefect-pg',
+                'session_pooled' => {
+                  'host' => 'pg.internal',
+                  'port' => 1234,
+                }
+              },
+              'failover' => {
+                'enabled' => true
+              },
+              'logging' => {
+                'format' => 'text',
+                'level' => 'debug'
+              },
+              'listen_addr' => 'localhost:4444',
+              'prometheus' => {
+                'grpc_latency_buckets' => [0.001, 0.005, 0.025, 0.1, 0.5, 1.0, 10.0, 30.0, 60.0, 300.0, 1500.0]
+              },
+              'reconciliation' => {
+                'histogram_buckets' => [1.0, 2.0],
+                'scheduling_interval' => '1m'
+              },
+              'sentry' => {
+                'sentry_dsn' => 'https://my_key:my_secret@sentry.io/test_project',
+                'sentry_environment' => 'production'
+              },
+              'prometheus_listen_addr' => 'localhost:1234',
+              'prometheus_exclude_database_from_default_metrics' => false,
+              'socket_path' => '/var/opt/gitlab/praefect/praefect.socket',
+              'tls' => {
+                'certificate_path' => '/path/to/cert.pem',
+                'key_path' => '/path/to/key.pem'
+              },
+              'tls_listen_addr' => 'localhost:5555',
+              'virtual_storage' => [
+                {
+                  'name' => 'default',
+                  'default_replication_factor' => 2,
+                  'node' => [
+                    {
+                      'address' => 'tcp://node1.internal',
+                      'storage' => 'praefect1',
+                      'token' => 'praefect1-token'
+                    },
+                    {
+                      'address' => 'tcp://node2.internal',
+                      'storage' => 'praefect2',
+                      'token' => 'praefect2-token'
+                    },
+                    {
+                      'address' => 'tcp://node3.internal',
+                      'storage' => 'praefect3',
+                      'token' => 'praefect3-token'
+                    },
+                    {
+                      'address' => 'tcp://node4.internal',
+                      'storage' => 'praefect4',
+                      'token' => 'praefect4-token'
+                    },
+                    {
+                      'address' => 'tcp://node5.internal',
+                      'storage' => 'praefect5',
+                      'token' => 'praefect5-token'
+                    }
+                  ]
+                }
+              ]
+            }
+          )
+        }
+      end
+
+      it 'renders the env dir files correctly' do
+        expect(chef_run).to render_file(File.join(env_dir, "WRAPPER_JSON_LOGGING"))
+          .with_content('false')
+      end
+
+      context 'with virtual_storages as an array' do
+        let(:virtual_storages) { [{ name: 'default', 'nodes' => [{ storage: 'praefect1', address: 'tcp://node1.internal', token: "praefect1-token" }] }] }
+
+        it 'raises an error' do
+          expect { chef_run }.to raise_error("Praefect virtual_storages must be a hash")
+        end
+      end
+
+      context 'with duplicate virtual storage node configured via fallback' do
+        let(:virtual_storages) { { 'default' => { 'node-1' => {}, 'nodes' => { 'node-1' => {} } } } }
+
+        it 'raises an error' do
+          allow(LoggingHelper).to receive(:deprecation)
+          expect(LoggingHelper).to receive(:deprecation).with(
+            <<~EOS
+              Configuring the Gitaly nodes directly in the virtual storage's root configuration object has
+              been deprecated in GitLab 13.12 and will no longer be supported in GitLab 15.0. Move the Gitaly
+              nodes under the 'nodes' key as described in step 6 of https://docs.gitlab.com/ee/administration/gitaly/praefect.html#praefect.
+            EOS
+          )
+
+          expect { chef_run }.to raise_error("Virtual storage 'default' contains duplicate configuration for node 'node-1'")
+        end
+      end
+
+      context 'with nodes within virtual_storages as an array' do
+        let(:virtual_storages) { { 'default' => [{ storage: 'praefect1', address: 'tcp://node1.internal', token: "praefect1-token" }] } }
+
+        it 'raises an error' do
+          expect { chef_run }.to raise_error("nodes of a Praefect virtual_storage must be a hash")
+        end
+      end
+    end
+
+    describe 'database migrations' do
+      it 'runs the migrations' do
+        expect(chef_run).to run_bash('migrate praefect database')
+      end
+
+      context 'with auto_migrate off' do
+        before { stub_gitlab_rb(praefect: { auto_migrate: false }) }
+
+        it 'skips running the migrations' do
+          expect(chef_run).not_to run_bash('migrate praefect database')
+        end
+      end
+    end
+
+    include_examples "consul service discovery", "praefect", "praefect"
+  end
+end
diff --git a/spec/chef/cookbooks/redis/recipes/redis_spec.rb b/spec/chef/cookbooks/redis/recipes/redis_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9yZWRpcy9yZWNpcGVzL3JlZGlzX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/redis/recipes/redis_spec.rb
@@ -0,0 +1,379 @@
+require 'chef_helper'
+
+RSpec.describe 'redis' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(redis_service runit_service)).converge('gitlab::default') }
+  let(:redis_conf) { '/var/opt/gitlab/redis/redis.conf' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'by default' do
+    let(:gitlab_redis_cli_rc) do
+      <<-EOF
+redis_dir='/var/opt/gitlab/redis'
+redis_host='127.0.0.1'
+redis_port='0'
+redis_socket='/var/opt/gitlab/redis/redis.socket'
+      EOF
+    end
+
+    it 'enables the redis service' do
+      expect(chef_run).to create_redis_service('redis')
+    end
+
+    it 'creates redis config with default values' do
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content { |content|
+          expect(content).to match(/client-output-buffer-limit normal 0 0 0/)
+          expect(content).to match(/client-output-buffer-limit replica 256mb 64mb 60/)
+          expect(content).to match(/client-output-buffer-limit pubsub 32mb 8mb 60/)
+          expect(content).to match(/^hz 10/)
+          expect(content).to match(/^save 900 1/)
+          expect(content).to match(/^save 300 10/)
+          expect(content).to match(/^save 60 10000/)
+          expect(content).to match(/^maxmemory 0/)
+          expect(content).to match(/^maxmemory-policy noeviction/)
+          expect(content).to match(/^maxmemory-samples 5/)
+          expect(content).to match(/^tcp-backlog 511/)
+          expect(content).to match(/^rename-command KEYS ""$/)
+          expect(content).to match(/^lazyfree-lazy-eviction no$/)
+          expect(content).to match(/^lazyfree-lazy-expire no$/)
+          expect(content).to match(/^io-threads 1$/)
+          expect(content).to match(/^io-threads-do-reads no$/)
+          expect(content).to match(/^stop-writes-on-bgsave-error yes$/)
+          expect(content).not_to match(/^replicaof/)
+          expect(content).not_to match(/^tls-/)
+        }
+    end
+
+    it 'creates redis user and group' do
+      expect(chef_run).to create_account('user and group for redis').with(username: 'gitlab-redis', groupname: 'gitlab-redis')
+    end
+
+    it_behaves_like 'enabled runit service', 'redis', 'root', 'root'
+
+    it 'creates gitlab-redis-cli-rc' do
+      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
+        .with_content(gitlab_redis_cli_rc)
+    end
+
+    describe 'pending restart check' do
+      context 'when running version is same as installed version' do
+        before do
+          allow_any_instance_of(RedisHelper).to receive(:running_version).and_return('3.2.12')
+          allow_any_instance_of(RedisHelper).to receive(:installed_version).and_return('3.2.12')
+        end
+
+        it 'does not raise a warning' do
+          expect(chef_run).not_to run_ruby_block('warn pending redis restart')
+        end
+      end
+
+      context 'when running version is different than installed version' do
+        before do
+          allow_any_instance_of(RedisHelper).to receive(:running_version).and_return('3.2.12')
+          allow_any_instance_of(RedisHelper).to receive(:installed_version).and_return('5.0.9')
+        end
+
+        it 'raises a warning' do
+          expect(chef_run).to run_ruby_block('warn pending redis restart')
+        end
+      end
+    end
+  end
+
+  context 'with user specified values' do
+    before do
+      stub_gitlab_rb(
+        redis: {
+          client_output_buffer_limit_normal: "5 5 5",
+          client_output_buffer_limit_replica: "512mb 128mb 120",
+          client_output_buffer_limit_pubsub: "64mb 16mb 120",
+          save: ["10 15000"],
+          maxmemory: "32gb",
+          maxmemory_policy: "allkeys-url",
+          maxmemory_samples: 10,
+          tcp_backlog: 1024,
+          hz: 100,
+          username: 'foo',
+          group: 'bar',
+          rename_commands: {
+            "FAKE_COMMAND" => "RENAMED_FAKE_COMMAND",
+            "DISABLED_FAKE_COMMAND" => ""
+          }
+        }
+      )
+    end
+
+    it 'creates redis config with custom values' do
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/client-output-buffer-limit normal 5 5 5/)
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/client-output-buffer-limit replica 512mb 128mb 120/)
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/client-output-buffer-limit pubsub 64mb 16mb 120/)
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/^save 10 15000/)
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/^maxmemory 32gb/)
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/^maxmemory-policy allkeys-url/)
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/^maxmemory-samples 10/)
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/^tcp-backlog 1024/)
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/^hz 100/)
+    end
+
+    it 'does not include the default renamed keys in redis.conf' do
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content { |content|
+          expect(content).not_to match(/^rename-command KEYS ""$/)
+          expect(content).to match(/^rename-command FAKE_COMMAND "RENAMED_FAKE_COMMAND"$/)
+          expect(content).to match(/^rename-command DISABLED_FAKE_COMMAND ""$/)
+        }
+    end
+
+    it 'creates redis user and group' do
+      expect(chef_run).to create_account('user and group for redis').with(username: 'foo', groupname: 'bar')
+    end
+
+    it_behaves_like 'enabled runit service', 'redis', 'root', 'root'
+  end
+
+  context 'with snapshotting disabled' do
+    before do
+      stub_gitlab_rb(
+        redis: {
+          save: []
+        }
+      )
+    end
+    it 'creates redis config without save setting' do
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+      expect(chef_run).not_to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/^save/)
+    end
+  end
+
+  context 'with snapshotting cleared' do
+    before do
+      stub_gitlab_rb(
+        redis: {
+          save: [""]
+        }
+      )
+    end
+    it 'creates redis config without save setting' do
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/^save ""/)
+    end
+  end
+
+  context 'with a replica configured' do
+    let(:redis_host) { '1.2.3.4' }
+    let(:redis_port) { 6370 }
+    let(:master_ip) { '10.0.0.0' }
+    let(:master_port) { 6371 }
+
+    let(:gitlab_redis_cli_rc) do
+      <<-EOF
+redis_dir='/var/opt/gitlab/redis'
+redis_host='1.2.3.4'
+redis_port='6370'
+redis_socket=''
+      EOF
+    end
+
+    before do
+      stub_gitlab_rb(
+        redis: {
+          bind: redis_host,
+          port: redis_port,
+          master_ip: master_ip,
+          master_port: master_port,
+          master_password: 'password',
+          master: false
+        }
+      )
+    end
+
+    it 'includes replicaof' do
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/^replicaof #{master_ip} #{master_port}/)
+    end
+
+    it 'creates gitlab-redis-cli-rc' do
+      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
+        .with_content(gitlab_redis_cli_rc)
+    end
+  end
+
+  context 'in HA mode with Sentinels' do
+    let(:redis_host) { '1.2.3.4' }
+    let(:redis_port) { 6370 }
+    let(:master_ip) { '10.0.0.0' }
+    let(:master_port) { 6371 }
+
+    let(:gitlab_redis_cli_rc) do
+      <<-EOF
+redis_dir='/var/opt/gitlab/redis'
+redis_host='1.2.3.4'
+redis_port='6370'
+redis_socket=''
+      EOF
+    end
+
+    before do
+      stub_gitlab_rb(
+        redis: {
+          bind: redis_host,
+          port: redis_port,
+          ha: true,
+          master_ip: master_ip,
+          master_port: master_port,
+          master_password: 'password',
+          master: false
+        }
+      )
+    end
+
+    it 'omits replicaof' do
+      expect(chef_run).not_to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content(/^replicaof/)
+    end
+
+    it 'creates gitlab-redis-cli-rc' do
+      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
+        .with_content(gitlab_redis_cli_rc)
+    end
+  end
+
+  context 'with rename_commands disabled' do
+    before do
+      stub_gitlab_rb(
+        redis: {
+          rename_commands: {}
+        }
+      )
+    end
+
+    it 'should not rename any commands' do
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content { |content|
+          expect(content).not_to match(/^rename-command/)
+        }
+    end
+  end
+
+  context 'with lazy eviction enabled' do
+    before do
+      stub_gitlab_rb(
+        redis: {
+          lazyfree_lazy_eviction: true
+        }
+      )
+    end
+
+    it 'creates redis config with lazyfree-lazy-eviction yes' do
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content { |content|
+          expect(content).to match(/^lazyfree-lazy-eviction yes$/)
+          expect(content).to match(/^lazyfree-lazy-expire no$/)
+        }
+    end
+  end
+
+  context 'with lazy eviction enabled' do
+    before do
+      stub_gitlab_rb(
+        redis: {
+          io_threads: 4,
+          io_threads_do_reads: true
+        }
+      )
+    end
+
+    it 'creates redis config with lazyfree-lazy-eviction yes' do
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content { |content|
+          expect(content).to match(/^io-threads 4$/)
+          expect(content).to match(/^io-threads-do-reads yes$/)
+        }
+    end
+  end
+
+  context 'with stop writes on bgsave error disabled' do
+    before do
+      stub_gitlab_rb(
+        redis: {
+          stop_writes_on_bgsave_error: false
+        }
+      )
+    end
+
+    it 'creates redis config with stop-writes-on-bgsave-error no' do
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content { |content|
+          expect(content).to match(/^stop-writes-on-bgsave-error no$/)
+        }
+    end
+  end
+
+  context 'with tls settings specified' do
+    before do
+      stub_gitlab_rb(
+        redis: {
+          tls_port: 6380,
+          tls_cert_file: '/etc/gitlab/ssl/redis.crt',
+          tls_key_file: '/etc/gitlab/ssl/redis.key',
+          tls_dh_params_file: '/etc/gitlab/ssl/redis-dhparams',
+          tls_ca_cert_file: '/etc/gitlab/ssl/redis-ca.crt',
+          tls_ca_cert_dir: '/opt/gitlab/embedded/ssl/certs',
+          tls_auth_clients: 'no',
+          tls_replication: 'yes',
+          tls_cluster: 'yes',
+          tls_protocols: 'TLSv1.2 TLSv1.3',
+          tls_ciphers: 'DEFAULT:!MEDIUM',
+          tls_ciphersuites: 'TLS_CHACHA20_POLY1305_SHA256',
+          tls_prefer_server_ciphers: 'yes',
+          tls_session_caching: 'no',
+          tls_session_cache_size: 10000,
+          tls_session_cache_timeout: 120
+        }
+      )
+    end
+
+    it 'renders redis config with tls settings' do
+      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
+        .with_content { |content|
+          expect(content).to match(%r{^tls-port 6380$})
+          expect(content).to match(%r{^tls-cert-file /etc/gitlab/ssl/redis.crt$})
+          expect(content).to match(%r{^tls-key-file /etc/gitlab/ssl/redis.key$})
+          expect(content).to match(%r{^tls-dh-params-file /etc/gitlab/ssl/redis-dhparams$})
+          expect(content).to match(%r{^tls-ca-cert-file /etc/gitlab/ssl/redis-ca.crt$})
+          expect(content).to match(%r{^tls-ca-cert-dir /opt/gitlab/embedded/ssl/certs$})
+          expect(content).to match(%r{^tls-auth-clients no$})
+          expect(content).to match(%r{^tls-replication yes$})
+          expect(content).to match(%r{^tls-cluster yes$})
+          expect(content).to match(%r{^tls-protocols "TLSv1.2 TLSv1.3"$})
+          expect(content).to match(%r{^tls-ciphers DEFAULT:!MEDIUM$})
+          expect(content).to match(%r{^tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256$})
+          expect(content).to match(%r{^tls-prefer-server-ciphers yes$})
+          expect(content).to match(%r{^tls-session-caching no$})
+          expect(content).to match(%r{^tls-session-cache-size 10000$})
+          expect(content).to match(%r{^tls-session-cache-timeout 120$})
+        }
+    end
+  end
+
+  context 'with redis disabled' do
+    before do
+      stub_gitlab_rb(redis: { enable: false })
+    end
+
+    it_behaves_like 'disabled runit service', 'redis', 'root', 'root'
+  end
+end
diff --git a/spec/chef/cookbooks/registry/recipes/registry_spec.rb b/spec/chef/cookbooks/registry/recipes/registry_spec.rb
new file mode 100755
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9yZWdpc3RyeS9yZWNpcGVzL3JlZ2lzdHJ5X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/cookbooks/registry/recipes/registry_spec.rb
@@ -0,0 +1,510 @@
+require 'chef_helper'
+
+RSpec.describe 'registry recipe' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  describe 'letsencrypt' do
+    before do
+      stub_gitlab_rb(
+        external_url: 'https://gitlab.example.com',
+        registry_external_url: 'https://registry.example.com'
+      )
+
+      allow(File).to receive(:exist?).and_call_original
+    end
+
+    describe 'HTTP to HTTPS redirection' do
+      context 'by default' do
+        it 'is enabled' do
+          expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/gitlab-registry.conf').with_content("return 301 https://registry.example.com$request_uri;")
+        end
+      end
+
+      context 'if disabled in gitlab.rb' do
+        before do
+          stub_gitlab_rb(
+            external_url: 'https://gitlab.example.com',
+            registry_external_url: 'https://registry.example.com',
+            registry_nginx: {
+              redirect_http_to_https: false
+            }
+          )
+        end
+
+        it 'is disabled' do
+          expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/gitlab-registry.conf')
+          expect(chef_run).not_to render_file('/var/opt/gitlab/nginx/conf/gitlab-registry.conf').with_content("return 301 https://registry.example.com$request_uri;")
+        end
+      end
+
+      context 'registry on gitlab domain with a different port ' do
+        before do
+          stub_gitlab_rb(
+            external_url: 'https://gitlab.example.com',
+            registry_external_url: 'https://gitlab.example.com:5005'
+          )
+        end
+
+        it 'is enabled and has correct redirect URL in nginx config' do
+          expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/gitlab-registry.conf').with_content("return 301 https://gitlab.example.com:5005$request_uri;")
+        end
+      end
+    end
+
+    context 'default certificate file is missing' do
+      before do
+        allow(File).to receive(:exist?).with('/etc/gitlab/ssl/registry.example.com.crt').and_return(false)
+      end
+
+      it 'adds itself to letsencrypt alt_names' do
+        expect(chef_run.node['letsencrypt']['alt_names']).to eql(['registry.example.com'])
+      end
+
+      it 'is reflected in the acme_selfsigned' do
+        expect(chef_run).to create_acme_selfsigned('gitlab.example.com').with(
+          alt_names: ['registry.example.com']
+        )
+      end
+    end
+
+    context 'default certificate file is present' do
+      before do
+        allow(File).to receive(:exist?).with('/etc/gitlab/ssl/registry.example.com.crt').and_return(true)
+      end
+
+      it 'does not alter letsencrypt alt_names' do
+        expect(chef_run.node['letsencrypt']['alt_names']).to eql([])
+      end
+
+      it 'is reflected in the acme_selfsigned' do
+        expect(chef_run).to create_acme_selfsigned('gitlab.example.com').with(
+          alt_names: []
+        )
+      end
+    end
+  end
+
+  context 'when registry is enabled' do
+    before { stub_gitlab_rb(registry_external_url: 'https://registry.example.com') }
+
+    it_behaves_like 'enabled registry service'
+
+    it_behaves_like 'renders a valid YAML file', '/var/opt/gitlab/registry/config.yml'
+
+    it 'creates a default VERSION file and restarts service' do
+      expect(chef_run).to create_version_file('Create version file for Registry').with(
+        version_file_path: '/var/opt/gitlab/registry/VERSION',
+        version_check_cmd: '/opt/gitlab/embedded/bin/registry --version'
+      )
+
+      expect(chef_run.version_file('Create version file for Registry')).to notify('runit_service[registry]').to(:restart)
+    end
+
+    context 'when registry storagedriver health check is disabled' do
+      before { stub_gitlab_rb(registry: { health_storagedriver_enabled: false }) }
+
+      it 'creates registry config with specified value' do
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/health:\s*storagedriver:\s*enabled:\s*false/)
+      end
+    end
+
+    context 'when registry validation is enabled' do
+      before { stub_gitlab_rb(registry: { validation_enabled: true }) }
+
+      it 'creates registry config with specified value' do
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/^validation:\s*disabled: false$/)
+      end
+    end
+
+    context 'when registry middleware is enabled' do
+      let(:middleware_config) do
+        { "storage" => [
+          { "name" => "googlecdn",
+            "options" => {
+              "baseurl" => "https://example.org",
+              "privatekey" => "/etc/gitlab/googlecdn.key",
+              "keyname" => "example-key"
+            } }
+        ] }
+      end
+
+      before { stub_gitlab_rb(registry: { middleware: middleware_config }) }
+
+      it 'creates registry config with middleware' do
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(%r(^middleware: {"storage":))
+      end
+    end
+
+    context 'when a log formatter is specified' do
+      before { stub_gitlab_rb(registry: { log_formatter: 'json' }) }
+
+      it 'creates the registry config with the specified value' do
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/log:\s*level: info\s*formatter:\s*json/)
+      end
+
+      it 'does not append timestamp in logs if logging format is json' do
+        expect(chef_run).to render_file('/opt/gitlab/sv/registry/log/run')
+          .with_content(/exec svlogd \/var\/log\/gitlab\/registry/)
+      end
+    end
+
+    context 'when schema1 compatibility is enabled' do
+      before { stub_gitlab_rb(registry: { compatibility_schema1_enabled: true }) }
+
+      it 'creates registry config with specified value' do
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/compatibility:\s*schema1:\s*enabled:\s*true/)
+      end
+    end
+  end
+
+  context 'when registry port is specified' do
+    before { stub_gitlab_rb(registry_external_url: 'https://registry.example.com', registry: { registry_http_addr: 'localhost:5001' }) }
+
+    it 'creates registry and rails configs with specified value' do
+      expect(chef_run).to create_templatesymlink('Create a gitlab.yml and create a symlink to Rails root').with_variables(hash_including('registry_api_url' => 'http://localhost:5001'))
+
+      expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+        .with_content(/addr: localhost:5001/)
+    end
+  end
+
+  context 'when a debug addr is specified' do
+    before { stub_gitlab_rb(registry_external_url: 'https://registry.example.com', registry: { debug_addr: 'localhost:5005' }) }
+
+    it 'creates the registry config with the specified debug value' do
+      expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+        .with_content(/debug:\n\s*addr: localhost:5005/)
+    end
+  end
+
+  context 'when user and group are specified' do
+    before { stub_gitlab_rb(registry_external_url: 'https://registry.example.com', registry: { username: 'registryuser', group: 'registrygroup' }) }
+    it 'make registry run file start registry under correct user' do
+      expect(chef_run).to render_file('/opt/gitlab/sv/registry/run')
+        .with_content(/-U registryuser:registrygroup/)
+      expect(chef_run).to render_file('/opt/gitlab/sv/registry/run')
+        .with_content(/-u registryuser:registrygroup/)
+    end
+  end
+end
+
+RSpec.describe 'registry' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  let(:default_vars) do
+    {
+      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
+    }
+  end
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when registry is enabled' do
+    before { stub_gitlab_rb(registry_external_url: 'https://registry.example.com') }
+
+    it_behaves_like 'enabled registry service'
+
+    context 'when custom storage parameters are specified' do
+      before do
+        stub_gitlab_rb(
+          registry: {
+            storage: {
+              s3: { accesskey: 'awsaccesskey', secretkey: 'awssecretkey', bucketname: 'bucketname' }
+            }
+          }
+        )
+      end
+
+      it 'uses custom storage instead of the default rootdirectory' do
+        expect(chef_run.node['registry']['storage'])
+          .to include(s3: { accesskey: 'awsaccesskey', secretkey: 'awssecretkey', bucketname: 'bucketname' })
+        expect(chef_run.node['registry']['storage'])
+          .not_to include('rootdirectory' => '/var/opt/gitlab/gitlab-rails/shared/registry')
+      end
+
+      it 'uses the default cache and delete settings if not overridden' do
+        expect(chef_run.node['registry']['storage']['cache'])
+          .to eql('blobdescriptor' => 'inmemory')
+        expect(chef_run.node['registry']['storage']['delete'])
+          .to eql('enabled' => true)
+      end
+
+      it 'allows the cache and delete settings to be overridden' do
+        stub_gitlab_rb(registry: { storage: { cache: 'somewhere-else', delete: { enabled: false } } })
+        expect(chef_run.node['registry']['storage']['cache'])
+          .to eql('somewhere-else')
+        expect(chef_run.node['registry']['storage']['delete'])
+          .to eql('enabled' => false)
+      end
+    end
+
+    context 'when storage_delete_enabled is false' do
+      before { stub_gitlab_rb(registry: { storage_delete_enabled: false }) }
+
+      it 'sets the delete enabled field on the storage object' do
+        expect(chef_run.node['registry']['storage']['delete'])
+          .to eql('enabled' => false)
+      end
+    end
+
+    context 'when notification is configured for Geo replication' do
+      before do
+        stub_gitlab_rb(
+          registry: {
+            notifications: [
+              {
+                'name' => 'geo_event',
+                'url' => 'https://registry.example.com/notify',
+                'timeout' => '500ms',
+                'threshold' => 5,
+                'backoff' => '1s',
+                'headers' => {
+                  "Authorization" => ["mysecret"]
+                }
+              }
+            ]
+          }
+        )
+      end
+
+      it 'assigns registry_notification_secret variable automatically' do
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"Authorization":\["mysecret"\]/)
+        expect(chef_run.node['gitlab']['gitlab-rails']['registry_notification_secret'])
+          .to eql('mysecret')
+      end
+    end
+
+    context 'when registry notification endpoint is configured with the minimum required' do
+      before do
+        stub_gitlab_rb(
+          registry: {
+            notifications: [
+              name: 'test_endpoint',
+              url: 'https://registry.example.com/notify'
+            ]
+          }
+        )
+      end
+
+      it 'creates the registry config with the specified endpoint config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"name":"test_endpoint"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"url":"https:\/\/registry.example.com\/notify"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"timeout":"500ms"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"threshold":5/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"backoff":"1s"/)
+      end
+    end
+
+    context 'when the default values are overridden' do
+      before do
+        stub_gitlab_rb(
+          registry: {
+            notifications: [
+              name: 'test_endpoint',
+              url: 'https://registry.example.com/notify'
+            ],
+            default_notifications_timeout: '5000ms',
+            default_notifications_threshold: 10,
+            default_notifications_backoff: '50s',
+            default_notifications_headers: {
+              "Authorization" => %w(AUTHORIZATION_EXAMPLE_TOKEN1 AUTHORIZATION_EXAMPLE_TOKEN2)
+            }
+          }
+        )
+      end
+
+      it 'creates the registry config overriding the values not set with the new defaults' do
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"name":"test_endpoint"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"url":"https:\/\/registry.example.com\/notify"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"timeout":"5000ms"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"threshold":10/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"backoff":"50s"/)
+      end
+    end
+
+    context 'when registry notification endpoint is configured with all the available variables' do
+      before do
+        stub_gitlab_rb(
+          registry: {
+            notifications: [
+              {
+                'name' => 'test_endpoint',
+                'url' => 'https://registry.example.com/notify',
+                'timeout' => '500ms',
+                'threshold' => 5,
+                'backoff' => '1s',
+                'headers' => {
+                  "Authorization" => ["AUTHORIZATION_EXAMPLE_TOKEN"]
+                }
+              }
+            ]
+          }
+        )
+      end
+
+      it 'creates the registry config with the specified endpoint config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"name":"test_endpoint"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"url":"https:\/\/registry.example.com\/notify"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"timeout":"500ms"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"threshold":5/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"backoff":"1s"/)
+      end
+    end
+
+    context 'when 3 registry notification endpoints are configured' do
+      before do
+        stub_gitlab_rb(
+          registry: {
+            notifications: [
+              {
+                'name' => 'test_endpoint',
+                'url' => 'https://registry.example.com/notify'
+              },
+              {
+                'name' => 'test_endpoint2',
+                'url' => 'https://registry.example.com/notify2',
+                'timeout' => '100ms',
+                'threshold' => 2,
+                'backoff' => '4s',
+                'headers' => {
+                  "Authorization" => ["AUTHORIZATION_EXAMPLE_TOKEN"]
+                }
+              },
+              {
+                'name' => 'test_endpoint3',
+                'url' => 'https://registry.example.com/notify3'
+              }
+            ]
+          }
+        )
+      end
+
+      it 'creates the registry config with the specified endpoint config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"name":"test_endpoint"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/\"url\":\"https:\/\/registry.example.com\/notify\"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"timeout":"500ms"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"threshold":5/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"backoff":"1s"/)
+        # Second endpoint
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"name":"test_endpoint2"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"url":"https:\/\/registry.example.com\/notify2"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"timeout":"100ms"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"threshold":2/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"backoff":"4s"/)
+        # Third endpoint
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"name":"test_endpoint3"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"url":"https:\/\/registry.example.com\/notify3"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"timeout":"500ms"/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"threshold":5/)
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content(/"backoff":"1s"/)
+      end
+    end
+
+    context 'when registry notification endpoint is not configured' do
+      it 'creates the registry config without the endpoint config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
+        expect(chef_run).not_to render_file('/var/opt/gitlab/registry/config.yml')
+          .with_content('notifications:')
+      end
+    end
+
+    context 'when registry has custom environment variables configured' do
+      before do
+        stub_gitlab_rb(registry: { env: { 'HTTP_PROXY' => 'my-proxy' } })
+      end
+
+      it 'creates necessary env variable files' do
+        expect(chef_run).to create_env_dir('/opt/gitlab/etc/registry/env').with_variables(
+          default_vars.merge(
+            {
+              'HTTP_PROXY' => 'my-proxy'
+            }
+          )
+        )
+      end
+    end
+  end
+end
+
+RSpec.describe 'auto enabling registry' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
+  let(:registry_config) { '/var/opt/gitlab/registry/config.yml' }
+  let(:nginx_config) { '/var/opt/gitlab/nginx/conf/gitlab-registry.conf' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    stub_gitlab_rb(
+      external_url: 'https://gitlab.example.com'
+    )
+  end
+
+  it_behaves_like 'enabled registry service'
+
+  it 'should listen on port 5050 with nginx' do
+    expect(chef_run).to render_file(nginx_config)
+      .with_content { |content|
+        expect(content).to include("listen *:5050 ssl;")
+        expect(content).to include("server_name gitlab.example.com;")
+      }
+  end
+
+  it "should use the default Let's Encrypt certificates" do
+    expect(chef_run).to render_file(nginx_config)
+      .with_content { |content|
+        expect(content).to include("ssl_certificate /etc/gitlab/ssl/gitlab.example.com.crt;")
+        expect(content).to include("ssl_certificate_key /etc/gitlab/ssl/gitlab.example.com.key;")
+      }
+  end
+
+  it 'should point gitlab-rails to the registry' do
+    expect(chef_run).to create_templatesymlink(
+      'Create a gitlab.yml and create a symlink to Rails root'
+    ).with_variables(
+      hash_including(
+        'registry_host' => 'gitlab.example.com',
+        'registry_port' => '5050'
+      )
+    )
+  end
+end
diff --git a/spec/chef/cookbooks/spamcheck/recipes/spamcheck_spec.rb b/spec/chef/cookbooks/spamcheck/recipes/spamcheck_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2Nvb2tib29rcy9zcGFtY2hlY2svcmVjaXBlcy9zcGFtY2hlY2tfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/cookbooks/spamcheck/recipes/spamcheck_spec.rb
@@ -0,0 +1,274 @@
+require 'chef_helper'
+require 'toml-rb'
+
+RSpec.describe 'spamcheck' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+  end
+
+  context 'when spamcheck is disabled (default)' do
+    it 'includes spamcheck::disable recipe' do
+      expect(chef_run).to include_recipe('spamcheck::disable')
+    end
+  end
+
+  context 'when spamcheck is enabled' do
+    before do
+      stub_gitlab_rb(
+        spamcheck: {
+          enable: true
+        }
+      )
+    end
+
+    it 'includes spamcheck::enable recipe' do
+      expect(chef_run).to include_recipe('spamcheck::enable')
+    end
+  end
+
+  describe 'spamcheck::enable' do
+    context 'with default values' do
+      before do
+        stub_gitlab_rb(
+          spamcheck: {
+            enable: true
+          }
+        )
+      end
+
+      it 'creates necessary directories at default locations' do
+        %w[
+          /var/opt/gitlab/spamcheck
+          /var/opt/gitlab/spamcheck/sockets
+          /var/log/gitlab/spamcheck
+          /var/log/gitlab/spam-classifier
+        ].each do |dir|
+          expect(chef_run).to create_directory(dir).with(
+            owner: 'git',
+            mode: '0700',
+            recursive: true
+          )
+        end
+      end
+
+      it 'creates config.toml with default values' do
+        actual_content = get_rendered_toml(chef_run, '/var/opt/gitlab/spamcheck/config.toml')
+        expected_content = {
+          grpc: {
+            port: '8001'
+          },
+          rest: {
+            externalPort: ''
+          },
+          logger: {
+            level: 'info',
+            format: 'json',
+            output: 'stdout'
+          },
+          monitor: {
+            address: ":8003"
+          },
+          extraAttributes: {
+            monitorMode: 'false'
+          },
+          filter: {
+            allowList: {},
+            denyList: {}
+          },
+          preprocessor: {
+            socketPath: '/var/opt/gitlab/spamcheck/sockets/preprocessor.sock'
+          },
+          modelAttributes: {
+            modelPath: '/opt/gitlab/embedded/service/spam-classifier/model/issues/tflite/model.tflite'
+          }
+        }
+        expect(actual_content).to eq(expected_content)
+      end
+
+      it 'creates env directory with default variables' do
+        expect(chef_run).to create_env_dir('/opt/gitlab/etc/spamcheck/env').with_variables(
+          'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
+        )
+      end
+
+      it_behaves_like "enabled runit service", "spamcheck", "root", "root"
+      it_behaves_like "enabled runit service", "spam-classifier", "root", "root"
+
+      it 'creates runit files for spamcheck service' do
+        expected_content = <<~EOS
+          #!/bin/bash
+
+          # Let runit capture all script error messages
+          exec 2>&1
+
+          exec chpst -e /opt/gitlab/etc/spamcheck/env -P \\
+            -u git:git \\
+            -U git:git \\
+            /opt/gitlab/embedded/bin/spamcheck -config /var/opt/gitlab/spamcheck/config.toml
+        EOS
+
+        expect(chef_run).to render_file('/opt/gitlab/sv/spamcheck/run').with_content(expected_content)
+      end
+
+      it 'creates runit files for spam-classifier service' do
+        expected_content = <<~EOS
+          #!/bin/bash
+
+          # Let runit capture all script error messages
+          exec 2>&1
+
+          exec chpst -e /opt/gitlab/etc/spamcheck/env -P \\
+            -u git:git \\
+            -U git:git \\
+            /opt/gitlab/embedded/bin/python3 /opt/gitlab/embedded/service/spam-classifier/preprocessor/preprocess.py \\
+              --tokenizer-pickle-path /opt/gitlab/embedded/service/spam-classifier/preprocessor/tokenizer.pickle \\
+              --log-dir /var/log/gitlab/spam-classifier \\
+              --socket-dir /var/opt/gitlab/spamcheck/sockets
+        EOS
+
+        expect(chef_run).to render_file('/opt/gitlab/sv/spam-classifier/run').with_content(expected_content)
+      end
+    end
+
+    context 'with user specified values' do
+      before do
+        stub_gitlab_rb(
+          user: {
+            username: 'randomuser',
+            group: 'randomgroup'
+          },
+          spamcheck: {
+            enable: true,
+            dir: '/data/spamcheck',
+            port: 5001,
+            external_port: 5002,
+            monitoring_address: ':5003',
+            log_level: 'debug',
+            log_format: 'text',
+            log_output: 'file',
+            monitor_mode: true,
+            allowlist: {
+              '14' => 'spamtest/hello'
+            },
+            denylist: {
+              '15' => 'foobar/random'
+            },
+            env_directory: '/env/spamcheck',
+            log_directory: '/log/spamcheck',
+            env: {
+              'FOO' => 'BAR'
+            },
+            classifier: {
+              log_directory: '/log/spam-classifier'
+            }
+          }
+        )
+      end
+
+      it 'creates necessary directories at user specified locations' do
+        %w[
+          /data/spamcheck
+          /data/spamcheck/sockets
+          /log/spamcheck
+          /log/spam-classifier
+        ].each do |dir|
+          expect(chef_run).to create_directory(dir).with(
+            owner: 'randomuser',
+            mode: '0700',
+            recursive: true
+          )
+        end
+      end
+
+      it 'creates config.toml with user specified values' do
+        actual_content = get_rendered_toml(chef_run, '/data/spamcheck/config.toml')
+        expected_content = {
+          grpc: {
+            port: '5001'
+          },
+          rest: {
+            externalPort: '5002'
+          },
+          logger: {
+            level: 'debug',
+            format: 'text',
+            output: 'file'
+          },
+          monitor: {
+            address: ":5003"
+          },
+          extraAttributes: {
+            monitorMode: 'true'
+          },
+          filter: {
+            allowList: {
+              '14': 'spamtest/hello'
+            },
+            denyList: {
+              '15': 'foobar/random'
+            }
+          },
+          preprocessor: {
+            socketPath: '/data/spamcheck/sockets/preprocessor.sock'
+          },
+          modelAttributes: {
+            modelPath: '/opt/gitlab/embedded/service/spam-classifier/model/issues/tflite/model.tflite'
+          }
+        }
+        expect(actual_content).to eq(expected_content)
+      end
+
+      it 'creates env directory with user specified and default variables' do
+        expect(chef_run).to create_env_dir('/env/spamcheck').with_variables(
+          'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
+          'FOO' => 'BAR'
+        )
+      end
+
+      it_behaves_like "enabled runit service", "spamcheck", "root", "root"
+      it_behaves_like "enabled runit service", "spam-classifier", "root", "root"
+
+      it 'creates runit files for spamcheck service' do
+        expected_content = <<~EOS
+          #!/bin/bash
+
+          # Let runit capture all script error messages
+          exec 2>&1
+
+          exec chpst -e /env/spamcheck -P \\
+            -u randomuser:randomgroup \\
+            -U randomuser:randomgroup \\
+            /opt/gitlab/embedded/bin/spamcheck -config /data/spamcheck/config.toml
+        EOS
+
+        expect(chef_run).to render_file('/opt/gitlab/sv/spamcheck/run').with_content(expected_content)
+      end
+
+      it 'creates runit files for spam-classifier service' do
+        expected_content = <<~EOS
+          #!/bin/bash
+
+          # Let runit capture all script error messages
+          exec 2>&1
+
+          exec chpst -e /env/spamcheck -P \\
+            -u randomuser:randomgroup \\
+            -U randomuser:randomgroup \\
+            /opt/gitlab/embedded/bin/python3 /opt/gitlab/embedded/service/spam-classifier/preprocessor/preprocess.py \\
+              --tokenizer-pickle-path /opt/gitlab/embedded/service/spam-classifier/preprocessor/tokenizer.pickle \\
+              --log-dir /log/spam-classifier \\
+              --socket-dir /data/spamcheck/sockets
+        EOS
+
+        expect(chef_run).to render_file('/opt/gitlab/sv/spam-classifier/run').with_content(expected_content)
+      end
+    end
+  end
+
+  describe 'spamcheck::disable' do
+    it_behaves_like "disabled runit service", "spamcheck", "root", "root"
+    it_behaves_like "disabled runit service", "spam-classifier", "root", "root"
+  end
+end
diff --git a/spec/chef/crond/resources/crond_job_spec.rb b/spec/chef/crond/resources/crond_job_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2Nyb25kL3Jlc291cmNlcy9jcm9uZF9qb2Jfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/crond/resources/crond_job_spec.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'crond_job' do
-  context 'crond enabled' do
-    let(:runner) do
-      ChefSpec::SoloRunner.new(step_into: %w(crond_job)) do |node|
-        node.normal['crond']['cron_d'] = "prefix"
-      end
-    end
-
-    context 'delete' do
-      let(:chef_run) { runner.converge("crond::enable", "test_crond::crond_job_delete") }
-
-      it 'should delete the file' do
-        expect(chef_run).to delete_file("prefix/delete")
-      end
-
-      it 'should notify the service' do
-        expect(chef_run.file("prefix/delete"))
-          .to notify("runit_service[crond]").to(:restart)
-      end
-    end
-
-    context 'minimal' do
-      let(:chef_run) { runner.converge("crond::enable", "test_crond::crond_job_minimal") }
-
-      it 'should set up the file' do
-        expect(chef_run).to create_file("prefix/minimal").with(
-          owner: "root",
-          content: "* * * * * rspec echo 'Hello world'\n"
-        )
-      end
-
-      it 'should notify the service' do
-        expect(chef_run.file("prefix/minimal"))
-          .to notify("runit_service[crond]").to(:restart)
-      end
-    end
-  end
-
-  context 'crond disabled' do
-    let(:runner) do
-      ChefSpec::SoloRunner.new(step_into: %w(crond_job)) do |node|
-        node.normal['crond']['cron_d'] = "prefix"
-      end
-    end
-
-    context 'delete' do
-      let(:chef_run) { runner.converge("test_crond::crond_job_delete") }
-
-      it 'should delete the file' do
-        expect(chef_run).to delete_file("prefix/delete")
-      end
-
-      it 'should not try and notify the service' do
-        expect(chef_run.file("prefix/delete"))
-          .not_to notify("runit_service[crond]")
-      end
-    end
-
-    context 'minimal' do
-      let(:chef_run) { runner.converge("test_crond::crond_job_minimal") }
-
-      it 'should not set up the file' do
-        expect(chef_run).not_to create_file("prefix/minimal")
-      end
-    end
-  end
-end
diff --git a/spec/chef/fixtures/cookbooks/acme/attributes/default.rb b/spec/chef/fixtures/cookbooks/acme/attributes/default.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy9hY21lL2F0dHJpYnV0ZXMvZGVmYXVsdC5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/acme/attributes/default.rb
@@ -0,0 +1,1 @@
+default['acme']['key_size'] = 2048
diff --git a/spec/chef/fixtures/cookbooks/acme/metadata.rb b/spec/chef/fixtures/cookbooks/acme/metadata.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy9hY21lL21ldGFkYXRhLnJi
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/acme/metadata.rb
@@ -0,0 +1,1 @@
+name 'acme'
diff --git a/spec/chef/fixtures/cookbooks/acme/resources/certificate.rb b/spec/chef/fixtures/cookbooks/acme/resources/certificate.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy9hY21lL3Jlc291cmNlcy9jZXJ0aWZpY2F0ZS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/acme/resources/certificate.rb
@@ -0,0 +1,15 @@
+property :cn, String, name_property: true
+property :crt, String
+property :dir, [String, nil]
+property :key, String
+property :owner, [String, nil]
+property :chain, [String, nil]
+property :wwwroot, String
+property :alt_names, Array
+property :key_size, [Integer, nil]
+property :crt, [String, nil]
+property :group, [String, nil]
+property :contact, [String, Array, nil]
+
+actions :create
+default_action :create
diff --git a/spec/chef/fixtures/cookbooks/acme/resources/selfsigned.rb b/spec/chef/fixtures/cookbooks/acme/resources/selfsigned.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy9hY21lL3Jlc291cmNlcy9zZWxmc2lnbmVkLnJi
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/acme/resources/selfsigned.rb
@@ -0,0 +1,8 @@
+property :crt, String
+property :key, String
+property :key_size, Integer
+property :chain, String
+property :alt_names, Array
+
+actions :create
+default_action :create
diff --git a/spec/chef/fixtures/cookbooks/test_consul/metadata.rb b/spec/chef/fixtures/cookbooks/test_consul/metadata.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2NvbnN1bC9tZXRhZGF0YS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_consul/metadata.rb
@@ -0,0 +1,2 @@
+name 'test_consul'
+depends 'consul'
diff --git a/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_address_port.rb b/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_address_port.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2NvbnN1bC9yZWNpcGVzL2NvbnN1bF9zZXJ2aWNlX2FkZHJlc3NfcG9ydC5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_address_port.rb
@@ -0,0 +1,6 @@
+include_recipe 'test_consul::reload_consul'
+
+consul_service 'node-exporter' do
+  ip_address '0.0.0.0'
+  port 1234
+end
diff --git a/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_delete.rb b/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_delete.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2NvbnN1bC9yZWNpcGVzL2NvbnN1bF9zZXJ2aWNlX2RlbGV0ZS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_delete.rb
@@ -0,0 +1,5 @@
+include_recipe 'test_consul::reload_consul'
+
+consul_service 'delete_me' do
+  action :delete
+end
diff --git a/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_delete_no_reload.rb b/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_delete_no_reload.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2NvbnN1bC9yZWNpcGVzL2NvbnN1bF9zZXJ2aWNlX2RlbGV0ZV9ub19yZWxvYWQucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_delete_no_reload.rb
@@ -0,0 +1,6 @@
+include_recipe 'test_consul::reload_consul'
+
+consul_service 'delete_no_reload' do
+  action :delete
+  reload_service false
+end
diff --git a/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_meta.rb b/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_meta.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2NvbnN1bC9yZWNpcGVzL2NvbnN1bF9zZXJ2aWNlX21ldGEucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_meta.rb
@@ -0,0 +1,9 @@
+include_recipe 'test_consul::reload_consul'
+
+meta = { some_key: "value" }
+
+consul_service 'node-exporter' do
+  ip_address '0.0.0.0'
+  port 1234
+  meta meta
+end
diff --git a/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_socket.rb b/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_socket.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2NvbnN1bC9yZWNpcGVzL2NvbnN1bF9zZXJ2aWNlX3NvY2tldC5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_consul/recipes/consul_service_socket.rb
@@ -0,0 +1,6 @@
+include_recipe 'test_consul::reload_consul'
+
+consul_service 'node-exporter' do
+  socket_address '0.0.0.0:5678'
+  reload_service false
+end
diff --git a/spec/chef/fixtures/cookbooks/test_consul/recipes/reload_consul.rb b/spec/chef/fixtures/cookbooks/test_consul/recipes/reload_consul.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2NvbnN1bC9yZWNpcGVzL3JlbG9hZF9jb25zdWwucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_consul/recipes/reload_consul.rb
@@ -0,0 +1,4 @@
+# A stub to satisfy the `consul_service`
+execute 'reload consul' do
+  action :nothing
+end
diff --git a/spec/chef/fixtures/cookbooks/test_crond/metadata.rb b/spec/chef/fixtures/cookbooks/test_crond/metadata.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2Nyb25kL21ldGFkYXRhLnJi
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_crond/metadata.rb
@@ -0,0 +1,2 @@
+name 'test_crond'
+depends 'crond'
diff --git a/spec/chef/fixtures/cookbooks/test_crond/recipes/crond_job_delete.rb b/spec/chef/fixtures/cookbooks/test_crond/recipes/crond_job_delete.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2Nyb25kL3JlY2lwZXMvY3JvbmRfam9iX2RlbGV0ZS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_crond/recipes/crond_job_delete.rb
@@ -0,0 +1,3 @@
+crond_job 'delete' do
+  action :delete
+end
diff --git a/spec/chef/fixtures/cookbooks/test_crond/recipes/crond_job_minimal.rb b/spec/chef/fixtures/cookbooks/test_crond/recipes/crond_job_minimal.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2Nyb25kL3JlY2lwZXMvY3JvbmRfam9iX21pbmltYWwucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_crond/recipes/crond_job_minimal.rb
@@ -0,0 +1,4 @@
+crond_job 'minimal' do
+  user "rspec"
+  command "echo 'Hello world'"
+end
diff --git a/spec/chef/fixtures/cookbooks/test_gitlab/metadata.rb b/spec/chef/fixtures/cookbooks/test_gitlab/metadata.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2dpdGxhYi9tZXRhZGF0YS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_gitlab/metadata.rb
@@ -0,0 +1,2 @@
+name 'test_gitlab'
+depends 'gitlab'
diff --git a/spec/chef/fixtures/cookbooks/test_gitlab/recipes/puma_config_create.rb b/spec/chef/fixtures/cookbooks/test_gitlab/recipes/puma_config_create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2dpdGxhYi9yZWNpcGVzL3B1bWFfY29uZmlnX2NyZWF0ZS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_gitlab/recipes/puma_config_create.rb
@@ -0,0 +1,12 @@
+puma_config '/var/opt/gitlab/gitlab-rails/etc/puma.rb' do
+  environment 'production'
+  pid '/opt/gitlab/var/puma/puma.pid'
+  state_path '/opt/gitlab/var/puma/puma.state'
+  listen_socket '/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket'
+  listen_tcp '127.0.0.1:8080'
+  working_directory '/var/opt/gitlab/gitlab-rails/working'
+  worker_processes 2
+  cookbook 'gitlab'
+
+  action :create
+end
diff --git a/spec/chef/fixtures/cookbooks/test_gitlab/recipes/puma_config_custom.rb b/spec/chef/fixtures/cookbooks/test_gitlab/recipes/puma_config_custom.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2dpdGxhYi9yZWNpcGVzL3B1bWFfY29uZmlnX2N1c3RvbS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_gitlab/recipes/puma_config_custom.rb
@@ -0,0 +1,14 @@
+puma_config '/var/opt/gitlab/gitlab-rails/etc/puma.rb' do
+  environment 'production'
+  pid '/opt/gitlab/var/puma/puma.pid'
+  state_path '/opt/gitlab/var/puma/puma.state'
+  listen_socket '/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket'
+  listen_tcp '127.0.0.1:8080'
+  working_directory '/var/opt/gitlab/gitlab-rails/working'
+  worker_processes 2
+  per_worker_max_memory_mb 1000
+  install_dir '/opt/custom/gitlab'
+  cookbook 'gitlab'
+
+  action :create
+end
diff --git a/spec/chef/fixtures/cookbooks/test_gitlab/recipes/rails_migration_run.rb b/spec/chef/fixtures/cookbooks/test_gitlab/recipes/rails_migration_run.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2dpdGxhYi9yZWNpcGVzL3JhaWxzX21pZ3JhdGlvbl9ydW4ucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_gitlab/recipes/rails_migration_run.rb
@@ -0,0 +1,12 @@
+ruby_block 'test-dependent' do
+  true
+end
+
+rails_migration 'gitlab-test' do
+  rake_task 'gitlab:db:configure'
+  logfile_prefix 'gitlab-test-db-migrate'
+  helper RailsMigrationHelper.new(node)
+  environment Hash['SOME_ENV' => 'some-value']
+
+  dependent_services ["ruby_block[test-dependent]"]
+end
diff --git a/spec/chef/fixtures/cookbooks/test_gitlab_ee/metadata.rb b/spec/chef/fixtures/cookbooks/test_gitlab_ee/metadata.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2dpdGxhYl9lZS9tZXRhZGF0YS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_gitlab_ee/metadata.rb
@@ -0,0 +1,2 @@
+name 'test_gitlab_ee'
+depends 'gitlab-ee'
diff --git a/spec/chef/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_geo.rb b/spec/chef/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_geo.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2dpdGxhYl9lZS9yZWNpcGVzL3BnYm91bmNlcl91c2VyX2NyZWF0ZV9nZW8ucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_geo.rb
@@ -0,0 +1,8 @@
+pgbouncer_user 'geo' do
+  helper GeoPgHelper.new(node)
+  user 'pgbouncer-geo'
+  password 'fakepassword-geo'
+  database 'fakedb-geo'
+  add_auth_function true
+  action :create
+end
diff --git a/spec/chef/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_rails.rb b/spec/chef/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_rails.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X2dpdGxhYl9lZS9yZWNpcGVzL3BnYm91bmNlcl91c2VyX2NyZWF0ZV9yYWlscy5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_rails.rb
@@ -0,0 +1,8 @@
+pgbouncer_user 'rails' do
+  helper PgHelper.new(node)
+  user 'pgbouncer-rails'
+  password 'fakepassword-rails'
+  database 'fakedb-rails'
+  add_auth_function true
+  action :create
+end
diff --git a/spec/chef/fixtures/cookbooks/test_package/metadata.rb b/spec/chef/fixtures/cookbooks/test_package/metadata.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3BhY2thZ2UvbWV0YWRhdGEucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_package/metadata.rb
@@ -0,0 +1,3 @@
+name 'test_package'
+depends 'package'
+depends 'runit'
diff --git a/spec/chef/fixtures/cookbooks/test_package/recipes/account_create.rb b/spec/chef/fixtures/cookbooks/test_package/recipes/account_create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3BhY2thZ2UvcmVjaXBlcy9hY2NvdW50X2NyZWF0ZS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_package/recipes/account_create.rb
@@ -0,0 +1,7 @@
+account 'foobar' do
+  username 'foo'
+  groupname 'bar'
+  manage true
+
+  action :create
+end
diff --git a/spec/chef/fixtures/cookbooks/test_package/recipes/account_remove.rb b/spec/chef/fixtures/cookbooks/test_package/recipes/account_remove.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3BhY2thZ2UvcmVjaXBlcy9hY2NvdW50X3JlbW92ZS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_package/recipes/account_remove.rb
@@ -0,0 +1,7 @@
+account 'foobar' do
+  username 'foo'
+  groupname 'bar'
+  manage true
+
+  action :remove
+end
diff --git a/spec/chef/fixtures/cookbooks/test_package/recipes/env_dir_create.rb b/spec/chef/fixtures/cookbooks/test_package/recipes/env_dir_create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3BhY2thZ2UvcmVjaXBlcy9lbnZfZGlyX2NyZWF0ZS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_package/recipes/env_dir_create.rb
@@ -0,0 +1,8 @@
+vars = {
+  'FOO' => 'Lorem',
+  'BAR' => 'Ipsum'
+}
+
+env_dir '/tmp/env' do
+  variables vars
+end
diff --git a/spec/chef/fixtures/cookbooks/test_package/recipes/gitlab_sysctl_create.rb b/spec/chef/fixtures/cookbooks/test_package/recipes/gitlab_sysctl_create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3BhY2thZ2UvcmVjaXBlcy9naXRsYWJfc3lzY3RsX2NyZWF0ZS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_package/recipes/gitlab_sysctl_create.rb
@@ -0,0 +1,3 @@
+gitlab_sysctl 'foo' do
+  value 15000
+end
diff --git a/spec/chef/fixtures/cookbooks/test_package/recipes/storage_directory_create.rb b/spec/chef/fixtures/cookbooks/test_package/recipes/storage_directory_create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3BhY2thZ2UvcmVjaXBlcy9zdG9yYWdlX2RpcmVjdG9yeV9jcmVhdGUucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_package/recipes/storage_directory_create.rb
@@ -0,0 +1,5 @@
+storage_directory '/tmp/bar' do
+  owner 'gitfoo'
+  group 'gitbar'
+  mode '0700'
+end
diff --git a/spec/chef/fixtures/cookbooks/test_package/recipes/templatesymlink_create.rb b/spec/chef/fixtures/cookbooks/test_package/recipes/templatesymlink_create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3BhY2thZ2UvcmVjaXBlcy90ZW1wbGF0ZXN5bWxpbmtfY3JlYXRlLnJi
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_package/recipes/templatesymlink_create.rb
@@ -0,0 +1,6 @@
+templatesymlink 'sample template' do
+  link_from '/opt/gitlab/embedded/service/gitlab-rails/config/database.yml'
+  link_to '/var/opt/gitlab/gitlab-rails/etc/database.yml'
+  source 'test-template.erb'
+  variables(value: 500)
+end
diff --git a/spec/chef/fixtures/cookbooks/test_package/recipes/templatesymlink_delete.rb b/spec/chef/fixtures/cookbooks/test_package/recipes/templatesymlink_delete.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3BhY2thZ2UvcmVjaXBlcy90ZW1wbGF0ZXN5bWxpbmtfZGVsZXRlLnJi
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_package/recipes/templatesymlink_delete.rb
@@ -0,0 +1,6 @@
+templatesymlink 'delete' do
+  link_from '/opt/gitlab/embedded/service/gitlab-rails/config/database.yml'
+  link_to '/var/opt/gitlab/gitlab-rails/etc/database.yml'
+
+  action :delete
+end
diff --git a/spec/chef/fixtures/cookbooks/test_package/recipes/version_file_create.rb b/spec/chef/fixtures/cookbooks/test_package/recipes/version_file_create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3BhY2thZ2UvcmVjaXBlcy92ZXJzaW9uX2ZpbGVfY3JlYXRlLnJi
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_package/recipes/version_file_create.rb
@@ -0,0 +1,8 @@
+runit_service 'foo' do
+end
+
+version_file 'Test version file creation' do
+  version_file_path '/tmp/VERSION_TEST'
+  version_check_cmd 'echo 1.0.0-test'
+  notifies :hup, "runit_service[foo]"
+end
diff --git a/spec/chef/fixtures/cookbooks/test_package/templates/default/test-template.erb b/spec/chef/fixtures/cookbooks/test_package/templates/default/test-template.erb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3BhY2thZ2UvdGVtcGxhdGVzL2RlZmF1bHQvdGVzdC10ZW1wbGF0ZS5lcmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_package/templates/default/test-template.erb
@@ -0,0 +1,1 @@
+value: <%= @value %>
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/metadata.rb b/spec/chef/fixtures/cookbooks/test_postgresql/metadata.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvbWV0YWRhdGEucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/metadata.rb
@@ -0,0 +1,3 @@
+name 'test_postgresql'
+depends 'postgresql'
+depends 'gitlab'
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_config.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_config.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX2NvbmZpZy5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_config.rb
@@ -0,0 +1,1 @@
+postgresql_config 'testconfig'
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX2RhdGFiYXNlX2NyZWF0ZS5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create.rb
@@ -0,0 +1,2 @@
+postgresql_database 'example' do
+end
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create_with_options.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create_with_options.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX2RhdGFiYXNlX2NyZWF0ZV93aXRoX29wdGlvbnMucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create_with_options.rb
@@ -0,0 +1,5 @@
+postgresql_database 'fakedb' do
+  database_port 9999
+  database_socket '/fake/dir'
+  owner 'fakeuser'
+end
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_extension_enable.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_extension_enable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX2V4dGVuc2lvbl9lbmFibGUucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_extension_enable.rb
@@ -0,0 +1,3 @@
+postgresql_extension 'foobar' do
+  database 'lorem'
+end
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_query_run.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_query_run.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX3F1ZXJ5X3J1bi5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_query_run.rb
@@ -0,0 +1,10 @@
+postgresql_query 'create schema' do
+  db_name 'omnibus_test'
+  query "CREATE SCHEMA example AUTHORIZATION foobar;"
+end
+
+postgresql_query 'create schema' do
+  db_name 'omnibus_test'
+  query "CREATE SCHEMA example AUTHORIZATION foobar;"
+  helper GeoPgHelper.new(node)
+end
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_schema_create.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_schema_create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX3NjaGVtYV9jcmVhdGUucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_schema_create.rb
@@ -0,0 +1,3 @@
+postgresql_schema 'example' do
+  database 'omnibus_gitlab_test'
+end
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_create.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX3VzZXJfY3JlYXRlLnJi
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_create.rb
@@ -0,0 +1,2 @@
+postgresql_user 'example' do
+end
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_superuser.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_superuser.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX3VzZXJfb3B0aW9uc19zdXBlcnVzZXIucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_superuser.rb
@@ -0,0 +1,3 @@
+postgresql_user 'example' do
+  options ['SUPERUSER']
+end
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_unspecified.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_unspecified.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX3VzZXJfb3B0aW9uc191bnNwZWNpZmllZC5yYg==
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_unspecified.rb
@@ -0,0 +1,2 @@
+postgresql_user 'example' do
+end
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_empty.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_empty.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX3VzZXJfcGFzc3dvcmRfZW1wdHkucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_empty.rb
@@ -0,0 +1,3 @@
+postgresql_user 'empty_password' do
+  password ''
+end
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_md5.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_md5.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX3VzZXJfcGFzc3dvcmRfbWQ1LnJi
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_md5.rb
@@ -0,0 +1,5 @@
+postgresql_user 'md5_password' do
+  # This is not a secret password:
+  #   ruby -rdigest -e 'puts Digest::MD5.hexdigest("toomanysecrets" + "md5_password")'
+  password 'e99b79fbdf9b997e6918df2385e60f5c'
+end
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_nil.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_nil.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX3VzZXJfcGFzc3dvcmRfbmlsLnJi
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_nil.rb
@@ -0,0 +1,3 @@
+postgresql_user 'nil_password' do
+  password nil
+end
diff --git a/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_unspecified.rb b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_unspecified.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2Nvb2tib29rcy90ZXN0X3Bvc3RncmVzcWwvcmVjaXBlcy9wb3N0Z3Jlc3FsX3VzZXJfcGFzc3dvcmRfdW5zcGVjaWZpZWQucmI=
--- /dev/null
+++ b/spec/chef/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_unspecified.rb
@@ -0,0 +1,2 @@
+postgresql_user 'no_password' do
+end
diff --git a/spec/chef/fixtures/fauxhai/ubuntu/16.04-docker.json b/spec/chef/fixtures/fauxhai/ubuntu/16.04-docker.json
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2ZhdXhoYWkvdWJ1bnR1LzE2LjA0LWRvY2tlci5qc29u
--- /dev/null
+++ b/spec/chef/fixtures/fauxhai/ubuntu/16.04-docker.json
@@ -0,0 +1,2130 @@
+{
+  "block_device": {
+    "sda": {
+      "size": "1677721600",
+      "removable": "0",
+      "model": "PersistentDisk",
+      "rev": "1",
+      "state": "running",
+      "timeout": "30",
+      "vendor": "Google",
+      "queue_depth": "256",
+      "rotational": "1",
+      "physical_block_size": "4096",
+      "logical_block_size": "512"
+    },
+    "loop0": {
+      "size": "177368",
+      "removable": "0",
+      "rotational": "1",
+      "physical_block_size": "512",
+      "logical_block_size": "512"
+    },
+    "loop1": {
+      "size": "177960",
+      "removable": "0",
+      "rotational": "1",
+      "physical_block_size": "512",
+      "logical_block_size": "512"
+    },
+    "loop2": {
+      "size": "177928",
+      "removable": "0",
+      "rotational": "1",
+      "physical_block_size": "512",
+      "logical_block_size": "512"
+    },
+    "loop3": {
+      "size": "0",
+      "removable": "0",
+      "rotational": "1",
+      "physical_block_size": "512",
+      "logical_block_size": "512"
+    },
+    "loop4": {
+      "size": "0",
+      "removable": "0",
+      "rotational": "1",
+      "physical_block_size": "512",
+      "logical_block_size": "512"
+    },
+    "loop5": {
+      "size": "0",
+      "removable": "0",
+      "rotational": "1",
+      "physical_block_size": "512",
+      "logical_block_size": "512"
+    },
+    "loop6": {
+      "size": "0",
+      "removable": "0",
+      "rotational": "1",
+      "physical_block_size": "512",
+      "logical_block_size": "512"
+    },
+    "loop7": {
+      "size": "0",
+      "removable": "0",
+      "rotational": "1",
+      "physical_block_size": "512",
+      "logical_block_size": "512"
+    }
+  },
+  "chef_packages": {
+    "ohai": {
+      "version": "14.3.0",
+      "ohai_root": "/var/lib/gems/2.4.0/gems/ohai-14.3.0/lib/ohai"
+    }
+  },
+  "command": {
+    "ps": "ps -ef"
+  },
+  "counters": {
+    "network": {
+      "interfaces": {
+        "lo": {
+          "tx": {
+            "queuelen": "1",
+            "bytes": 0,
+            "packets": 0,
+            "errors": 0,
+            "drop": 0,
+            "carrier": 0,
+            "collisions": 0
+          },
+          "rx": {
+            "bytes": 0,
+            "packets": 0,
+            "errors": 0,
+            "drop": 0,
+            "overrun": 0
+          }
+        },
+        "eth0": {
+          "rx": {
+            "bytes": 0,
+            "packets": 0,
+            "errors": 0,
+            "drop": 0,
+            "overrun": 0,
+            "frame": 0,
+            "compressed": 0,
+            "multicast": 0
+          },
+          "tx": {
+            "bytes": 0,
+            "packets": 0,
+            "errors": 0,
+            "drop": 0,
+            "overrun": 0,
+            "collisions": 0,
+            "carrier": 0,
+            "compressed": 0
+          }
+        }
+      }
+    }
+  },
+  "cpu": {
+    "real": 1,
+    "total": 1,
+    "cores": 1
+  },
+  "current_user": "fauxhai",
+  "dmi": {
+  },
+  "domain": "local",
+  "filesystem": {
+    "by_device": {
+      "overlay": {
+        "kb_size": "813139348",
+        "kb_used": "314504628",
+        "kb_available": "498618336",
+        "percent_used": "39%",
+        "total_inodes": "102400000",
+        "inodes_used": "7082272",
+        "inodes_available": "95317728",
+        "inodes_percent_used": "7%",
+        "fs_type": "overlay",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "lowerdir=/var/lib/docker/overlay2/l/UVFEI53IFKBMSYIBD6MEWPNZ6F:/var/lib/docker/overlay2/l/BBMNBZRYFC727VBBGOEXGFD3HA:/var/lib/docker/overlay2/l/3CXZ5G5TCWJEUVMPSRSMBQD6Z4:/var/lib/docker/overlay2/l/GCI6OPPHSEJXZTDES7HKM4FJOA:/var/lib/docker/overlay2/l/XQ7VNACY7RTHN2VODETXL7PG2B:/var/lib/docker/overlay2/l/O63N6IYQUXRP6ATLHTRWF76PUK",
+          "upperdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/diff",
+          "workdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/work"
+        ],
+        "mounts": [
+          "/"
+        ]
+      },
+      "tmpfs": {
+        "kb_size": "3829544",
+        "kb_used": "0",
+        "kb_available": "3829544",
+        "percent_used": "0%",
+        "total_inodes": "957386",
+        "inodes_used": "1",
+        "inodes_available": "957385",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "size=65536k",
+          "mode=755"
+        ],
+        "mounts": [
+          "/dev",
+          "/sys/fs/cgroup",
+          "/sys/firmware",
+          "/proc/kcore",
+          "/proc/timer_list",
+          "/proc/timer_stats",
+          "/proc/sched_debug"
+        ]
+      },
+      "/dev/sda1": {
+        "kb_size": "813139348",
+        "kb_used": "314504628",
+        "kb_available": "498618336",
+        "percent_used": "39%",
+        "total_inodes": "102400000",
+        "inodes_used": "7082272",
+        "inodes_available": "95317728",
+        "inodes_percent_used": "7%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "mounts": [
+          "/etc/hosts",
+          "/etc/resolv.conf",
+          "/etc/hostname"
+        ]
+      },
+      "shm": {
+        "kb_size": "65536",
+        "kb_used": "0",
+        "kb_available": "65536",
+        "percent_used": "0%",
+        "total_inodes": "957386",
+        "inodes_used": "1",
+        "inodes_available": "957385",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "size=65536k"
+        ],
+        "mounts": [
+          "/dev/shm"
+        ]
+      },
+      "proc": {
+        "fs_type": "proc",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ],
+        "mounts": [
+          "/proc",
+          "/proc/bus",
+          "/proc/fs",
+          "/proc/irq",
+          "/proc/sys",
+          "/proc/sysrq-trigger"
+        ]
+      },
+      "devpts": {
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=666"
+        ],
+        "mounts": [
+          "/dev/pts",
+          "/dev/console"
+        ]
+      },
+      "sysfs": {
+        "fs_type": "sysfs",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys"
+        ]
+      },
+      "cgroup": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "memory"
+        ],
+        "mounts": [
+          "/sys/fs/cgroup/systemd",
+          "/sys/fs/cgroup/freezer",
+          "/sys/fs/cgroup/hugetlb",
+          "/sys/fs/cgroup/cpu,cpuacct",
+          "/sys/fs/cgroup/net_cls,net_prio",
+          "/sys/fs/cgroup/pids",
+          "/sys/fs/cgroup/blkio",
+          "/sys/fs/cgroup/devices",
+          "/sys/fs/cgroup/cpuset",
+          "/sys/fs/cgroup/perf_event",
+          "/sys/fs/cgroup/memory"
+        ]
+      },
+      "mqueue": {
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/dev/mqueue"
+        ]
+      },
+      "sda": {
+        "mounts": [
+
+        ]
+      },
+      "sda1": {
+        "mounts": [
+
+        ]
+      },
+      "loop0": {
+        "mounts": [
+
+        ]
+      },
+      "loop1": {
+        "mounts": [
+
+        ]
+      },
+      "loop2": {
+        "mounts": [
+
+        ]
+      }
+    },
+    "by_mountpoint": {
+      "/": {
+        "kb_size": "813139348",
+        "kb_used": "314504628",
+        "kb_available": "498618336",
+        "percent_used": "39%",
+        "total_inodes": "102400000",
+        "inodes_used": "7082272",
+        "inodes_available": "95317728",
+        "inodes_percent_used": "7%",
+        "fs_type": "overlay",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "lowerdir=/var/lib/docker/overlay2/l/UVFEI53IFKBMSYIBD6MEWPNZ6F:/var/lib/docker/overlay2/l/BBMNBZRYFC727VBBGOEXGFD3HA:/var/lib/docker/overlay2/l/3CXZ5G5TCWJEUVMPSRSMBQD6Z4:/var/lib/docker/overlay2/l/GCI6OPPHSEJXZTDES7HKM4FJOA:/var/lib/docker/overlay2/l/XQ7VNACY7RTHN2VODETXL7PG2B:/var/lib/docker/overlay2/l/O63N6IYQUXRP6ATLHTRWF76PUK",
+          "upperdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/diff",
+          "workdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/work"
+        ],
+        "devices": [
+          "overlay"
+        ]
+      },
+      "/dev": {
+        "kb_size": "65536",
+        "kb_used": "0",
+        "kb_available": "65536",
+        "percent_used": "0%",
+        "total_inodes": "957386",
+        "inodes_used": "17",
+        "inodes_available": "957369",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "size=65536k",
+          "mode=755"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/sys/fs/cgroup": {
+        "kb_size": "3829544",
+        "kb_used": "0",
+        "kb_available": "3829544",
+        "percent_used": "0%",
+        "total_inodes": "957386",
+        "inodes_used": "16",
+        "inodes_available": "957370",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "mode=755"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/etc/hosts": {
+        "kb_size": "813139348",
+        "kb_used": "314504628",
+        "kb_available": "498618336",
+        "percent_used": "39%",
+        "total_inodes": "102400000",
+        "inodes_used": "7082272",
+        "inodes_available": "95317728",
+        "inodes_percent_used": "7%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "devices": [
+          "/dev/sda1"
+        ]
+      },
+      "/dev/shm": {
+        "kb_size": "65536",
+        "kb_used": "0",
+        "kb_available": "65536",
+        "percent_used": "0%",
+        "total_inodes": "957386",
+        "inodes_used": "1",
+        "inodes_available": "957385",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "size=65536k"
+        ],
+        "devices": [
+          "shm"
+        ]
+      },
+      "/sys/firmware": {
+        "kb_size": "3829544",
+        "kb_used": "0",
+        "kb_available": "3829544",
+        "percent_used": "0%",
+        "total_inodes": "957386",
+        "inodes_used": "1",
+        "inodes_available": "957385",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/proc": {
+        "fs_type": "proc",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "proc"
+        ]
+      },
+      "/dev/pts": {
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=666"
+        ],
+        "devices": [
+          "devpts"
+        ]
+      },
+      "/sys": {
+        "fs_type": "sysfs",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "sysfs"
+        ]
+      },
+      "/sys/fs/cgroup/systemd": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "xattr",
+          "release_agent=/lib/systemd/systemd-cgroups-agent",
+          "name=systemd"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/freezer": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "freezer"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/hugetlb": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "hugetlb"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/cpu,cpuacct": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpu",
+          "cpuacct"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/net_cls,net_prio": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "net_cls",
+          "net_prio"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/pids": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "pids"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/blkio": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "blkio"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/devices": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "devices"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/cpuset": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpuset"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/perf_event": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "perf_event"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/memory": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "memory"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/dev/mqueue": {
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "mqueue"
+        ]
+      },
+      "/etc/resolv.conf": {
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "devices": [
+          "/dev/sda1"
+        ]
+      },
+      "/etc/hostname": {
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "devices": [
+          "/dev/sda1"
+        ]
+      },
+      "/dev/console": {
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=666"
+        ],
+        "devices": [
+          "devpts"
+        ]
+      },
+      "/proc/bus": {
+        "fs_type": "proc",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ],
+        "devices": [
+          "proc"
+        ]
+      },
+      "/proc/fs": {
+        "fs_type": "proc",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ],
+        "devices": [
+          "proc"
+        ]
+      },
+      "/proc/irq": {
+        "fs_type": "proc",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ],
+        "devices": [
+          "proc"
+        ]
+      },
+      "/proc/sys": {
+        "fs_type": "proc",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ],
+        "devices": [
+          "proc"
+        ]
+      },
+      "/proc/sysrq-trigger": {
+        "fs_type": "proc",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ],
+        "devices": [
+          "proc"
+        ]
+      },
+      "/proc/kcore": {
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "size=65536k",
+          "mode=755"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/proc/timer_list": {
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "size=65536k",
+          "mode=755"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/proc/timer_stats": {
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "size=65536k",
+          "mode=755"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/proc/sched_debug": {
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "size=65536k",
+          "mode=755"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      }
+    },
+    "by_pair": {
+      "overlay,/": {
+        "device": "overlay",
+        "kb_size": "813139348",
+        "kb_used": "314504628",
+        "kb_available": "498618336",
+        "percent_used": "39%",
+        "mount": "/",
+        "total_inodes": "102400000",
+        "inodes_used": "7082272",
+        "inodes_available": "95317728",
+        "inodes_percent_used": "7%",
+        "fs_type": "overlay",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "lowerdir=/var/lib/docker/overlay2/l/UVFEI53IFKBMSYIBD6MEWPNZ6F:/var/lib/docker/overlay2/l/BBMNBZRYFC727VBBGOEXGFD3HA:/var/lib/docker/overlay2/l/3CXZ5G5TCWJEUVMPSRSMBQD6Z4:/var/lib/docker/overlay2/l/GCI6OPPHSEJXZTDES7HKM4FJOA:/var/lib/docker/overlay2/l/XQ7VNACY7RTHN2VODETXL7PG2B:/var/lib/docker/overlay2/l/O63N6IYQUXRP6ATLHTRWF76PUK",
+          "upperdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/diff",
+          "workdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/work"
+        ]
+      },
+      "tmpfs,/dev": {
+        "device": "tmpfs",
+        "kb_size": "65536",
+        "kb_used": "0",
+        "kb_available": "65536",
+        "percent_used": "0%",
+        "mount": "/dev",
+        "total_inodes": "957386",
+        "inodes_used": "17",
+        "inodes_available": "957369",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "size=65536k",
+          "mode=755"
+        ]
+      },
+      "tmpfs,/sys/fs/cgroup": {
+        "device": "tmpfs",
+        "kb_size": "3829544",
+        "kb_used": "0",
+        "kb_available": "3829544",
+        "percent_used": "0%",
+        "mount": "/sys/fs/cgroup",
+        "total_inodes": "957386",
+        "inodes_used": "16",
+        "inodes_available": "957370",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "mode=755"
+        ]
+      },
+      "/dev/sda1,/etc/hosts": {
+        "device": "/dev/sda1",
+        "kb_size": "813139348",
+        "kb_used": "314504628",
+        "kb_available": "498618336",
+        "percent_used": "39%",
+        "mount": "/etc/hosts",
+        "total_inodes": "102400000",
+        "inodes_used": "7082272",
+        "inodes_available": "95317728",
+        "inodes_percent_used": "7%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ]
+      },
+      "shm,/dev/shm": {
+        "device": "shm",
+        "kb_size": "65536",
+        "kb_used": "0",
+        "kb_available": "65536",
+        "percent_used": "0%",
+        "mount": "/dev/shm",
+        "total_inodes": "957386",
+        "inodes_used": "1",
+        "inodes_available": "957385",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "size=65536k"
+        ]
+      },
+      "tmpfs,/sys/firmware": {
+        "device": "tmpfs",
+        "kb_size": "3829544",
+        "kb_used": "0",
+        "kb_available": "3829544",
+        "percent_used": "0%",
+        "mount": "/sys/firmware",
+        "total_inodes": "957386",
+        "inodes_used": "1",
+        "inodes_available": "957385",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ]
+      },
+      "proc,/proc": {
+        "device": "proc",
+        "mount": "/proc",
+        "fs_type": "proc",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "devpts,/dev/pts": {
+        "device": "devpts",
+        "mount": "/dev/pts",
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=666"
+        ]
+      },
+      "sysfs,/sys": {
+        "device": "sysfs",
+        "mount": "/sys",
+        "fs_type": "sysfs",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/systemd": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/systemd",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "xattr",
+          "release_agent=/lib/systemd/systemd-cgroups-agent",
+          "name=systemd"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/freezer": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/freezer",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "freezer"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/hugetlb": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/hugetlb",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "hugetlb"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/cpu,cpuacct": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/cpu,cpuacct",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpu",
+          "cpuacct"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/net_cls,net_prio": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/net_cls,net_prio",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "net_cls",
+          "net_prio"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/pids": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/pids",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "pids"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/blkio": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/blkio",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "blkio"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/devices": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/devices",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "devices"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/cpuset": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/cpuset",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpuset"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/perf_event": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/perf_event",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "perf_event"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/memory": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/memory",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "memory"
+        ]
+      },
+      "mqueue,/dev/mqueue": {
+        "device": "mqueue",
+        "mount": "/dev/mqueue",
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "/dev/sda1,/etc/resolv.conf": {
+        "device": "/dev/sda1",
+        "mount": "/etc/resolv.conf",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ]
+      },
+      "/dev/sda1,/etc/hostname": {
+        "device": "/dev/sda1",
+        "mount": "/etc/hostname",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ]
+      },
+      "devpts,/dev/console": {
+        "device": "devpts",
+        "mount": "/dev/console",
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=666"
+        ]
+      },
+      "proc,/proc/bus": {
+        "device": "proc",
+        "mount": "/proc/bus",
+        "fs_type": "proc",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ]
+      },
+      "proc,/proc/fs": {
+        "device": "proc",
+        "mount": "/proc/fs",
+        "fs_type": "proc",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ]
+      },
+      "proc,/proc/irq": {
+        "device": "proc",
+        "mount": "/proc/irq",
+        "fs_type": "proc",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ]
+      },
+      "proc,/proc/sys": {
+        "device": "proc",
+        "mount": "/proc/sys",
+        "fs_type": "proc",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ]
+      },
+      "proc,/proc/sysrq-trigger": {
+        "device": "proc",
+        "mount": "/proc/sysrq-trigger",
+        "fs_type": "proc",
+        "mount_options": [
+          "ro",
+          "relatime"
+        ]
+      },
+      "tmpfs,/proc/kcore": {
+        "device": "tmpfs",
+        "mount": "/proc/kcore",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "size=65536k",
+          "mode=755"
+        ]
+      },
+      "tmpfs,/proc/timer_list": {
+        "device": "tmpfs",
+        "mount": "/proc/timer_list",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "size=65536k",
+          "mode=755"
+        ]
+      },
+      "tmpfs,/proc/timer_stats": {
+        "device": "tmpfs",
+        "mount": "/proc/timer_stats",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "size=65536k",
+          "mode=755"
+        ]
+      },
+      "tmpfs,/proc/sched_debug": {
+        "device": "tmpfs",
+        "mount": "/proc/sched_debug",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "size=65536k",
+          "mode=755"
+        ]
+      },
+      "sda,": {
+        "device": "sda"
+      },
+      "sda1,": {
+        "device": "sda1"
+      },
+      "loop0,": {
+        "device": "loop0"
+      },
+      "loop1,": {
+        "device": "loop1"
+      },
+      "loop2,": {
+        "device": "loop2"
+      }
+    }
+  },
+  "fips": {
+    "kernel": {
+      "enabled": false
+    }
+  },
+  "fqdn": "fauxhai.local",
+  "hostname": "Fauxhai",
+  "idle": "30 days 15 hours 07 minutes 30 seconds",
+  "idletime_seconds": 2646450,
+  "init_package": "bash",
+  "ipaddress": "10.0.0.2",
+  "kernel": {
+    "name": "Linux",
+    "release": "4.4.0-130-generic",
+    "version": "#156-Ubuntu SMP Thu Jun 14 08:53:28 UTC 2018",
+    "machine": "x86_64",
+    "processor": "x86_64",
+    "os": "GNU/Linux",
+    "modules": {
+    }
+  },
+  "keys": {
+    "ssh": {
+      "host_dsa_public": "ssh-dss AAAAB3NzaC1kc3MAAACBAJFo9BLAw4WKEs5hgipk5m423FzBsDXCZSMcC9ca/om/1VYzMqImixGe3uICDzNFUWxFoLJTQAOccyzo6MXZiQqwWJDLFi5qOSr6w2XcMyE+zd4wOyMoDiVM5fizmG8K3FzrqvGjwBcHcBdOQnavSijoj38DN25J9zhrid5BY4WlAAAAFQDxXrCyG52XCzn3FV4ej38wJBkomQAAAIBovGPJ4mP2P6BK8lHl0PPbktwQbWlpJ13oz6REJFDVcUi7vV26bX/BjQX+ohzZQzljdz1SpUbPc/8nuA4darYkVh91eBi307EN8IdxRHj2eBgp/ZG4yshIebG3WHrwJD/xUjjZ1MRfyDT1ermVi4LvjjPgWDxLZnPpMaR6S1nzgQAAAIEAj0Vd6DCWslvlsZ8+N53HWsqPi3gnx35JoLPz9Z2epkKIKqmEHav+93G3hdfztVa4I4t3phoPniQchYryF5+RNg8hqxKzjNtrIqUYCeuf2NJrksNsH7OZygPHZpqt4kTuwAGZxjxEGfAI0y8DhkU2ntp2LnzRnWH106BQBCmcXwo= fauxhai.local",
+      "host_rsa_public": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtLCeqtqr/HbnORckw1ukdLhpfGoOPFi5/esKEokzTqq1gsgQ2V8emmyjfq1i6XXfRtSBxkdlHv/GWdP5wBTuE2G85MzBkVSQPvmwQN8lX/JMPEEtKXkeOo0o92/PiSmvY4eRsdF0mw40Uvg7jtE3f3fxj497kzh5fKtkrHnF4x9gXCbVdr3FqXJfggR5IJwAxToerbK7x/uRS+7YuZI9Pip3tt14nv9ezwXcuGb/tvjWOZINiFl8izVIFKi7sxfTX09p4NgamxRS7TD2Yd0jT8nEoF9UZTsgXcJ1kDSx7N7NxFfNfP6rCdOGRRz4gUhXtsUjG/XkxPeCwZ7A9VnOD fauxhai.local"
+    }
+  },
+  "languages": {
+    "ruby": {
+      "platform": "x86_64-linux-gnu",
+      "version": "2.4.4",
+      "release_date": "2018-03-28",
+      "target": "x86_64-pc-linux-gnu",
+      "target_cpu": "x86_64",
+      "target_vendor": "pc",
+      "target_os": "linux-gnu",
+      "host": "x86_64-pc-linux-gnu",
+      "host_cpu": "x86_64",
+      "host_os": "linux-gnu",
+      "host_vendor": "pc",
+      "bin_dir": "/usr/local/bin",
+      "ruby_bin": "/usr/local/bin/ruby",
+      "gems_dir": "/usr/local/gems",
+      "gem_bin": "/usr/local/bin/gem"
+    },
+    "powershell": null
+  },
+  "lsb": {
+    "id": "Ubuntu",
+    "release": "16.04",
+    "codename": "xenial",
+    "description": "Ubuntu 16.04.3 LTS"
+  },
+  "macaddress": "11:11:11:11:11:11",
+  "machinename": "Fauxhai",
+  "memory": {
+    "total": "1048576kB"
+  },
+  "network": {
+    "interfaces": {
+      "lo": {
+        "mtu": "65536",
+        "flags": [
+          "LOOPBACK",
+          "UP",
+          "LOWER_UP"
+        ],
+        "encapsulation": "Loopback",
+        "addresses": {
+          "127.0.0.1": {
+            "family": "inet",
+            "prefixlen": "8",
+            "netmask": "255.0.0.0",
+            "scope": "Node",
+            "ip_scope": "LOOPBACK"
+          },
+          "::1": {
+            "family": "inet6",
+            "prefixlen": "128",
+            "scope": "Node",
+            "tags": [
+
+            ],
+            "ip_scope": "LINK LOCAL LOOPBACK"
+          }
+        },
+        "state": "unknown"
+      },
+      "eth0": {
+        "type": "eth",
+        "number": "0",
+        "mtu": "1500",
+        "flags": [
+          "BROADCAST",
+          "MULTICAST",
+          "UP",
+          "LOWER_UP"
+        ],
+        "encapsulation": "Ethernet",
+        "addresses": {
+          "11:11:11:11:11:11": {
+            "family": "lladdr"
+          },
+          "10.0.0.2": {
+            "family": "inet",
+            "prefixlen": "24",
+            "netmask": "255.255.255.0",
+            "broadcast": "10.0.0.255",
+            "scope": "Global",
+            "ip_scope": "RFC1918 PRIVATE"
+          },
+          "fe80::11:1111:1111:1111": {
+            "family": "inet6",
+            "prefixlen": "64",
+            "scope": "Link",
+            "tags": [
+
+            ],
+            "ip_scope": "LINK LOCAL UNICAST"
+          }
+        },
+        "state": "up",
+        "arp": {
+          "10.0.0.1": "fe:ff:ff:ff:ff:ff"
+        },
+        "routes": [
+          {
+            "destination": "default",
+            "family": "inet",
+            "via": "10.0.0.1"
+          },
+          {
+            "destination": "10.0.0.0/24",
+            "family": "inet",
+            "scope": "link",
+            "proto": "kernel",
+            "src": "10.0.0.2"
+          },
+          {
+            "destination": "fe80::/64",
+            "family": "inet6",
+            "metric": "256",
+            "proto": "kernel"
+          }
+        ],
+        "ring_params": {
+        }
+      }
+    },
+    "default_interface": "eth0",
+    "default_gateway": "10.0.0.1"
+  },
+  "ohai_time": 1532332846.1504228,
+  "os": "linux",
+  "os_version": "4.4.0-130-generic",
+  "packages": {
+    "adduser": {
+      "version": "3.113+nmu3ubuntu4",
+      "arch": "all"
+    },
+    "apt": {
+      "version": "1.2.27",
+      "arch": "amd64"
+    },
+    "apt-utils": {
+      "version": "1.2.27",
+      "arch": "amd64"
+    },
+    "base-files": {
+      "version": "9.4ubuntu4.5",
+      "arch": "amd64"
+    },
+    "base-passwd": {
+      "version": "3.5.39",
+      "arch": "amd64"
+    },
+    "bash": {
+      "version": "4.3-14ubuntu1.2",
+      "arch": "amd64"
+    },
+    "bsdutils": {
+      "version": "1:2.27.1-6ubuntu3.3",
+      "arch": "amd64"
+    },
+    "ca-certificates": {
+      "version": "20170717~16.04.1",
+      "arch": "all"
+    },
+    "coreutils": {
+      "version": "8.25-2ubuntu3~16.04",
+      "arch": "amd64"
+    },
+    "cron": {
+      "version": "3.0pl1-128ubuntu2",
+      "arch": "amd64"
+    },
+    "curl": {
+      "version": "7.47.0-1ubuntu2.8",
+      "arch": "amd64"
+    },
+    "dash": {
+      "version": "0.5.8-2.1ubuntu2",
+      "arch": "amd64"
+    },
+    "dbus": {
+      "version": "1.10.6-1ubuntu3.3",
+      "arch": "amd64"
+    },
+    "debconf": {
+      "version": "1.5.58ubuntu1",
+      "arch": "all"
+    },
+    "debianutils": {
+      "version": "4.7",
+      "arch": "amd64"
+    },
+    "diffutils": {
+      "version": "1:3.3-3",
+      "arch": "amd64"
+    },
+    "dpkg": {
+      "version": "1.18.4ubuntu1.3",
+      "arch": "amd64"
+    },
+    "dpkg-dev": {
+      "version": "1.18.4ubuntu1.4",
+      "arch": "all"
+    },
+    "e2fslibs": {
+      "version": "1.42.13-1ubuntu1",
+      "arch": "amd64"
+    },
+    "e2fsprogs": {
+      "version": "1.42.13-1ubuntu1",
+      "arch": "amd64"
+    },
+    "fakeroot": {
+      "version": "1.20.2-1ubuntu1",
+      "arch": "amd64"
+    },
+    "findutils": {
+      "version": "4.6.0+git+20160126-2",
+      "arch": "amd64"
+    },
+    "gcc-5-base": {
+      "version": "5.4.0-6ubuntu1~16.04.10",
+      "arch": "amd64"
+    },
+    "gcc-6-base": {
+      "version": "6.0.1-0ubuntu1",
+      "arch": "amd64"
+    },
+    "gnupg": {
+      "version": "1.4.20-1ubuntu3.1",
+      "arch": "amd64"
+    },
+    "gpgv": {
+      "version": "1.4.20-1ubuntu3.1",
+      "arch": "amd64"
+    },
+    "grep": {
+      "version": "2.25-1~16.04.1",
+      "arch": "amd64"
+    },
+    "gzip": {
+      "version": "1.6-4ubuntu1",
+      "arch": "amd64"
+    },
+    "hostname": {
+      "version": "3.16ubuntu2",
+      "arch": "amd64"
+    },
+    "ifupdown": {
+      "version": "0.8.10ubuntu1.4",
+      "arch": "amd64"
+    },
+    "init": {
+      "version": "1.29ubuntu4",
+      "arch": "amd64"
+    },
+    "init-system-helpers": {
+      "version": "1.29ubuntu4",
+      "arch": "all"
+    },
+    "initscripts": {
+      "version": "2.88dsf-59.3ubuntu2",
+      "arch": "amd64"
+    },
+    "insserv": {
+      "version": "1.14.0-5ubuntu3",
+      "arch": "amd64"
+    },
+    "iproute2": {
+      "version": "4.3.0-1ubuntu3.16.04.3",
+      "arch": "amd64"
+    },
+    "isc-dhcp-client": {
+      "version": "4.3.3-5ubuntu12.10",
+      "arch": "amd64"
+    },
+    "javascript-common": {
+      "version": "11",
+      "arch": "all"
+    },
+    "krb5-locales": {
+      "version": "1.13.2+dfsg-5ubuntu2",
+      "arch": "all"
+    },
+    "libacl1": {
+      "version": "2.2.52-3",
+      "arch": "amd64"
+    },
+    "libapparmor1": {
+      "version": "2.10.95-0ubuntu2.7",
+      "arch": "amd64"
+    },
+    "libapt-inst2.0": {
+      "version": "1.2.27",
+      "arch": "amd64"
+    },
+    "libapt-pkg5.0": {
+      "version": "1.2.27",
+      "arch": "amd64"
+    },
+    "libasn1-8-heimdal": {
+      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
+      "arch": "amd64"
+    },
+    "libatm1": {
+      "version": "1:2.5.1-1.5",
+      "arch": "amd64"
+    },
+    "libattr1": {
+      "version": "1:2.4.47-2",
+      "arch": "amd64"
+    },
+    "libaudit-common": {
+      "version": "1:2.4.5-1ubuntu2",
+      "arch": "all"
+    },
+    "libaudit1": {
+      "version": "1:2.4.5-1ubuntu2",
+      "arch": "amd64"
+    },
+    "libblkid1": {
+      "version": "2.27.1-6ubuntu3.3",
+      "arch": "amd64"
+    },
+    "libbz2-1.0": {
+      "version": "1.0.6-8",
+      "arch": "amd64"
+    },
+    "libc-bin": {
+      "version": "2.23-0ubuntu9",
+      "arch": "amd64"
+    },
+    "libc6": {
+      "version": "2.23-0ubuntu10",
+      "arch": "amd64"
+    },
+    "libcap-ng0": {
+      "version": "0.7.7-1",
+      "arch": "amd64"
+    },
+    "libcap2": {
+      "version": "1:2.24-12",
+      "arch": "amd64"
+    },
+    "libcap2-bin": {
+      "version": "1:2.24-12",
+      "arch": "amd64"
+    },
+    "libcomerr2": {
+      "version": "1.42.13-1ubuntu1",
+      "arch": "amd64"
+    },
+    "libcryptsetup4": {
+      "version": "2:1.6.6-5ubuntu2.1",
+      "arch": "amd64"
+    },
+    "libcurl3-gnutls": {
+      "version": "7.47.0-1ubuntu2.8",
+      "arch": "amd64"
+    },
+    "libdb5.3": {
+      "version": "5.3.28-11ubuntu0.1",
+      "arch": "amd64"
+    },
+    "libdbus-1-3": {
+      "version": "1.10.6-1ubuntu3.3",
+      "arch": "amd64"
+    },
+    "libdebconfclient0": {
+      "version": "0.198ubuntu1",
+      "arch": "amd64"
+    },
+    "libdevmapper1.02.1": {
+      "version": "2:1.02.110-1ubuntu10",
+      "arch": "amd64"
+    },
+    "libexpat1": {
+      "version": "2.1.0-7ubuntu0.16.04.3",
+      "arch": "amd64"
+    },
+    "libfakeroot": {
+      "version": "1.20.2-1ubuntu1",
+      "arch": "amd64"
+    },
+    "libfdisk1": {
+      "version": "2.27.1-6ubuntu3.3",
+      "arch": "amd64"
+    },
+    "libffi6": {
+      "version": "3.2.1-4",
+      "arch": "amd64"
+    },
+    "libgcc1": {
+      "version": "1:6.0.1-0ubuntu1",
+      "arch": "amd64"
+    },
+    "libgcrypt20": {
+      "version": "1.6.5-2ubuntu0.3",
+      "arch": "amd64"
+    },
+    "libgdbm3": {
+      "version": "1.8.3-13.1",
+      "arch": "amd64"
+    },
+    "libglib2.0-0": {
+      "version": "2.48.2-0ubuntu1",
+      "arch": "amd64"
+    },
+    "libgmp-dev": {
+      "version": "2:6.1.0+dfsg-2",
+      "arch": "amd64"
+    },
+    "libgmp10": {
+      "version": "2:6.1.0+dfsg-2",
+      "arch": "amd64"
+    },
+    "libgmpxx4ldbl": {
+      "version": "2:6.1.0+dfsg-2",
+      "arch": "amd64"
+    },
+    "libgnutls30": {
+      "version": "3.4.10-4ubuntu1.4",
+      "arch": "amd64"
+    },
+    "libgpg-error0": {
+      "version": "1.21-2ubuntu1",
+      "arch": "amd64"
+    },
+    "libgssapi-krb5-2": {
+      "version": "1.13.2+dfsg-5ubuntu2",
+      "arch": "amd64"
+    },
+    "libgssapi3-heimdal": {
+      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
+      "arch": "amd64"
+    },
+    "libhcrypto4-heimdal": {
+      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
+      "arch": "amd64"
+    },
+    "libheimbase1-heimdal": {
+      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
+      "arch": "amd64"
+    },
+    "libheimntlm0-heimdal": {
+      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
+      "arch": "amd64"
+    },
+    "libhogweed4": {
+      "version": "3.2-1ubuntu0.16.04.1",
+      "arch": "amd64"
+    },
+    "libhx509-5-heimdal": {
+      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
+      "arch": "amd64"
+    },
+    "libidn11": {
+      "version": "1.32-3ubuntu1.2",
+      "arch": "amd64"
+    },
+    "libjs-jquery": {
+      "version": "1.11.3+dfsg-4",
+      "arch": "all"
+    },
+    "libk5crypto3": {
+      "version": "1.13.2+dfsg-5ubuntu2",
+      "arch": "amd64"
+    },
+    "libkeyutils1": {
+      "version": "1.5.9-8ubuntu1",
+      "arch": "amd64"
+    },
+    "libkmod2": {
+      "version": "22-1ubuntu5",
+      "arch": "amd64"
+    },
+    "libkrb5-26-heimdal": {
+      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
+      "arch": "amd64"
+    },
+    "libkrb5-3": {
+      "version": "1.13.2+dfsg-5ubuntu2",
+      "arch": "amd64"
+    },
+    "libkrb5support0": {
+      "version": "1.13.2+dfsg-5ubuntu2",
+      "arch": "amd64"
+    },
+    "libldap-2.4-2": {
+      "version": "2.4.42+dfsg-2ubuntu3.3",
+      "arch": "amd64"
+    },
+    "liblz4-1": {
+      "version": "0.0~r131-2ubuntu2",
+      "arch": "amd64"
+    },
+    "liblzma5": {
+      "version": "5.1.1alpha+20120614-2ubuntu2",
+      "arch": "amd64"
+    },
+    "libmagic1": {
+      "version": "1:5.25-2ubuntu1.1",
+      "arch": "amd64"
+    },
+    "libmnl0": {
+      "version": "1.0.3-5",
+      "arch": "amd64"
+    },
+    "libmount1": {
+      "version": "2.27.1-6ubuntu3.3",
+      "arch": "amd64"
+    },
+    "libmpc3": {
+      "version": "1.0.3-1",
+      "arch": "amd64"
+    },
+    "libncurses5": {
+      "version": "6.0+20160213-1ubuntu1",
+      "arch": "amd64"
+    },
+    "libncursesw5": {
+      "version": "6.0+20160213-1ubuntu1",
+      "arch": "amd64"
+    },
+    "libnettle6": {
+      "version": "3.2-1ubuntu0.16.04.1",
+      "arch": "amd64"
+    },
+    "libp11-kit0": {
+      "version": "0.23.2-5~ubuntu16.04.1",
+      "arch": "amd64"
+    },
+    "libpam-modules": {
+      "version": "1.1.8-3.2ubuntu2",
+      "arch": "amd64"
+    },
+    "libpam-modules-bin": {
+      "version": "1.1.8-3.2ubuntu2",
+      "arch": "amd64"
+    },
+    "libpam-runtime": {
+      "version": "1.1.8-3.2ubuntu2",
+      "arch": "all"
+    },
+    "libpam0g": {
+      "version": "1.1.8-3.2ubuntu2",
+      "arch": "amd64"
+    },
+    "libpcre3": {
+      "version": "2:8.38-3.1",
+      "arch": "amd64"
+    },
+    "libperl5.22": {
+      "version": "5.22.1-9ubuntu0.5",
+      "arch": "amd64"
+    },
+    "libprocps4": {
+      "version": "2:3.3.10-4ubuntu2.3",
+      "arch": "amd64"
+    },
+    "libpython3.5-minimal": {
+      "version": "3.5.2-2ubuntu0~16.04.4",
+      "arch": "amd64"
+    },
+    "libreadline6": {
+      "version": "6.3-8ubuntu2",
+      "arch": "amd64"
+    },
+    "libroken18-heimdal": {
+      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
+      "arch": "amd64"
+    },
+    "librtmp1": {
+      "version": "2.4+20151223.gitfa8646d-1ubuntu0.1",
+      "arch": "amd64"
+    },
+    "libruby2.4": {
+      "version": "2.4.4-1bbox1~xenial1",
+      "arch": "amd64"
+    },
+    "libsasl2-2": {
+      "version": "2.1.26.dfsg1-14build1",
+      "arch": "amd64"
+    },
+    "libsasl2-modules": {
+      "version": "2.1.26.dfsg1-14build1",
+      "arch": "amd64"
+    },
+    "libsasl2-modules-db": {
+      "version": "2.1.26.dfsg1-14build1",
+      "arch": "amd64"
+    },
+    "libseccomp2": {
+      "version": "2.2.3-3ubuntu3",
+      "arch": "amd64"
+    },
+    "libselinux1": {
+      "version": "2.4-3build2",
+      "arch": "amd64"
+    },
+    "libsemanage-common": {
+      "version": "2.3-1build3",
+      "arch": "all"
+    },
+    "libsemanage1": {
+      "version": "2.3-1build3",
+      "arch": "amd64"
+    },
+    "libsepol1": {
+      "version": "2.4-2",
+      "arch": "amd64"
+    },
+    "libsmartcols1": {
+      "version": "2.27.1-6ubuntu3.3",
+      "arch": "amd64"
+    },
+    "libsqlite3-0": {
+      "version": "3.11.0-1ubuntu1",
+      "arch": "amd64"
+    },
+    "libss2": {
+      "version": "1.42.13-1ubuntu1",
+      "arch": "amd64"
+    },
+    "libssl1.0.0": {
+      "version": "1.0.2g-1ubuntu4.13",
+      "arch": "amd64"
+    },
+    "libstdc++6": {
+      "version": "5.4.0-6ubuntu1~16.04.10",
+      "arch": "amd64"
+    },
+    "libsystemd0": {
+      "version": "229-4ubuntu21",
+      "arch": "amd64"
+    },
+    "libtasn1-6": {
+      "version": "4.7-3ubuntu0.16.04.3",
+      "arch": "amd64"
+    },
+    "libtinfo5": {
+      "version": "6.0+20160213-1ubuntu1",
+      "arch": "amd64"
+    },
+    "libudev1": {
+      "version": "229-4ubuntu21",
+      "arch": "amd64"
+    },
+    "libusb-0.1-4": {
+      "version": "2:0.1.12-28",
+      "arch": "amd64"
+    },
+    "libustr-1.0-1": {
+      "version": "1.0.4-5",
+      "arch": "amd64"
+    },
+    "libuuid1": {
+      "version": "2.27.1-6ubuntu3.3",
+      "arch": "amd64"
+    },
+    "libwind0-heimdal": {
+      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
+      "arch": "amd64"
+    },
+    "libyaml-0-2": {
+      "version": "0.1.6-3",
+      "arch": "amd64"
+    },
+    "login": {
+      "version": "1:4.2-3.1ubuntu5.3",
+      "arch": "amd64"
+    },
+    "lsb-base": {
+      "version": "9.20160110ubuntu0.2",
+      "arch": "all"
+    },
+    "lsb-release": {
+      "version": "9.20160110ubuntu0.2",
+      "arch": "all"
+    },
+    "makedev": {
+      "version": "2.3.1-93ubuntu2~ubuntu16.04.1",
+      "arch": "all"
+    },
+    "mawk": {
+      "version": "1.3.3-17ubuntu2",
+      "arch": "amd64"
+    },
+    "mime-support": {
+      "version": "3.59ubuntu1",
+      "arch": "all"
+    },
+    "mount": {
+      "version": "2.27.1-6ubuntu3.3",
+      "arch": "amd64"
+    },
+    "multiarch-support": {
+      "version": "2.23-0ubuntu9",
+      "arch": "amd64"
+    },
+    "ncurses-base": {
+      "version": "6.0+20160213-1ubuntu1",
+      "arch": "all"
+    },
+    "ncurses-bin": {
+      "version": "6.0+20160213-1ubuntu1",
+      "arch": "amd64"
+    },
+    "netbase": {
+      "version": "5.3",
+      "arch": "all"
+    },
+    "openssl": {
+      "version": "1.0.2g-1ubuntu4.13",
+      "arch": "amd64"
+    },
+    "passwd": {
+      "version": "1:4.2-3.1ubuntu5.3",
+      "arch": "amd64"
+    },
+    "perl": {
+      "version": "5.22.1-9ubuntu0.5",
+      "arch": "amd64"
+    },
+    "perl-base": {
+      "version": "5.22.1-9ubuntu0.5",
+      "arch": "amd64"
+    },
+    "procps": {
+      "version": "2:3.3.10-4ubuntu2.3",
+      "arch": "amd64"
+    },
+    "python3": {
+      "version": "3.5.1-3",
+      "arch": "amd64"
+    },
+    "python3.5-minimal": {
+      "version": "3.5.2-2ubuntu0~16.04.4",
+      "arch": "amd64"
+    },
+    "readline-common": {
+      "version": "6.3-8ubuntu2",
+      "arch": "all"
+    },
+    "ruby2.4": {
+      "version": "2.4.4-1bbox1~xenial1",
+      "arch": "amd64"
+    },
+    "ruby2.4-dev": {
+      "version": "2.4.4-1bbox1~xenial1",
+      "arch": "amd64"
+    },
+    "rubygems-integration": {
+      "version": "1.10",
+      "arch": "all"
+    },
+    "sed": {
+      "version": "4.2.2-7",
+      "arch": "amd64"
+    },
+    "sensible-utils": {
+      "version": "0.0.9",
+      "arch": "all"
+    },
+    "sgml-base": {
+      "version": "1.26+nmu4ubuntu1",
+      "arch": "all"
+    },
+    "shared-mime-info": {
+      "version": "1.5-2ubuntu0.1",
+      "arch": "amd64"
+    },
+    "systemd": {
+      "version": "229-4ubuntu21",
+      "arch": "amd64"
+    },
+    "systemd-sysv": {
+      "version": "229-4ubuntu21",
+      "arch": "amd64"
+    },
+    "sysv-rc": {
+      "version": "2.88dsf-59.3ubuntu2",
+      "arch": "all"
+    },
+    "sysvinit-utils": {
+      "version": "2.88dsf-59.3ubuntu2",
+      "arch": "amd64"
+    },
+    "tar": {
+      "version": "1.28-2.1ubuntu0.1",
+      "arch": "amd64"
+    },
+    "ubuntu-keyring": {
+      "version": "2012.05.19",
+      "arch": "all"
+    },
+    "ucf": {
+      "version": "3.0036",
+      "arch": "all"
+    },
+    "unattended-upgrades": {
+      "version": "0.90ubuntu0.9",
+      "arch": "all"
+    },
+    "unzip": {
+      "version": "6.0-20ubuntu1",
+      "arch": "amd64"
+    },
+    "util-linux": {
+      "version": "2.27.1-6ubuntu3.3",
+      "arch": "amd64"
+    },
+    "xdg-user-dirs": {
+      "version": "0.15-2ubuntu6.16.04.1",
+      "arch": "amd64"
+    },
+    "xml-core": {
+      "version": "0.13+nmu2",
+      "arch": "all"
+    },
+    "zlib1g": {
+      "version": "1:1.2.8.dfsg-2ubuntu4.1",
+      "arch": "amd64"
+    }
+  },
+  "platform": "ubuntu",
+  "platform_family": "debian",
+  "platform_version": "16.04",
+  "root_group": "root",
+  "shard_seed": 161653957,
+  "shells": [
+    "/bin/sh",
+    "/bin/dash",
+    "/bin/bash",
+    "/bin/rbash"
+  ],
+  "time": {
+    "timezone": "GMT"
+  },
+  "uptime": "30 days 15 hours 07 minutes 30 seconds",
+  "uptime_seconds": 2646450,
+  "virtualization": {
+    "systems": {
+    }
+  }
+}
diff --git a/spec/chef/fixtures/fauxhai/ubuntu/16.04-more-cpus.json b/spec/chef/fixtures/fauxhai/ubuntu/16.04-more-cpus.json
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2ZhdXhoYWkvdWJ1bnR1LzE2LjA0LW1vcmUtY3B1cy5qc29u
--- /dev/null
+++ b/spec/chef/fixtures/fauxhai/ubuntu/16.04-more-cpus.json
@@ -0,0 +1,1519 @@
+{
+  "kernel": {
+    "name": "Linux",
+    "release": "4.8.0-52-generic",
+    "version": "#55~16.04.1-Ubuntu SMP Fri Apr 28 14:36:29 UTC 2017",
+    "machine": "x86_64",
+    "processor": "x86_64",
+    "os": "GNU/Linux",
+    "modules": {
+      "ip6table_filter": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "ip6_tables": {
+        "size": "28672",
+        "refcount": "1"
+      },
+      "iptable_filter": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "ip_tables": {
+        "size": "24576",
+        "refcount": "1"
+      },
+      "x_tables": {
+        "size": "36864",
+        "refcount": "4"
+      },
+      "ppdev": {
+        "size": "20480",
+        "refcount": "0"
+      },
+      "input_leds": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "serio_raw": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "parport_pc": {
+        "size": "32768",
+        "refcount": "0"
+      },
+      "pvpanic": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "parport": {
+        "size": "49152",
+        "refcount": "2"
+      },
+      "ib_iser": {
+        "size": "49152",
+        "refcount": "0",
+        "version": "1.6"
+      },
+      "rdma_cm": {
+        "size": "57344",
+        "refcount": "1"
+      },
+      "iw_cm": {
+        "size": "49152",
+        "refcount": "1"
+      },
+      "ib_cm": {
+        "size": "45056",
+        "refcount": "1"
+      },
+      "ib_core": {
+        "size": "212992",
+        "refcount": "4"
+      },
+      "configfs": {
+        "size": "40960",
+        "refcount": "2",
+        "version": "0.0.2"
+      },
+      "iscsi_tcp": {
+        "size": "20480",
+        "refcount": "0"
+      },
+      "libiscsi_tcp": {
+        "size": "24576",
+        "refcount": "1"
+      },
+      "libiscsi": {
+        "size": "53248",
+        "refcount": "3"
+      },
+      "scsi_transport_iscsi": {
+        "size": "98304",
+        "refcount": "4",
+        "version": "2.0-870"
+      },
+      "autofs4": {
+        "size": "40960",
+        "refcount": "2"
+      },
+      "btrfs": {
+        "size": "1060864",
+        "refcount": "0"
+      },
+      "raid10": {
+        "size": "49152",
+        "refcount": "0"
+      },
+      "raid456": {
+        "size": "110592",
+        "refcount": "0"
+      },
+      "async_raid6_recov": {
+        "size": "20480",
+        "refcount": "1"
+      },
+      "async_memcpy": {
+        "size": "16384",
+        "refcount": "2"
+      },
+      "async_pq": {
+        "size": "16384",
+        "refcount": "2"
+      },
+      "async_xor": {
+        "size": "16384",
+        "refcount": "3"
+      },
+      "async_tx": {
+        "size": "16384",
+        "refcount": "5"
+      },
+      "xor": {
+        "size": "24576",
+        "refcount": "2"
+      },
+      "raid6_pq": {
+        "size": "102400",
+        "refcount": "4"
+      },
+      "libcrc32c": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "raid1": {
+        "size": "40960",
+        "refcount": "0"
+      },
+      "raid0": {
+        "size": "20480",
+        "refcount": "0"
+      },
+      "multipath": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "linear": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "crct10dif_pclmul": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "crc32_pclmul": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "ghash_clmulni_intel": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "aesni_intel": {
+        "size": "167936",
+        "refcount": "0"
+      },
+      "aes_x86_64": {
+        "size": "20480",
+        "refcount": "1"
+      },
+      "lrw": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "glue_helper": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "ablk_helper": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "cryptd": {
+        "size": "24576",
+        "refcount": "3"
+      },
+      "psmouse": {
+        "size": "139264",
+        "refcount": "0"
+      },
+      "virtio_net": {
+        "size": "32768",
+        "refcount": "0"
+      },
+      "virtio_scsi": {
+        "size": "20480",
+        "refcount": "1"
+      }
+    }
+  },
+  "os": "linux",
+  "os_version": "4.8.0-52-generic",
+  "lsb": {
+    "id": "Ubuntu",
+    "description": "Ubuntu 16.04.2 LTS",
+    "release": "16.04",
+    "codename": "xenial"
+  },
+  "platform": "ubuntu",
+  "platform_version": "16.04",
+  "platform_family": "debian",
+  "filesystem": {
+    "by_device": {
+      "udev": {
+        "kb_size": "1886700",
+        "kb_used": "0",
+        "kb_available": "1886700",
+        "percent_used": "0%",
+        "total_inodes": "471675",
+        "inodes_used": "371",
+        "inodes_available": "471304",
+        "inodes_percent_used": "1%",
+        "fs_type": "devtmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "relatime",
+          "size=1886700k",
+          "nr_inodes=471675",
+          "mode=755"
+        ],
+        "mounts": [
+          "/dev"
+        ]
+      },
+      "tmpfs": {
+        "kb_size": "378828",
+        "kb_used": "0",
+        "kb_available": "378828",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "4",
+        "inodes_available": "473527",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "size=378828k",
+          "mode=700",
+          "uid=1001",
+          "gid=1002"
+        ],
+        "mounts": [
+          "/run",
+          "/dev/shm",
+          "/run/lock",
+          "/sys/fs/cgroup",
+          "/run/user/1001"
+        ]
+      },
+      "/dev/sda1": {
+        "kb_size": "10098468",
+        "kb_used": "1401956",
+        "kb_available": "8680128",
+        "percent_used": "14%",
+        "total_inodes": "1280000",
+        "inodes_used": "90038",
+        "inodes_available": "1189962",
+        "inodes_percent_used": "8%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
+        "label": "cloudimg-rootfs",
+        "mounts": [
+          "/"
+        ]
+      },
+      "sysfs": {
+        "fs_type": "sysfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys"
+        ]
+      },
+      "proc": {
+        "fs_type": "proc",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/proc"
+        ]
+      },
+      "devpts": {
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=000"
+        ],
+        "mounts": [
+          "/dev/pts"
+        ]
+      },
+      "securityfs": {
+        "fs_type": "securityfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/kernel/security"
+        ]
+      },
+      "cgroup": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "hugetlb"
+        ],
+        "mounts": [
+          "/sys/fs/cgroup/systemd",
+          "/sys/fs/cgroup/perf_event",
+          "/sys/fs/cgroup/blkio",
+          "/sys/fs/cgroup/devices",
+          "/sys/fs/cgroup/net_cls,net_prio",
+          "/sys/fs/cgroup/cpuset",
+          "/sys/fs/cgroup/cpu,cpuacct",
+          "/sys/fs/cgroup/memory",
+          "/sys/fs/cgroup/freezer",
+          "/sys/fs/cgroup/pids",
+          "/sys/fs/cgroup/hugetlb"
+        ]
+      },
+      "pstore": {
+        "fs_type": "pstore",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/fs/pstore"
+        ]
+      },
+      "hugetlbfs": {
+        "fs_type": "hugetlbfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/dev/hugepages"
+        ]
+      },
+      "mqueue": {
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/dev/mqueue"
+        ]
+      },
+      "systemd-1": {
+        "fs_type": "autofs",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "fd=33",
+          "pgrp=1",
+          "timeout=0",
+          "minproto=5",
+          "maxproto=5",
+          "direct",
+          "pipe_ino=10449"
+        ],
+        "mounts": [
+          "/proc/sys/fs/binfmt_misc"
+        ]
+      },
+      "debugfs": {
+        "fs_type": "debugfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/kernel/debug"
+        ]
+      },
+      "fusectl": {
+        "fs_type": "fusectl",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/fs/fuse/connections"
+        ]
+      },
+      "configfs": {
+        "fs_type": "configfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/kernel/config"
+        ]
+      },
+      "lxcfs": {
+        "fs_type": "fuse.lxcfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "user_id=0",
+          "group_id=0",
+          "allow_other"
+        ],
+        "mounts": [
+          "/var/lib/lxcfs"
+        ]
+      },
+      "/dev/sda": {
+      }
+    },
+    "by_mountpoint": {
+      "/dev": {
+        "kb_size": "1886700",
+        "kb_used": "0",
+        "kb_available": "1886700",
+        "percent_used": "0%",
+        "total_inodes": "471675",
+        "inodes_used": "371",
+        "inodes_available": "471304",
+        "inodes_percent_used": "1%",
+        "fs_type": "devtmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "relatime",
+          "size=1886700k",
+          "nr_inodes=471675",
+          "mode=755"
+        ],
+        "devices": [
+          "udev"
+        ]
+      },
+      "/run": {
+        "kb_size": "378828",
+        "kb_used": "5292",
+        "kb_available": "373536",
+        "percent_used": "2%",
+        "total_inodes": "473531",
+        "inodes_used": "460",
+        "inodes_available": "473071",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "size=378828k",
+          "mode=755"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/": {
+        "kb_size": "10098468",
+        "kb_used": "1401956",
+        "kb_available": "8680128",
+        "percent_used": "14%",
+        "total_inodes": "1280000",
+        "inodes_used": "90038",
+        "inodes_available": "1189962",
+        "inodes_percent_used": "8%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
+        "label": "cloudimg-rootfs",
+        "devices": [
+          "/dev/sda1"
+        ]
+      },
+      "/dev/shm": {
+        "kb_size": "1894124",
+        "kb_used": "0",
+        "kb_available": "1894124",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "1",
+        "inodes_available": "473530",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/run/lock": {
+        "kb_size": "5120",
+        "kb_used": "0",
+        "kb_available": "5120",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "8",
+        "inodes_available": "473523",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "size=5120k"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/sys/fs/cgroup": {
+        "kb_size": "1894124",
+        "kb_used": "0",
+        "kb_available": "1894124",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "16",
+        "inodes_available": "473515",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "mode=755"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/run/user/1001": {
+        "kb_size": "378828",
+        "kb_used": "0",
+        "kb_available": "378828",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "4",
+        "inodes_available": "473527",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "size=378828k",
+          "mode=700",
+          "uid=1001",
+          "gid=1002"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/sys": {
+        "fs_type": "sysfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "sysfs"
+        ]
+      },
+      "/proc": {
+        "fs_type": "proc",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "proc"
+        ]
+      },
+      "/dev/pts": {
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=000"
+        ],
+        "devices": [
+          "devpts"
+        ]
+      },
+      "/sys/kernel/security": {
+        "fs_type": "securityfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "securityfs"
+        ]
+      },
+      "/sys/fs/cgroup/systemd": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "xattr",
+          "release_agent=/lib/systemd/systemd-cgroups-agent",
+          "name=systemd"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/pstore": {
+        "fs_type": "pstore",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "pstore"
+        ]
+      },
+      "/sys/fs/cgroup/perf_event": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "perf_event"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/blkio": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "blkio"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/devices": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "devices"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/net_cls,net_prio": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "net_cls",
+          "net_prio"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/cpuset": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpuset"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/cpu,cpuacct": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpu",
+          "cpuacct"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/memory": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "memory"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/freezer": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "freezer"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/pids": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "pids"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/hugetlb": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "hugetlb"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/dev/hugepages": {
+        "fs_type": "hugetlbfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "hugetlbfs"
+        ]
+      },
+      "/dev/mqueue": {
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "mqueue"
+        ]
+      },
+      "/proc/sys/fs/binfmt_misc": {
+        "fs_type": "autofs",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "fd=33",
+          "pgrp=1",
+          "timeout=0",
+          "minproto=5",
+          "maxproto=5",
+          "direct",
+          "pipe_ino=10449"
+        ],
+        "devices": [
+          "systemd-1"
+        ]
+      },
+      "/sys/kernel/debug": {
+        "fs_type": "debugfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "debugfs"
+        ]
+      },
+      "/sys/fs/fuse/connections": {
+        "fs_type": "fusectl",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "fusectl"
+        ]
+      },
+      "/sys/kernel/config": {
+        "fs_type": "configfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "configfs"
+        ]
+      },
+      "/var/lib/lxcfs": {
+        "fs_type": "fuse.lxcfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "user_id=0",
+          "group_id=0",
+          "allow_other"
+        ],
+        "devices": [
+          "lxcfs"
+        ]
+      }
+    },
+    "by_pair": {
+      "udev,/dev": {
+        "device": "udev",
+        "kb_size": "1886700",
+        "kb_used": "0",
+        "kb_available": "1886700",
+        "percent_used": "0%",
+        "mount": "/dev",
+        "total_inodes": "471675",
+        "inodes_used": "371",
+        "inodes_available": "471304",
+        "inodes_percent_used": "1%",
+        "fs_type": "devtmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "relatime",
+          "size=1886700k",
+          "nr_inodes=471675",
+          "mode=755"
+        ]
+      },
+      "tmpfs,/run": {
+        "device": "tmpfs",
+        "kb_size": "378828",
+        "kb_used": "5292",
+        "kb_available": "373536",
+        "percent_used": "2%",
+        "mount": "/run",
+        "total_inodes": "473531",
+        "inodes_used": "460",
+        "inodes_available": "473071",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "size=378828k",
+          "mode=755"
+        ]
+      },
+      "/dev/sda1,/": {
+        "device": "/dev/sda1",
+        "kb_size": "10098468",
+        "kb_used": "1401956",
+        "kb_available": "8680128",
+        "percent_used": "14%",
+        "mount": "/",
+        "total_inodes": "1280000",
+        "inodes_used": "90038",
+        "inodes_available": "1189962",
+        "inodes_percent_used": "8%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
+        "label": "cloudimg-rootfs"
+      },
+      "tmpfs,/dev/shm": {
+        "device": "tmpfs",
+        "kb_size": "1894124",
+        "kb_used": "0",
+        "kb_available": "1894124",
+        "percent_used": "0%",
+        "mount": "/dev/shm",
+        "total_inodes": "473531",
+        "inodes_used": "1",
+        "inodes_available": "473530",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev"
+        ]
+      },
+      "tmpfs,/run/lock": {
+        "device": "tmpfs",
+        "kb_size": "5120",
+        "kb_used": "0",
+        "kb_available": "5120",
+        "percent_used": "0%",
+        "mount": "/run/lock",
+        "total_inodes": "473531",
+        "inodes_used": "8",
+        "inodes_available": "473523",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "size=5120k"
+        ]
+      },
+      "tmpfs,/sys/fs/cgroup": {
+        "device": "tmpfs",
+        "kb_size": "1894124",
+        "kb_used": "0",
+        "kb_available": "1894124",
+        "percent_used": "0%",
+        "mount": "/sys/fs/cgroup",
+        "total_inodes": "473531",
+        "inodes_used": "16",
+        "inodes_available": "473515",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "mode=755"
+        ]
+      },
+      "tmpfs,/run/user/1001": {
+        "device": "tmpfs",
+        "kb_size": "378828",
+        "kb_used": "0",
+        "kb_available": "378828",
+        "percent_used": "0%",
+        "mount": "/run/user/1001",
+        "total_inodes": "473531",
+        "inodes_used": "4",
+        "inodes_available": "473527",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "size=378828k",
+          "mode=700",
+          "uid=1001",
+          "gid=1002"
+        ]
+      },
+      "sysfs,/sys": {
+        "device": "sysfs",
+        "mount": "/sys",
+        "fs_type": "sysfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "proc,/proc": {
+        "device": "proc",
+        "mount": "/proc",
+        "fs_type": "proc",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "devpts,/dev/pts": {
+        "device": "devpts",
+        "mount": "/dev/pts",
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=000"
+        ]
+      },
+      "securityfs,/sys/kernel/security": {
+        "device": "securityfs",
+        "mount": "/sys/kernel/security",
+        "fs_type": "securityfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/systemd": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/systemd",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "xattr",
+          "release_agent=/lib/systemd/systemd-cgroups-agent",
+          "name=systemd"
+        ]
+      },
+      "pstore,/sys/fs/pstore": {
+        "device": "pstore",
+        "mount": "/sys/fs/pstore",
+        "fs_type": "pstore",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/perf_event": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/perf_event",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "perf_event"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/blkio": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/blkio",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "blkio"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/devices": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/devices",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "devices"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/net_cls,net_prio": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/net_cls,net_prio",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "net_cls",
+          "net_prio"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/cpuset": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/cpuset",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpuset"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/cpu,cpuacct": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/cpu,cpuacct",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpu",
+          "cpuacct"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/memory": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/memory",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "memory"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/freezer": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/freezer",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "freezer"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/pids": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/pids",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "pids"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/hugetlb": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/hugetlb",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "hugetlb"
+        ]
+      },
+      "hugetlbfs,/dev/hugepages": {
+        "device": "hugetlbfs",
+        "mount": "/dev/hugepages",
+        "fs_type": "hugetlbfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "mqueue,/dev/mqueue": {
+        "device": "mqueue",
+        "mount": "/dev/mqueue",
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "systemd-1,/proc/sys/fs/binfmt_misc": {
+        "device": "systemd-1",
+        "mount": "/proc/sys/fs/binfmt_misc",
+        "fs_type": "autofs",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "fd=33",
+          "pgrp=1",
+          "timeout=0",
+          "minproto=5",
+          "maxproto=5",
+          "direct",
+          "pipe_ino=10449"
+        ]
+      },
+      "debugfs,/sys/kernel/debug": {
+        "device": "debugfs",
+        "mount": "/sys/kernel/debug",
+        "fs_type": "debugfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "fusectl,/sys/fs/fuse/connections": {
+        "device": "fusectl",
+        "mount": "/sys/fs/fuse/connections",
+        "fs_type": "fusectl",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "configfs,/sys/kernel/config": {
+        "device": "configfs",
+        "mount": "/sys/kernel/config",
+        "fs_type": "configfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "lxcfs,/var/lib/lxcfs": {
+        "device": "lxcfs",
+        "mount": "/var/lib/lxcfs",
+        "fs_type": "fuse.lxcfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "user_id=0",
+          "group_id=0",
+          "allow_other"
+        ]
+      },
+      "/dev/sda,": {
+        "device": "/dev/sda"
+      }
+    }
+  },
+  "dmi": {
+    "dmidecode_version": "3.0"
+  },
+  "init_package": "systemd",
+  "ohai_time": 1496219099.5081818,
+  "command": {
+    "ps": "ps -ef"
+  },
+  "root_group": "root",
+  "languages": {
+    "ruby": {
+      "platform": "x86_64-linux-gnu",
+      "version": "2.3.1",
+      "release_date": "2016-04-26",
+      "target": "x86_64-pc-linux-gnu",
+      "target_cpu": "x86_64",
+      "target_vendor": "pc",
+      "target_os": "linux-gnu",
+      "host": "x86_64-pc-linux-gnu",
+      "host_cpu": "x86_64",
+      "host_os": "linux-gnu",
+      "host_vendor": "pc",
+      "bin_dir": "/usr/local/bin",
+      "ruby_bin": "/usr/local/bin/ruby",
+      "gems_dir": "/usr/local/gems",
+      "gem_bin": "/usr/local/bin/gem"
+    },
+    "powershell": null
+  },
+  "chef_packages": {
+    "chef": {
+      "version": "13.1.31",
+      "chef_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-13.1.31/lib"
+    },
+    "ohai": {
+      "version": "13.1.0",
+      "ohai_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/ohai-13.1.0/lib/ohai"
+    }
+  },
+  "counters": {
+    "network": {
+      "interfaces": {
+        "eth0": {
+          "rx": {
+            "bytes": "0",
+            "packets": "0",
+            "errors": "0",
+            "drop": 0,
+            "overrun": 0,
+            "frame": 0,
+            "compressed": 0,
+            "multicast": 0
+          },
+          "tx": {
+            "bytes": "342",
+            "packets": "0",
+            "errors": "0",
+            "drop": 0,
+            "overrun": 0,
+            "collisions": "0",
+            "carrier": 0,
+            "compressed": 0
+          }
+        }
+      }
+    }
+  },
+  "current_user": "fauxhai",
+  "domain": "local",
+  "etc": {
+    "passwd": {
+      "fauxhai": {
+        "dir": "/home/fauxhai",
+        "gid": 0,
+        "uid": 0,
+        "shell": "/bin/bash",
+        "gecos": "Fauxhai"
+      }
+    },
+    "group": {
+      "fauxhai": {
+        "gid": 0,
+        "members": [
+          "fauxhai"
+        ]
+      }
+    }
+  },
+  "hostname": "Fauxhai",
+  "fqdn": "fauxhai.local",
+  "ipaddress": "10.0.0.2",
+  "keys": {
+    "ssh": {
+      "host_dsa_public": "ssh-dss AAAAB3NzaC1kc3MAAACBAJFo9BLAw4WKEs5hgipk5m423FzBsDXCZSMcC9ca/om/1VYzMqImixGe3uICDzNFUWxFoLJTQAOccyzo6MXZiQqwWJDLFi5qOSr6w2XcMyE+zd4wOyMoDiVM5fizmG8K3FzrqvGjwBcHcBdOQnavSijoj38DN25J9zhrid5BY4WlAAAAFQDxXrCyG52XCzn3FV4ej38wJBkomQAAAIBovGPJ4mP2P6BK8lHl0PPbktwQbWlpJ13oz6REJFDVcUi7vV26bX/BjQX+ohzZQzljdz1SpUbPc/8nuA4darYkVh91eBi307EN8IdxRHj2eBgp/ZG4yshIebG3WHrwJD/xUjjZ1MRfyDT1ermVi4LvjjPgWDxLZnPpMaR6S1nzgQAAAIEAj0Vd6DCWslvlsZ8+N53HWsqPi3gnx35JoLPz9Z2epkKIKqmEHav+93G3hdfztVa4I4t3phoPniQchYryF5+RNg8hqxKzjNtrIqUYCeuf2NJrksNsH7OZygPHZpqt4kTuwAGZxjxEGfAI0y8DhkU2ntp2LnzRnWH106BQBCmcXwo= fauxhai.local",
+      "host_rsa_public": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtLCeqtqr/HbnORckw1ukdLhpfGoOPFi5/esKEokzTqq1gsgQ2V8emmyjfq1i6XXfRtSBxkdlHv/GWdP5wBTuE2G85MzBkVSQPvmwQN8lX/JMPEEtKXkeOo0o92/PiSmvY4eRsdF0mw40Uvg7jtE3f3fxj497kzh5fKtkrHnF4x9gXCbVdr3FqXJfggR5IJwAxToerbK7x/uRS+7YuZI9Pip3tt14nv9ezwXcuGb/tvjWOZINiFl8izVIFKi7sxfTX09p4NgamxRS7TD2Yd0jT8nEoF9UZTsgXcJ1kDSx7N7NxFfNfP6rCdOGRRz4gUhXtsUjG/XkxPeCwZ7A9VnOD fauxhai.local"
+    }
+  },
+  "macaddress": "11:11:11:11:11:11",
+  "network": {
+    "default_gateway": "10.0.0.1",
+    "default_interface": "eth0",
+    "settings": {
+    },
+    "interfaces": {
+      "eth0": {
+        "addresses": {
+          "10.0.0.2": {
+            "broadcast": "10.0.0.255",
+            "family": "inet",
+            "netmask": "255.255.255.0",
+            "prefixlen": "24",
+            "scope": "Global"
+          }
+        },
+        "arp": {
+          "10.0.0.1": "fe:ff:ff:ff:ff:ff"
+        },
+        "encapsulation": "Ethernet",
+        "flags": [
+          "BROADCAST",
+          "MULTICAST",
+          "UP",
+          "LOWER_UP"
+        ],
+        "mtu": "1500",
+        "number": "0",
+        "routes": [
+          {
+            "destination": "10.0.0.0/24",
+            "family": "inet",
+            "scope": "link",
+            "proto": "kernel",
+            "src": "10.0.0.2"
+          }
+        ],
+        "state": "up",
+        "type": "eth"
+      }
+    }
+  },
+  "uptime": "30 days 15 hours 07 minutes 30 seconds",
+  "uptime_seconds": 2646450,
+  "cpu": {
+    "real": 16,
+    "total": 16,
+    "cores": 16
+  },
+  "memory": {
+    "total": "33554432kB"
+  },
+  "virtualization": {
+    "systems": {
+    }
+  }
+}
diff --git a/spec/chef/fixtures/fauxhai/ubuntu/16.04-no-run-tmpfs.json b/spec/chef/fixtures/fauxhai/ubuntu/16.04-no-run-tmpfs.json
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2ZhdXhoYWkvdWJ1bnR1LzE2LjA0LW5vLXJ1bi10bXBmcy5qc29u
--- /dev/null
+++ b/spec/chef/fixtures/fauxhai/ubuntu/16.04-no-run-tmpfs.json
@@ -0,0 +1,1438 @@
+{
+  "kernel": {
+    "name": "Linux",
+    "release": "4.8.0-52-generic",
+    "version": "#55~16.04.1-Ubuntu SMP Fri Apr 28 14:36:29 UTC 2017",
+    "machine": "x86_64",
+    "processor": "x86_64",
+    "os": "GNU/Linux",
+    "modules": {
+      "ip6table_filter": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "ip6_tables": {
+        "size": "28672",
+        "refcount": "1"
+      },
+      "iptable_filter": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "ip_tables": {
+        "size": "24576",
+        "refcount": "1"
+      },
+      "x_tables": {
+        "size": "36864",
+        "refcount": "4"
+      },
+      "ppdev": {
+        "size": "20480",
+        "refcount": "0"
+      },
+      "input_leds": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "serio_raw": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "parport_pc": {
+        "size": "32768",
+        "refcount": "0"
+      },
+      "pvpanic": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "parport": {
+        "size": "49152",
+        "refcount": "2"
+      },
+      "ib_iser": {
+        "size": "49152",
+        "refcount": "0",
+        "version": "1.6"
+      },
+      "rdma_cm": {
+        "size": "57344",
+        "refcount": "1"
+      },
+      "iw_cm": {
+        "size": "49152",
+        "refcount": "1"
+      },
+      "ib_cm": {
+        "size": "45056",
+        "refcount": "1"
+      },
+      "ib_core": {
+        "size": "212992",
+        "refcount": "4"
+      },
+      "configfs": {
+        "size": "40960",
+        "refcount": "2",
+        "version": "0.0.2"
+      },
+      "iscsi_tcp": {
+        "size": "20480",
+        "refcount": "0"
+      },
+      "libiscsi_tcp": {
+        "size": "24576",
+        "refcount": "1"
+      },
+      "libiscsi": {
+        "size": "53248",
+        "refcount": "3"
+      },
+      "scsi_transport_iscsi": {
+        "size": "98304",
+        "refcount": "4",
+        "version": "2.0-870"
+      },
+      "autofs4": {
+        "size": "40960",
+        "refcount": "2"
+      },
+      "btrfs": {
+        "size": "1060864",
+        "refcount": "0"
+      },
+      "raid10": {
+        "size": "49152",
+        "refcount": "0"
+      },
+      "raid456": {
+        "size": "110592",
+        "refcount": "0"
+      },
+      "async_raid6_recov": {
+        "size": "20480",
+        "refcount": "1"
+      },
+      "async_memcpy": {
+        "size": "16384",
+        "refcount": "2"
+      },
+      "async_pq": {
+        "size": "16384",
+        "refcount": "2"
+      },
+      "async_xor": {
+        "size": "16384",
+        "refcount": "3"
+      },
+      "async_tx": {
+        "size": "16384",
+        "refcount": "5"
+      },
+      "xor": {
+        "size": "24576",
+        "refcount": "2"
+      },
+      "raid6_pq": {
+        "size": "102400",
+        "refcount": "4"
+      },
+      "libcrc32c": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "raid1": {
+        "size": "40960",
+        "refcount": "0"
+      },
+      "raid0": {
+        "size": "20480",
+        "refcount": "0"
+      },
+      "multipath": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "linear": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "crct10dif_pclmul": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "crc32_pclmul": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "ghash_clmulni_intel": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "aesni_intel": {
+        "size": "167936",
+        "refcount": "0"
+      },
+      "aes_x86_64": {
+        "size": "20480",
+        "refcount": "1"
+      },
+      "lrw": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "glue_helper": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "ablk_helper": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "cryptd": {
+        "size": "24576",
+        "refcount": "3"
+      },
+      "psmouse": {
+        "size": "139264",
+        "refcount": "0"
+      },
+      "virtio_net": {
+        "size": "32768",
+        "refcount": "0"
+      },
+      "virtio_scsi": {
+        "size": "20480",
+        "refcount": "1"
+      }
+    }
+  },
+  "os": "linux",
+  "os_version": "4.8.0-52-generic",
+  "lsb": {
+    "id": "Ubuntu",
+    "description": "Ubuntu 16.04.2 LTS",
+    "release": "16.04",
+    "codename": "xenial"
+  },
+  "platform": "ubuntu",
+  "platform_version": "16.04",
+  "platform_family": "debian",
+  "filesystem": {
+    "by_device": {
+      "udev": {
+        "kb_size": "1886700",
+        "kb_used": "0",
+        "kb_available": "1886700",
+        "percent_used": "0%",
+        "total_inodes": "471675",
+        "inodes_used": "371",
+        "inodes_available": "471304",
+        "inodes_percent_used": "1%",
+        "fs_type": "devtmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "relatime",
+          "size=1886700k",
+          "nr_inodes=471675",
+          "mode=755"
+        ],
+        "mounts": [
+          "/dev"
+        ]
+      },
+      "tmpfs": {
+        "kb_size": "378828",
+        "kb_used": "0",
+        "kb_available": "378828",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "4",
+        "inodes_available": "473527",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "size=378828k",
+          "mode=700",
+          "uid=1001",
+          "gid=1002"
+        ],
+        "mounts": [
+          "/dev/shm",
+          "/run/lock",
+          "/sys/fs/cgroup",
+          "/run/user/1001"
+        ]
+      },
+      "/dev/sda1": {
+        "kb_size": "10098468",
+        "kb_used": "1401956",
+        "kb_available": "8680128",
+        "percent_used": "14%",
+        "total_inodes": "1280000",
+        "inodes_used": "90038",
+        "inodes_available": "1189962",
+        "inodes_percent_used": "8%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
+        "label": "cloudimg-rootfs",
+        "mounts": [
+          "/"
+        ]
+      },
+      "sysfs": {
+        "fs_type": "sysfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys"
+        ]
+      },
+      "proc": {
+        "fs_type": "proc",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/proc"
+        ]
+      },
+      "devpts": {
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=000"
+        ],
+        "mounts": [
+          "/dev/pts"
+        ]
+      },
+      "securityfs": {
+        "fs_type": "securityfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/kernel/security"
+        ]
+      },
+      "cgroup": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "hugetlb"
+        ],
+        "mounts": [
+          "/sys/fs/cgroup/systemd",
+          "/sys/fs/cgroup/perf_event",
+          "/sys/fs/cgroup/blkio",
+          "/sys/fs/cgroup/devices",
+          "/sys/fs/cgroup/net_cls,net_prio",
+          "/sys/fs/cgroup/cpuset",
+          "/sys/fs/cgroup/cpu,cpuacct",
+          "/sys/fs/cgroup/memory",
+          "/sys/fs/cgroup/freezer",
+          "/sys/fs/cgroup/pids",
+          "/sys/fs/cgroup/hugetlb"
+        ]
+      },
+      "pstore": {
+        "fs_type": "pstore",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/fs/pstore"
+        ]
+      },
+      "hugetlbfs": {
+        "fs_type": "hugetlbfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/dev/hugepages"
+        ]
+      },
+      "mqueue": {
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/dev/mqueue"
+        ]
+      },
+      "systemd-1": {
+        "fs_type": "autofs",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "fd=33",
+          "pgrp=1",
+          "timeout=0",
+          "minproto=5",
+          "maxproto=5",
+          "direct",
+          "pipe_ino=10449"
+        ],
+        "mounts": [
+          "/proc/sys/fs/binfmt_misc"
+        ]
+      },
+      "debugfs": {
+        "fs_type": "debugfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/kernel/debug"
+        ]
+      },
+      "fusectl": {
+        "fs_type": "fusectl",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/fs/fuse/connections"
+        ]
+      },
+      "configfs": {
+        "fs_type": "configfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/kernel/config"
+        ]
+      },
+      "lxcfs": {
+        "fs_type": "fuse.lxcfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "user_id=0",
+          "group_id=0",
+          "allow_other"
+        ],
+        "mounts": [
+          "/var/lib/lxcfs"
+        ]
+      },
+      "/dev/sda": {
+      }
+    },
+    "by_mountpoint": {
+      "/dev": {
+        "kb_size": "1886700",
+        "kb_used": "0",
+        "kb_available": "1886700",
+        "percent_used": "0%",
+        "total_inodes": "471675",
+        "inodes_used": "371",
+        "inodes_available": "471304",
+        "inodes_percent_used": "1%",
+        "fs_type": "devtmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "relatime",
+          "size=1886700k",
+          "nr_inodes=471675",
+          "mode=755"
+        ],
+        "devices": [
+          "udev"
+        ]
+      },
+      "/": {
+        "kb_size": "10098468",
+        "kb_used": "1401956",
+        "kb_available": "8680128",
+        "percent_used": "14%",
+        "total_inodes": "1280000",
+        "inodes_used": "90038",
+        "inodes_available": "1189962",
+        "inodes_percent_used": "8%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
+        "label": "cloudimg-rootfs",
+        "devices": [
+          "/dev/sda1"
+        ]
+      },
+      "/run/lock": {
+        "kb_size": "5120",
+        "kb_used": "0",
+        "kb_available": "5120",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "8",
+        "inodes_available": "473523",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "size=5120k"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/sys/fs/cgroup": {
+        "kb_size": "1894124",
+        "kb_used": "0",
+        "kb_available": "1894124",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "16",
+        "inodes_available": "473515",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "mode=755"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/run/user/1001": {
+        "kb_size": "378828",
+        "kb_used": "0",
+        "kb_available": "378828",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "4",
+        "inodes_available": "473527",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "size=378828k",
+          "mode=700",
+          "uid=1001",
+          "gid=1002"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/sys": {
+        "fs_type": "sysfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "sysfs"
+        ]
+      },
+      "/proc": {
+        "fs_type": "proc",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "proc"
+        ]
+      },
+      "/dev/pts": {
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=000"
+        ],
+        "devices": [
+          "devpts"
+        ]
+      },
+      "/sys/kernel/security": {
+        "fs_type": "securityfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "securityfs"
+        ]
+      },
+      "/sys/fs/cgroup/systemd": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "xattr",
+          "release_agent=/lib/systemd/systemd-cgroups-agent",
+          "name=systemd"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/pstore": {
+        "fs_type": "pstore",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "pstore"
+        ]
+      },
+      "/sys/fs/cgroup/perf_event": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "perf_event"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/blkio": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "blkio"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/devices": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "devices"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/net_cls,net_prio": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "net_cls",
+          "net_prio"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/cpuset": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpuset"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/cpu,cpuacct": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpu",
+          "cpuacct"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/memory": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "memory"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/freezer": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "freezer"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/pids": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "pids"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/hugetlb": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "hugetlb"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/dev/hugepages": {
+        "fs_type": "hugetlbfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "hugetlbfs"
+        ]
+      },
+      "/dev/mqueue": {
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "mqueue"
+        ]
+      },
+      "/proc/sys/fs/binfmt_misc": {
+        "fs_type": "autofs",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "fd=33",
+          "pgrp=1",
+          "timeout=0",
+          "minproto=5",
+          "maxproto=5",
+          "direct",
+          "pipe_ino=10449"
+        ],
+        "devices": [
+          "systemd-1"
+        ]
+      },
+      "/sys/kernel/debug": {
+        "fs_type": "debugfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "debugfs"
+        ]
+      },
+      "/sys/fs/fuse/connections": {
+        "fs_type": "fusectl",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "fusectl"
+        ]
+      },
+      "/sys/kernel/config": {
+        "fs_type": "configfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "configfs"
+        ]
+      },
+      "/var/lib/lxcfs": {
+        "fs_type": "fuse.lxcfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "user_id=0",
+          "group_id=0",
+          "allow_other"
+        ],
+        "devices": [
+          "lxcfs"
+        ]
+      }
+    },
+    "by_pair": {
+      "udev,/dev": {
+        "device": "udev",
+        "kb_size": "1886700",
+        "kb_used": "0",
+        "kb_available": "1886700",
+        "percent_used": "0%",
+        "mount": "/dev",
+        "total_inodes": "471675",
+        "inodes_used": "371",
+        "inodes_available": "471304",
+        "inodes_percent_used": "1%",
+        "fs_type": "devtmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "relatime",
+          "size=1886700k",
+          "nr_inodes=471675",
+          "mode=755"
+        ]
+      },
+      "/dev/sda1,/": {
+        "device": "/dev/sda1",
+        "kb_size": "10098468",
+        "kb_used": "1401956",
+        "kb_available": "8680128",
+        "percent_used": "14%",
+        "mount": "/",
+        "total_inodes": "1280000",
+        "inodes_used": "90038",
+        "inodes_available": "1189962",
+        "inodes_percent_used": "8%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
+        "label": "cloudimg-rootfs"
+      },
+      "tmpfs,/run/lock": {
+        "device": "tmpfs",
+        "kb_size": "5120",
+        "kb_used": "0",
+        "kb_available": "5120",
+        "percent_used": "0%",
+        "mount": "/run/lock",
+        "total_inodes": "473531",
+        "inodes_used": "8",
+        "inodes_available": "473523",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "size=5120k"
+        ]
+      },
+      "tmpfs,/sys/fs/cgroup": {
+        "device": "tmpfs",
+        "kb_size": "1894124",
+        "kb_used": "0",
+        "kb_available": "1894124",
+        "percent_used": "0%",
+        "mount": "/sys/fs/cgroup",
+        "total_inodes": "473531",
+        "inodes_used": "16",
+        "inodes_available": "473515",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "mode=755"
+        ]
+      },
+      "tmpfs,/run/user/1001": {
+        "device": "tmpfs",
+        "kb_size": "378828",
+        "kb_used": "0",
+        "kb_available": "378828",
+        "percent_used": "0%",
+        "mount": "/run/user/1001",
+        "total_inodes": "473531",
+        "inodes_used": "4",
+        "inodes_available": "473527",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "size=378828k",
+          "mode=700",
+          "uid=1001",
+          "gid=1002"
+        ]
+      },
+      "sysfs,/sys": {
+        "device": "sysfs",
+        "mount": "/sys",
+        "fs_type": "sysfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "proc,/proc": {
+        "device": "proc",
+        "mount": "/proc",
+        "fs_type": "proc",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "devpts,/dev/pts": {
+        "device": "devpts",
+        "mount": "/dev/pts",
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=000"
+        ]
+      },
+      "securityfs,/sys/kernel/security": {
+        "device": "securityfs",
+        "mount": "/sys/kernel/security",
+        "fs_type": "securityfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/systemd": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/systemd",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "xattr",
+          "release_agent=/lib/systemd/systemd-cgroups-agent",
+          "name=systemd"
+        ]
+      },
+      "pstore,/sys/fs/pstore": {
+        "device": "pstore",
+        "mount": "/sys/fs/pstore",
+        "fs_type": "pstore",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/perf_event": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/perf_event",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "perf_event"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/blkio": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/blkio",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "blkio"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/devices": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/devices",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "devices"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/net_cls,net_prio": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/net_cls,net_prio",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "net_cls",
+          "net_prio"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/cpuset": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/cpuset",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpuset"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/cpu,cpuacct": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/cpu,cpuacct",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpu",
+          "cpuacct"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/memory": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/memory",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "memory"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/freezer": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/freezer",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "freezer"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/pids": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/pids",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "pids"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/hugetlb": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/hugetlb",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "hugetlb"
+        ]
+      },
+      "hugetlbfs,/dev/hugepages": {
+        "device": "hugetlbfs",
+        "mount": "/dev/hugepages",
+        "fs_type": "hugetlbfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "mqueue,/dev/mqueue": {
+        "device": "mqueue",
+        "mount": "/dev/mqueue",
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "systemd-1,/proc/sys/fs/binfmt_misc": {
+        "device": "systemd-1",
+        "mount": "/proc/sys/fs/binfmt_misc",
+        "fs_type": "autofs",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "fd=33",
+          "pgrp=1",
+          "timeout=0",
+          "minproto=5",
+          "maxproto=5",
+          "direct",
+          "pipe_ino=10449"
+        ]
+      },
+      "debugfs,/sys/kernel/debug": {
+        "device": "debugfs",
+        "mount": "/sys/kernel/debug",
+        "fs_type": "debugfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "fusectl,/sys/fs/fuse/connections": {
+        "device": "fusectl",
+        "mount": "/sys/fs/fuse/connections",
+        "fs_type": "fusectl",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "configfs,/sys/kernel/config": {
+        "device": "configfs",
+        "mount": "/sys/kernel/config",
+        "fs_type": "configfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "lxcfs,/var/lib/lxcfs": {
+        "device": "lxcfs",
+        "mount": "/var/lib/lxcfs",
+        "fs_type": "fuse.lxcfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "user_id=0",
+          "group_id=0",
+          "allow_other"
+        ]
+      },
+      "/dev/sda,": {
+        "device": "/dev/sda"
+      }
+    }
+  },
+  "dmi": {
+    "dmidecode_version": "3.0"
+  },
+  "init_package": "systemd",
+  "ohai_time": 1496219099.5081818,
+  "command": {
+    "ps": "ps -ef"
+  },
+  "root_group": "root",
+  "languages": {
+    "ruby": {
+      "platform": "x86_64-linux-gnu",
+      "version": "2.3.1",
+      "release_date": "2016-04-26",
+      "target": "x86_64-pc-linux-gnu",
+      "target_cpu": "x86_64",
+      "target_vendor": "pc",
+      "target_os": "linux-gnu",
+      "host": "x86_64-pc-linux-gnu",
+      "host_cpu": "x86_64",
+      "host_os": "linux-gnu",
+      "host_vendor": "pc",
+      "bin_dir": "/usr/local/bin",
+      "ruby_bin": "/usr/local/bin/ruby",
+      "gems_dir": "/usr/local/gems",
+      "gem_bin": "/usr/local/bin/gem"
+    },
+    "powershell": null
+  },
+  "chef_packages": {
+    "chef": {
+      "version": "13.1.31",
+      "chef_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-13.1.31/lib"
+    },
+    "ohai": {
+      "version": "13.1.0",
+      "ohai_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/ohai-13.1.0/lib/ohai"
+    }
+  },
+  "counters": {
+    "network": {
+      "interfaces": {
+        "eth0": {
+          "rx": {
+            "bytes": "0",
+            "packets": "0",
+            "errors": "0",
+            "drop": 0,
+            "overrun": 0,
+            "frame": 0,
+            "compressed": 0,
+            "multicast": 0
+          },
+          "tx": {
+            "bytes": "342",
+            "packets": "0",
+            "errors": "0",
+            "drop": 0,
+            "overrun": 0,
+            "collisions": "0",
+            "carrier": 0,
+            "compressed": 0
+          }
+        }
+      }
+    }
+  },
+  "current_user": "fauxhai",
+  "domain": "local",
+  "etc": {
+    "passwd": {
+      "fauxhai": {
+        "dir": "/home/fauxhai",
+        "gid": 0,
+        "uid": 0,
+        "shell": "/bin/bash",
+        "gecos": "Fauxhai"
+      }
+    },
+    "group": {
+      "fauxhai": {
+        "gid": 0,
+        "members": [
+          "fauxhai"
+        ]
+      }
+    }
+  },
+  "hostname": "Fauxhai",
+  "fqdn": "fauxhai.local",
+  "ipaddress": "10.0.0.2",
+  "keys": {
+    "ssh": {
+      "host_dsa_public": "ssh-dss AAAAB3NzaC1kc3MAAACBAJFo9BLAw4WKEs5hgipk5m423FzBsDXCZSMcC9ca/om/1VYzMqImixGe3uICDzNFUWxFoLJTQAOccyzo6MXZiQqwWJDLFi5qOSr6w2XcMyE+zd4wOyMoDiVM5fizmG8K3FzrqvGjwBcHcBdOQnavSijoj38DN25J9zhrid5BY4WlAAAAFQDxXrCyG52XCzn3FV4ej38wJBkomQAAAIBovGPJ4mP2P6BK8lHl0PPbktwQbWlpJ13oz6REJFDVcUi7vV26bX/BjQX+ohzZQzljdz1SpUbPc/8nuA4darYkVh91eBi307EN8IdxRHj2eBgp/ZG4yshIebG3WHrwJD/xUjjZ1MRfyDT1ermVi4LvjjPgWDxLZnPpMaR6S1nzgQAAAIEAj0Vd6DCWslvlsZ8+N53HWsqPi3gnx35JoLPz9Z2epkKIKqmEHav+93G3hdfztVa4I4t3phoPniQchYryF5+RNg8hqxKzjNtrIqUYCeuf2NJrksNsH7OZygPHZpqt4kTuwAGZxjxEGfAI0y8DhkU2ntp2LnzRnWH106BQBCmcXwo= fauxhai.local",
+      "host_rsa_public": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtLCeqtqr/HbnORckw1ukdLhpfGoOPFi5/esKEokzTqq1gsgQ2V8emmyjfq1i6XXfRtSBxkdlHv/GWdP5wBTuE2G85MzBkVSQPvmwQN8lX/JMPEEtKXkeOo0o92/PiSmvY4eRsdF0mw40Uvg7jtE3f3fxj497kzh5fKtkrHnF4x9gXCbVdr3FqXJfggR5IJwAxToerbK7x/uRS+7YuZI9Pip3tt14nv9ezwXcuGb/tvjWOZINiFl8izVIFKi7sxfTX09p4NgamxRS7TD2Yd0jT8nEoF9UZTsgXcJ1kDSx7N7NxFfNfP6rCdOGRRz4gUhXtsUjG/XkxPeCwZ7A9VnOD fauxhai.local"
+    }
+  },
+  "macaddress": "11:11:11:11:11:11",
+  "network": {
+    "default_gateway": "10.0.0.1",
+    "default_interface": "eth0",
+    "settings": {
+    },
+    "interfaces": {
+      "eth0": {
+        "addresses": {
+          "10.0.0.2": {
+            "broadcast": "10.0.0.255",
+            "family": "inet",
+            "netmask": "255.255.255.0",
+            "prefixlen": "24",
+            "scope": "Global"
+          }
+        },
+        "arp": {
+          "10.0.0.1": "fe:ff:ff:ff:ff:ff"
+        },
+        "encapsulation": "Ethernet",
+        "flags": [
+          "BROADCAST",
+          "MULTICAST",
+          "UP",
+          "LOWER_UP"
+        ],
+        "mtu": "1500",
+        "number": "0",
+        "routes": [
+          {
+            "destination": "10.0.0.0/24",
+            "family": "inet",
+            "scope": "link",
+            "proto": "kernel",
+            "src": "10.0.0.2"
+          }
+        ],
+        "state": "up",
+        "type": "eth"
+      }
+    }
+  },
+  "uptime": "30 days 15 hours 07 minutes 30 seconds",
+  "uptime_seconds": 2646450,
+  "cpu": {
+    "real": 1,
+    "total": 1,
+    "cores": 1
+  },
+  "memory": {
+    "total": "1048576kB"
+  },
+  "virtualization": {
+    "systems": {
+    }
+  }
+}
diff --git a/spec/chef/fixtures/fauxhai/ubuntu/16.04.json b/spec/chef/fixtures/fauxhai/ubuntu/16.04.json
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL2ZhdXhoYWkvdWJ1bnR1LzE2LjA0Lmpzb24=
--- /dev/null
+++ b/spec/chef/fixtures/fauxhai/ubuntu/16.04.json
@@ -0,0 +1,1519 @@
+{
+  "kernel": {
+    "name": "Linux",
+    "release": "4.8.0-52-generic",
+    "version": "#55~16.04.1-Ubuntu SMP Fri Apr 28 14:36:29 UTC 2017",
+    "machine": "x86_64",
+    "processor": "x86_64",
+    "os": "GNU/Linux",
+    "modules": {
+      "ip6table_filter": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "ip6_tables": {
+        "size": "28672",
+        "refcount": "1"
+      },
+      "iptable_filter": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "ip_tables": {
+        "size": "24576",
+        "refcount": "1"
+      },
+      "x_tables": {
+        "size": "36864",
+        "refcount": "4"
+      },
+      "ppdev": {
+        "size": "20480",
+        "refcount": "0"
+      },
+      "input_leds": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "serio_raw": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "parport_pc": {
+        "size": "32768",
+        "refcount": "0"
+      },
+      "pvpanic": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "parport": {
+        "size": "49152",
+        "refcount": "2"
+      },
+      "ib_iser": {
+        "size": "49152",
+        "refcount": "0",
+        "version": "1.6"
+      },
+      "rdma_cm": {
+        "size": "57344",
+        "refcount": "1"
+      },
+      "iw_cm": {
+        "size": "49152",
+        "refcount": "1"
+      },
+      "ib_cm": {
+        "size": "45056",
+        "refcount": "1"
+      },
+      "ib_core": {
+        "size": "212992",
+        "refcount": "4"
+      },
+      "configfs": {
+        "size": "40960",
+        "refcount": "2",
+        "version": "0.0.2"
+      },
+      "iscsi_tcp": {
+        "size": "20480",
+        "refcount": "0"
+      },
+      "libiscsi_tcp": {
+        "size": "24576",
+        "refcount": "1"
+      },
+      "libiscsi": {
+        "size": "53248",
+        "refcount": "3"
+      },
+      "scsi_transport_iscsi": {
+        "size": "98304",
+        "refcount": "4",
+        "version": "2.0-870"
+      },
+      "autofs4": {
+        "size": "40960",
+        "refcount": "2"
+      },
+      "btrfs": {
+        "size": "1060864",
+        "refcount": "0"
+      },
+      "raid10": {
+        "size": "49152",
+        "refcount": "0"
+      },
+      "raid456": {
+        "size": "110592",
+        "refcount": "0"
+      },
+      "async_raid6_recov": {
+        "size": "20480",
+        "refcount": "1"
+      },
+      "async_memcpy": {
+        "size": "16384",
+        "refcount": "2"
+      },
+      "async_pq": {
+        "size": "16384",
+        "refcount": "2"
+      },
+      "async_xor": {
+        "size": "16384",
+        "refcount": "3"
+      },
+      "async_tx": {
+        "size": "16384",
+        "refcount": "5"
+      },
+      "xor": {
+        "size": "24576",
+        "refcount": "2"
+      },
+      "raid6_pq": {
+        "size": "102400",
+        "refcount": "4"
+      },
+      "libcrc32c": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "raid1": {
+        "size": "40960",
+        "refcount": "0"
+      },
+      "raid0": {
+        "size": "20480",
+        "refcount": "0"
+      },
+      "multipath": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "linear": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "crct10dif_pclmul": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "crc32_pclmul": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "ghash_clmulni_intel": {
+        "size": "16384",
+        "refcount": "0"
+      },
+      "aesni_intel": {
+        "size": "167936",
+        "refcount": "0"
+      },
+      "aes_x86_64": {
+        "size": "20480",
+        "refcount": "1"
+      },
+      "lrw": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "glue_helper": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "ablk_helper": {
+        "size": "16384",
+        "refcount": "1"
+      },
+      "cryptd": {
+        "size": "24576",
+        "refcount": "3"
+      },
+      "psmouse": {
+        "size": "139264",
+        "refcount": "0"
+      },
+      "virtio_net": {
+        "size": "32768",
+        "refcount": "0"
+      },
+      "virtio_scsi": {
+        "size": "20480",
+        "refcount": "1"
+      }
+    }
+  },
+  "os": "linux",
+  "os_version": "4.8.0-52-generic",
+  "lsb": {
+    "id": "Ubuntu",
+    "description": "Ubuntu 16.04.2 LTS",
+    "release": "16.04",
+    "codename": "xenial"
+  },
+  "platform": "ubuntu",
+  "platform_version": "16.04",
+  "platform_family": "debian",
+  "filesystem": {
+    "by_device": {
+      "udev": {
+        "kb_size": "1886700",
+        "kb_used": "0",
+        "kb_available": "1886700",
+        "percent_used": "0%",
+        "total_inodes": "471675",
+        "inodes_used": "371",
+        "inodes_available": "471304",
+        "inodes_percent_used": "1%",
+        "fs_type": "devtmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "relatime",
+          "size=1886700k",
+          "nr_inodes=471675",
+          "mode=755"
+        ],
+        "mounts": [
+          "/dev"
+        ]
+      },
+      "tmpfs": {
+        "kb_size": "378828",
+        "kb_used": "0",
+        "kb_available": "378828",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "4",
+        "inodes_available": "473527",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "size=378828k",
+          "mode=700",
+          "uid=1001",
+          "gid=1002"
+        ],
+        "mounts": [
+          "/run",
+          "/dev/shm",
+          "/run/lock",
+          "/sys/fs/cgroup",
+          "/run/user/1001"
+        ]
+      },
+      "/dev/sda1": {
+        "kb_size": "10098468",
+        "kb_used": "1401956",
+        "kb_available": "8680128",
+        "percent_used": "14%",
+        "total_inodes": "1280000",
+        "inodes_used": "90038",
+        "inodes_available": "1189962",
+        "inodes_percent_used": "8%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
+        "label": "cloudimg-rootfs",
+        "mounts": [
+          "/"
+        ]
+      },
+      "sysfs": {
+        "fs_type": "sysfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys"
+        ]
+      },
+      "proc": {
+        "fs_type": "proc",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/proc"
+        ]
+      },
+      "devpts": {
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=000"
+        ],
+        "mounts": [
+          "/dev/pts"
+        ]
+      },
+      "securityfs": {
+        "fs_type": "securityfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/kernel/security"
+        ]
+      },
+      "cgroup": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "hugetlb"
+        ],
+        "mounts": [
+          "/sys/fs/cgroup/systemd",
+          "/sys/fs/cgroup/perf_event",
+          "/sys/fs/cgroup/blkio",
+          "/sys/fs/cgroup/devices",
+          "/sys/fs/cgroup/net_cls,net_prio",
+          "/sys/fs/cgroup/cpuset",
+          "/sys/fs/cgroup/cpu,cpuacct",
+          "/sys/fs/cgroup/memory",
+          "/sys/fs/cgroup/freezer",
+          "/sys/fs/cgroup/pids",
+          "/sys/fs/cgroup/hugetlb"
+        ]
+      },
+      "pstore": {
+        "fs_type": "pstore",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/fs/pstore"
+        ]
+      },
+      "hugetlbfs": {
+        "fs_type": "hugetlbfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/dev/hugepages"
+        ]
+      },
+      "mqueue": {
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/dev/mqueue"
+        ]
+      },
+      "systemd-1": {
+        "fs_type": "autofs",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "fd=33",
+          "pgrp=1",
+          "timeout=0",
+          "minproto=5",
+          "maxproto=5",
+          "direct",
+          "pipe_ino=10449"
+        ],
+        "mounts": [
+          "/proc/sys/fs/binfmt_misc"
+        ]
+      },
+      "debugfs": {
+        "fs_type": "debugfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/kernel/debug"
+        ]
+      },
+      "fusectl": {
+        "fs_type": "fusectl",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/fs/fuse/connections"
+        ]
+      },
+      "configfs": {
+        "fs_type": "configfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "mounts": [
+          "/sys/kernel/config"
+        ]
+      },
+      "lxcfs": {
+        "fs_type": "fuse.lxcfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "user_id=0",
+          "group_id=0",
+          "allow_other"
+        ],
+        "mounts": [
+          "/var/lib/lxcfs"
+        ]
+      },
+      "/dev/sda": {
+      }
+    },
+    "by_mountpoint": {
+      "/dev": {
+        "kb_size": "1886700",
+        "kb_used": "0",
+        "kb_available": "1886700",
+        "percent_used": "0%",
+        "total_inodes": "471675",
+        "inodes_used": "371",
+        "inodes_available": "471304",
+        "inodes_percent_used": "1%",
+        "fs_type": "devtmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "relatime",
+          "size=1886700k",
+          "nr_inodes=471675",
+          "mode=755"
+        ],
+        "devices": [
+          "udev"
+        ]
+      },
+      "/run": {
+        "kb_size": "378828",
+        "kb_used": "5292",
+        "kb_available": "373536",
+        "percent_used": "2%",
+        "total_inodes": "473531",
+        "inodes_used": "460",
+        "inodes_available": "473071",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "size=378828k",
+          "mode=755"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/": {
+        "kb_size": "10098468",
+        "kb_used": "1401956",
+        "kb_available": "8680128",
+        "percent_used": "14%",
+        "total_inodes": "1280000",
+        "inodes_used": "90038",
+        "inodes_available": "1189962",
+        "inodes_percent_used": "8%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
+        "label": "cloudimg-rootfs",
+        "devices": [
+          "/dev/sda1"
+        ]
+      },
+      "/dev/shm": {
+        "kb_size": "1894124",
+        "kb_used": "0",
+        "kb_available": "1894124",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "1",
+        "inodes_available": "473530",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/run/lock": {
+        "kb_size": "5120",
+        "kb_used": "0",
+        "kb_available": "5120",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "8",
+        "inodes_available": "473523",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "size=5120k"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/sys/fs/cgroup": {
+        "kb_size": "1894124",
+        "kb_used": "0",
+        "kb_available": "1894124",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "16",
+        "inodes_available": "473515",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "mode=755"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/run/user/1001": {
+        "kb_size": "378828",
+        "kb_used": "0",
+        "kb_available": "378828",
+        "percent_used": "0%",
+        "total_inodes": "473531",
+        "inodes_used": "4",
+        "inodes_available": "473527",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "size=378828k",
+          "mode=700",
+          "uid=1001",
+          "gid=1002"
+        ],
+        "devices": [
+          "tmpfs"
+        ]
+      },
+      "/sys": {
+        "fs_type": "sysfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "sysfs"
+        ]
+      },
+      "/proc": {
+        "fs_type": "proc",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "proc"
+        ]
+      },
+      "/dev/pts": {
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=000"
+        ],
+        "devices": [
+          "devpts"
+        ]
+      },
+      "/sys/kernel/security": {
+        "fs_type": "securityfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "securityfs"
+        ]
+      },
+      "/sys/fs/cgroup/systemd": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "xattr",
+          "release_agent=/lib/systemd/systemd-cgroups-agent",
+          "name=systemd"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/pstore": {
+        "fs_type": "pstore",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ],
+        "devices": [
+          "pstore"
+        ]
+      },
+      "/sys/fs/cgroup/perf_event": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "perf_event"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/blkio": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "blkio"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/devices": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "devices"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/net_cls,net_prio": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "net_cls",
+          "net_prio"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/cpuset": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpuset"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/cpu,cpuacct": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpu",
+          "cpuacct"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/memory": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "memory"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/freezer": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "freezer"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/pids": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "pids"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/sys/fs/cgroup/hugetlb": {
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "hugetlb"
+        ],
+        "devices": [
+          "cgroup"
+        ]
+      },
+      "/dev/hugepages": {
+        "fs_type": "hugetlbfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "hugetlbfs"
+        ]
+      },
+      "/dev/mqueue": {
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "mqueue"
+        ]
+      },
+      "/proc/sys/fs/binfmt_misc": {
+        "fs_type": "autofs",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "fd=33",
+          "pgrp=1",
+          "timeout=0",
+          "minproto=5",
+          "maxproto=5",
+          "direct",
+          "pipe_ino=10449"
+        ],
+        "devices": [
+          "systemd-1"
+        ]
+      },
+      "/sys/kernel/debug": {
+        "fs_type": "debugfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "debugfs"
+        ]
+      },
+      "/sys/fs/fuse/connections": {
+        "fs_type": "fusectl",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "fusectl"
+        ]
+      },
+      "/sys/kernel/config": {
+        "fs_type": "configfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ],
+        "devices": [
+          "configfs"
+        ]
+      },
+      "/var/lib/lxcfs": {
+        "fs_type": "fuse.lxcfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "user_id=0",
+          "group_id=0",
+          "allow_other"
+        ],
+        "devices": [
+          "lxcfs"
+        ]
+      }
+    },
+    "by_pair": {
+      "udev,/dev": {
+        "device": "udev",
+        "kb_size": "1886700",
+        "kb_used": "0",
+        "kb_available": "1886700",
+        "percent_used": "0%",
+        "mount": "/dev",
+        "total_inodes": "471675",
+        "inodes_used": "371",
+        "inodes_available": "471304",
+        "inodes_percent_used": "1%",
+        "fs_type": "devtmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "relatime",
+          "size=1886700k",
+          "nr_inodes=471675",
+          "mode=755"
+        ]
+      },
+      "tmpfs,/run": {
+        "device": "tmpfs",
+        "kb_size": "378828",
+        "kb_used": "5292",
+        "kb_available": "373536",
+        "percent_used": "2%",
+        "mount": "/run",
+        "total_inodes": "473531",
+        "inodes_used": "460",
+        "inodes_available": "473071",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "size=378828k",
+          "mode=755"
+        ]
+      },
+      "/dev/sda1,/": {
+        "device": "/dev/sda1",
+        "kb_size": "10098468",
+        "kb_used": "1401956",
+        "kb_available": "8680128",
+        "percent_used": "14%",
+        "mount": "/",
+        "total_inodes": "1280000",
+        "inodes_used": "90038",
+        "inodes_available": "1189962",
+        "inodes_percent_used": "8%",
+        "fs_type": "ext4",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "data=ordered"
+        ],
+        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
+        "label": "cloudimg-rootfs"
+      },
+      "tmpfs,/dev/shm": {
+        "device": "tmpfs",
+        "kb_size": "1894124",
+        "kb_used": "0",
+        "kb_available": "1894124",
+        "percent_used": "0%",
+        "mount": "/dev/shm",
+        "total_inodes": "473531",
+        "inodes_used": "1",
+        "inodes_available": "473530",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev"
+        ]
+      },
+      "tmpfs,/run/lock": {
+        "device": "tmpfs",
+        "kb_size": "5120",
+        "kb_used": "0",
+        "kb_available": "5120",
+        "percent_used": "0%",
+        "mount": "/run/lock",
+        "total_inodes": "473531",
+        "inodes_used": "8",
+        "inodes_available": "473523",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "size=5120k"
+        ]
+      },
+      "tmpfs,/sys/fs/cgroup": {
+        "device": "tmpfs",
+        "kb_size": "1894124",
+        "kb_used": "0",
+        "kb_available": "1894124",
+        "percent_used": "0%",
+        "mount": "/sys/fs/cgroup",
+        "total_inodes": "473531",
+        "inodes_used": "16",
+        "inodes_available": "473515",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "ro",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "mode=755"
+        ]
+      },
+      "tmpfs,/run/user/1001": {
+        "device": "tmpfs",
+        "kb_size": "378828",
+        "kb_used": "0",
+        "kb_available": "378828",
+        "percent_used": "0%",
+        "mount": "/run/user/1001",
+        "total_inodes": "473531",
+        "inodes_used": "4",
+        "inodes_available": "473527",
+        "inodes_percent_used": "1%",
+        "fs_type": "tmpfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "size=378828k",
+          "mode=700",
+          "uid=1001",
+          "gid=1002"
+        ]
+      },
+      "sysfs,/sys": {
+        "device": "sysfs",
+        "mount": "/sys",
+        "fs_type": "sysfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "proc,/proc": {
+        "device": "proc",
+        "mount": "/proc",
+        "fs_type": "proc",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "devpts,/dev/pts": {
+        "device": "devpts",
+        "mount": "/dev/pts",
+        "fs_type": "devpts",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "noexec",
+          "relatime",
+          "gid=5",
+          "mode=620",
+          "ptmxmode=000"
+        ]
+      },
+      "securityfs,/sys/kernel/security": {
+        "device": "securityfs",
+        "mount": "/sys/kernel/security",
+        "fs_type": "securityfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/systemd": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/systemd",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "xattr",
+          "release_agent=/lib/systemd/systemd-cgroups-agent",
+          "name=systemd"
+        ]
+      },
+      "pstore,/sys/fs/pstore": {
+        "device": "pstore",
+        "mount": "/sys/fs/pstore",
+        "fs_type": "pstore",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/perf_event": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/perf_event",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "perf_event"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/blkio": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/blkio",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "blkio"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/devices": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/devices",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "devices"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/net_cls,net_prio": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/net_cls,net_prio",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "net_cls",
+          "net_prio"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/cpuset": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/cpuset",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpuset"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/cpu,cpuacct": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/cpu,cpuacct",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "cpu",
+          "cpuacct"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/memory": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/memory",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "memory"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/freezer": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/freezer",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "freezer"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/pids": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/pids",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "pids"
+        ]
+      },
+      "cgroup,/sys/fs/cgroup/hugetlb": {
+        "device": "cgroup",
+        "mount": "/sys/fs/cgroup/hugetlb",
+        "fs_type": "cgroup",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "noexec",
+          "relatime",
+          "hugetlb"
+        ]
+      },
+      "hugetlbfs,/dev/hugepages": {
+        "device": "hugetlbfs",
+        "mount": "/dev/hugepages",
+        "fs_type": "hugetlbfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "mqueue,/dev/mqueue": {
+        "device": "mqueue",
+        "mount": "/dev/mqueue",
+        "fs_type": "mqueue",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "systemd-1,/proc/sys/fs/binfmt_misc": {
+        "device": "systemd-1",
+        "mount": "/proc/sys/fs/binfmt_misc",
+        "fs_type": "autofs",
+        "mount_options": [
+          "rw",
+          "relatime",
+          "fd=33",
+          "pgrp=1",
+          "timeout=0",
+          "minproto=5",
+          "maxproto=5",
+          "direct",
+          "pipe_ino=10449"
+        ]
+      },
+      "debugfs,/sys/kernel/debug": {
+        "device": "debugfs",
+        "mount": "/sys/kernel/debug",
+        "fs_type": "debugfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "fusectl,/sys/fs/fuse/connections": {
+        "device": "fusectl",
+        "mount": "/sys/fs/fuse/connections",
+        "fs_type": "fusectl",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "configfs,/sys/kernel/config": {
+        "device": "configfs",
+        "mount": "/sys/kernel/config",
+        "fs_type": "configfs",
+        "mount_options": [
+          "rw",
+          "relatime"
+        ]
+      },
+      "lxcfs,/var/lib/lxcfs": {
+        "device": "lxcfs",
+        "mount": "/var/lib/lxcfs",
+        "fs_type": "fuse.lxcfs",
+        "mount_options": [
+          "rw",
+          "nosuid",
+          "nodev",
+          "relatime",
+          "user_id=0",
+          "group_id=0",
+          "allow_other"
+        ]
+      },
+      "/dev/sda,": {
+        "device": "/dev/sda"
+      }
+    }
+  },
+  "dmi": {
+    "dmidecode_version": "3.0"
+  },
+  "init_package": "systemd",
+  "ohai_time": 1496219099.5081818,
+  "command": {
+    "ps": "ps -ef"
+  },
+  "root_group": "root",
+  "languages": {
+    "ruby": {
+      "platform": "x86_64-linux-gnu",
+      "version": "2.3.1",
+      "release_date": "2016-04-26",
+      "target": "x86_64-pc-linux-gnu",
+      "target_cpu": "x86_64",
+      "target_vendor": "pc",
+      "target_os": "linux-gnu",
+      "host": "x86_64-pc-linux-gnu",
+      "host_cpu": "x86_64",
+      "host_os": "linux-gnu",
+      "host_vendor": "pc",
+      "bin_dir": "/usr/local/bin",
+      "ruby_bin": "/usr/local/bin/ruby",
+      "gems_dir": "/usr/local/gems",
+      "gem_bin": "/usr/local/bin/gem"
+    },
+    "powershell": null
+  },
+  "chef_packages": {
+    "chef": {
+      "version": "13.1.31",
+      "chef_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-13.1.31/lib"
+    },
+    "ohai": {
+      "version": "13.1.0",
+      "ohai_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/ohai-13.1.0/lib/ohai"
+    }
+  },
+  "counters": {
+    "network": {
+      "interfaces": {
+        "eth0": {
+          "rx": {
+            "bytes": "0",
+            "packets": "0",
+            "errors": "0",
+            "drop": 0,
+            "overrun": 0,
+            "frame": 0,
+            "compressed": 0,
+            "multicast": 0
+          },
+          "tx": {
+            "bytes": "342",
+            "packets": "0",
+            "errors": "0",
+            "drop": 0,
+            "overrun": 0,
+            "collisions": "0",
+            "carrier": 0,
+            "compressed": 0
+          }
+        }
+      }
+    }
+  },
+  "current_user": "fauxhai",
+  "domain": "local",
+  "etc": {
+    "passwd": {
+      "fauxhai": {
+        "dir": "/home/fauxhai",
+        "gid": 0,
+        "uid": 0,
+        "shell": "/bin/bash",
+        "gecos": "Fauxhai"
+      }
+    },
+    "group": {
+      "fauxhai": {
+        "gid": 0,
+        "members": [
+          "fauxhai"
+        ]
+      }
+    }
+  },
+  "hostname": "Fauxhai",
+  "fqdn": "fauxhai.local",
+  "ipaddress": "10.0.0.2",
+  "keys": {
+    "ssh": {
+      "host_dsa_public": "ssh-dss AAAAB3NzaC1kc3MAAACBAJFo9BLAw4WKEs5hgipk5m423FzBsDXCZSMcC9ca/om/1VYzMqImixGe3uICDzNFUWxFoLJTQAOccyzo6MXZiQqwWJDLFi5qOSr6w2XcMyE+zd4wOyMoDiVM5fizmG8K3FzrqvGjwBcHcBdOQnavSijoj38DN25J9zhrid5BY4WlAAAAFQDxXrCyG52XCzn3FV4ej38wJBkomQAAAIBovGPJ4mP2P6BK8lHl0PPbktwQbWlpJ13oz6REJFDVcUi7vV26bX/BjQX+ohzZQzljdz1SpUbPc/8nuA4darYkVh91eBi307EN8IdxRHj2eBgp/ZG4yshIebG3WHrwJD/xUjjZ1MRfyDT1ermVi4LvjjPgWDxLZnPpMaR6S1nzgQAAAIEAj0Vd6DCWslvlsZ8+N53HWsqPi3gnx35JoLPz9Z2epkKIKqmEHav+93G3hdfztVa4I4t3phoPniQchYryF5+RNg8hqxKzjNtrIqUYCeuf2NJrksNsH7OZygPHZpqt4kTuwAGZxjxEGfAI0y8DhkU2ntp2LnzRnWH106BQBCmcXwo= fauxhai.local",
+      "host_rsa_public": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtLCeqtqr/HbnORckw1ukdLhpfGoOPFi5/esKEokzTqq1gsgQ2V8emmyjfq1i6XXfRtSBxkdlHv/GWdP5wBTuE2G85MzBkVSQPvmwQN8lX/JMPEEtKXkeOo0o92/PiSmvY4eRsdF0mw40Uvg7jtE3f3fxj497kzh5fKtkrHnF4x9gXCbVdr3FqXJfggR5IJwAxToerbK7x/uRS+7YuZI9Pip3tt14nv9ezwXcuGb/tvjWOZINiFl8izVIFKi7sxfTX09p4NgamxRS7TD2Yd0jT8nEoF9UZTsgXcJ1kDSx7N7NxFfNfP6rCdOGRRz4gUhXtsUjG/XkxPeCwZ7A9VnOD fauxhai.local"
+    }
+  },
+  "macaddress": "11:11:11:11:11:11",
+  "network": {
+    "default_gateway": "10.0.0.1",
+    "default_interface": "eth0",
+    "settings": {
+    },
+    "interfaces": {
+      "eth0": {
+        "addresses": {
+          "10.0.0.2": {
+            "broadcast": "10.0.0.255",
+            "family": "inet",
+            "netmask": "255.255.255.0",
+            "prefixlen": "24",
+            "scope": "Global"
+          }
+        },
+        "arp": {
+          "10.0.0.1": "fe:ff:ff:ff:ff:ff"
+        },
+        "encapsulation": "Ethernet",
+        "flags": [
+          "BROADCAST",
+          "MULTICAST",
+          "UP",
+          "LOWER_UP"
+        ],
+        "mtu": "1500",
+        "number": "0",
+        "routes": [
+          {
+            "destination": "10.0.0.0/24",
+            "family": "inet",
+            "scope": "link",
+            "proto": "kernel",
+            "src": "10.0.0.2"
+          }
+        ],
+        "state": "up",
+        "type": "eth"
+      }
+    }
+  },
+  "uptime": "30 days 15 hours 07 minutes 30 seconds",
+  "uptime_seconds": 2646450,
+  "cpu": {
+    "real": 1,
+    "total": 1,
+    "cores": 1
+  },
+  "memory": {
+    "total": "1048576kB"
+  },
+  "virtualization": {
+    "systems": {
+    }
+  }
+}
diff --git a/spec/chef/fixtures/migration/failed-migration-status-file b/spec/chef/fixtures/migration/failed-migration-status-file
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL21pZ3JhdGlvbi9mYWlsZWQtbWlncmF0aW9uLXN0YXR1cy1maWxl
--- /dev/null
+++ b/spec/chef/fixtures/migration/failed-migration-status-file
@@ -0,0 +1,1 @@
+1
diff --git a/spec/chef/fixtures/migration/successful-migration-status-file b/spec/chef/fixtures/migration/successful-migration-status-file
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2ZpeHR1cmVzL21pZ3JhdGlvbi9zdWNjZXNzZnVsLW1pZ3JhdGlvbi1zdGF0dXMtZmlsZQ==
--- /dev/null
+++ b/spec/chef/fixtures/migration/successful-migration-status-file
@@ -0,0 +1,1 @@
+0
diff --git a/spec/chef/gitlab-ctl-commands-ee/geo_replication_spec.rb b/spec/chef/gitlab-ctl-commands-ee/geo_replication_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvZ2VvX3JlcGxpY2F0aW9uX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/geo_replication_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+require 'omnibus-ctl'
+
+RSpec.describe 'gitlab-ctl geo-replication' do
+  let(:toggle_command) { double(Geo::ReplicationToggleCommand) }
+
+  subject { Omnibus::Ctl.new('testing-ctl') }
+
+  before do
+    allow_any_instance_of(Omnibus::Ctl).to receive(:require).and_call_original
+    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
+      '/opt/testing-ctl/embedded/service/omnibus-ctl-ee/lib/geo/replication_process'
+    ) do
+      require_relative('../../../files/gitlab-ctl-commands-ee/lib/geo/replication_process')
+    end
+    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
+      '/opt/testing-ctl/embedded/service/omnibus-ctl-ee/lib/geo/replication_toggle_command'
+    ) do
+      require_relative('../../../files/gitlab-ctl-commands-ee/lib/geo/replication_toggle_command')
+    end
+
+    subject.load_file('files/gitlab-ctl-commands-ee/geo_replication.rb')
+  end
+
+  it 'appends the geo replication pause and resume commands' do
+    expect(subject.get_all_commands_hash).to include('geo-replication-pause')
+    expect(subject.get_all_commands_hash).to include('geo-replication-resume')
+  end
+
+  describe 'pause' do
+    it 'calls pause' do
+      expect(Geo::ReplicationToggleCommand).to receive(:new).with(anything, 'pause', anything).and_return(toggle_command)
+      expect(toggle_command).to receive(:execute!)
+
+      subject.geo_replication_pause
+    end
+  end
+
+  describe 'resume' do
+    it 'calls resume' do
+      expect(Geo::ReplicationToggleCommand).to receive(:new).with(anything, 'resume', anything).and_return(toggle_command)
+      expect(toggle_command).to receive(:execute!)
+
+      subject.geo_replication_resume
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/geo_spec.rb b/spec/chef/gitlab-ctl-commands-ee/geo_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvZ2VvX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/geo_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'omnibus-ctl'
+require 'optparse'
+
+require_relative('../../../files/gitlab-ctl-commands/lib/gitlab_ctl')
+require_relative('../../../files/gitlab-ctl-commands-ee/lib/geo')
+
+RSpec.describe 'gitlab-ctl geo' do
+  commands = %w(promote)
+
+  command_lines = {
+    'promote' => []
+  }
+
+  command_options = {
+    'promote' => {}
+  }
+
+  describe '.parse_options' do
+    before do
+      allow(Geo::Utils).to receive(:warn_and_exit).and_call_original
+      allow(Kernel).to receive(:exit) { |code| raise "Kernel.exit(#{code})" }
+      allow(Kernel).to receive(:warn)
+    end
+
+    it 'throws an error when global options are invalid' do
+      expect { Geo.parse_options(%w(geo --foo)) }.to raise_error(OptionParser::ParseError)
+    end
+
+    it 'throws an error when sub-command is not specified' do
+      expect { Geo.parse_options(%w(geo -v)) }.to raise_error(OptionParser::ParseError)
+    end
+
+    it 'throws an error when sub-command is not defined' do
+      expect { Geo.parse_options(%w(geo -v foo)) }.to raise_error(OptionParser::ParseError)
+    end
+
+    it 'recognizes global options' do
+      expect(Geo.parse_options(%w(geo -v -q promote))).to include(quiet: true, verbose: true)
+    end
+
+    context 'when sub-command is passed' do
+      commands.each do |cmd|
+        it "parses #{cmd} options" do
+          cmd_line = command_lines[cmd] || []
+          cmd_opts = command_options[cmd] || {}
+          cmd_opts[:command] = cmd
+
+          expect(Geo.parse_options(%W(geo #{cmd}) + cmd_line)).to include(cmd_opts)
+        end
+      end
+    end
+
+    context 'when help option is passed' do
+      it 'shows help message and exit for global help option' do
+        expect { Geo.parse_options(%w(geo -h)) }.to raise_error('Kernel.exit(0)')
+        expect(Geo::Utils).to have_received(:warn_and_exit).with(/Usage help/)
+      end
+
+      commands.each do |cmd|
+        it "shows help message and exit for #{cmd} help option" do
+          expect { Geo.parse_options(%W(geo #{cmd} -h)) }.to raise_error('Kernel.exit(0)')
+          expect(Geo::Utils).to have_received(:warn_and_exit).with(instance_of(OptionParser))
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/consul_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/consul_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL2NvbnN1bF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/consul_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands-ee/lib')
+
+require 'consul'
+
+RSpec.describe ConsulHandler do
+  describe '#initialize' do
+    it 'creates instance based on args' do
+      instance = ConsulHandler.new([nil, nil, 'consul', 'kv', 'set'], 'rspec')
+      expect(instance.command).to eq(ConsulHandler::Kv)
+      expect(instance.subcommand).to eq('set')
+      expect(instance.input).to eq('rspec')
+    end
+  end
+
+  describe '#execute' do
+    it 'calls the method on command' do
+      instance = ConsulHandler.new([nil, nil, 'consul', 'kv', 'set'], 'rspec')
+      instance.command = spy
+      expect(instance.command).to receive(:set).with('rspec')
+      instance.execute
+    end
+  end
+
+  let(:consul_cmd) { '/opt/gitlab/embedded/bin/consul' }
+
+  describe ConsulHandler::Kv do
+    it 'allows nil values' do
+      results = double('results', run_command: [], error!: nil, stdout: '')
+      expect(Mixlib::ShellOut).to receive(:new).with("#{consul_cmd} kv put foo ").and_return(results)
+      described_class.send(:put, 'foo')
+    end
+
+    describe '#get' do
+      context 'existing key' do
+        before do
+          results = double('results', run_command: [], error!: nil, stdout: 'bar')
+          allow(Mixlib::ShellOut).to receive(:new).with("#{consul_cmd} kv get foo").and_return(results)
+        end
+
+        it 'returns the expected key' do
+          expect(described_class.get('foo')).to eq('bar')
+        end
+      end
+
+      context 'non-existing key' do
+        before do
+          results = double('results', run_command: [], stdout: '', stderr: 'oops')
+          allow(results).to receive(:error!).and_raise(StandardError)
+          allow(Mixlib::ShellOut).to receive(:new).with("#{consul_cmd} kv get foo").and_return(results)
+        end
+
+        it 'raises an error' do
+          expect { described_class.get('foo') }.to raise_error(ConsulHandler::ConsulError, 'StandardError: oops')
+        end
+      end
+    end
+  end
+
+  describe ConsulHandler::Encrypt do
+    describe '#keygen' do
+      it 'returns the generated key' do
+        generated_key = 'BYX6EPaUiUI0TDdm6gAMmmLnpJSwePJ33Xwh6rjCYbg='
+        results = double('results', run_command: [], error!: nil, stdout: generated_key)
+        allow(Mixlib::ShellOut).to receive(:new).with("#{consul_cmd} keygen").and_return(results)
+
+        expect(described_class.keygen).to eq(generated_key)
+      end
+
+      it 'raises an error' do
+        results = double('results', run_command: [], stdout: '', stderr: 'oops')
+        allow(results).to receive(:error!).and_raise(StandardError)
+        allow(Mixlib::ShellOut).to receive(:new).with("#{consul_cmd} keygen").and_return(results)
+
+        expect { described_class.keygen }.to raise_error(ConsulHandler::ConsulError, 'StandardError: oops')
+      end
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/geo/pitr_file_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/geo/pitr_file_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL2dlby9waXRyX2ZpbGVfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/geo/pitr_file_spec.rb
@@ -0,0 +1,105 @@
+require 'spec_helper'
+require 'geo/pitr_file'
+
+RSpec.describe Geo::PitrFile do
+  let(:lsn) { 'imafakelsn' }
+  let(:filepath) { '/fake/file/path' }
+  subject { described_class.new(filepath, consul_key: 'fake-key') }
+
+  context 'with a local file' do
+    before do
+      allow(Dir).to receive(:exist?).with('/opt/gitlab/service/consul').and_return(false)
+    end
+
+    it "doesn't use consul" do
+      expect(subject.use_consul?).to be(false)
+    end
+
+    context '#create' do
+      it 'creates the pitr file' do
+        expect(File).to receive(:write).with(filepath, lsn)
+        subject.create(lsn)
+      end
+    end
+
+    context '#delete' do
+      it 'removes the pitr file' do
+        allow(File).to receive(:exist?).with(filepath).and_return(true)
+        expect(File).to receive(:delete).with(filepath)
+
+        subject.delete
+      end
+
+      it 'does not raise an error if the file does not exist' do
+        allow(File).to receive(:exist?).with(filepath).and_return(false)
+        allow(File).to receive(:delete).with(filepath).and_raise(Errno::ENOENT)
+
+        expect { subject.delete }.to_not raise_error(Geo::PitrFileError, "Unable to delete PITR")
+      end
+    end
+
+    context '#get' do
+      before do
+        allow(File).to receive(:read).with(filepath).and_return(lsn)
+      end
+
+      it 'returns the correct lsn' do
+        expect(subject.get).to eq(lsn)
+      end
+
+      it 'raises an error if the file does not exist' do
+        allow(File).to receive(:read).with(filepath).and_raise(Errno::ENOENT)
+
+        expect { subject.get }.to raise_error(Geo::PitrFileError, "Unable to fetch PITR")
+      end
+    end
+  end
+
+  context 'when consul is enabled' do
+    before do
+      allow(Dir).to receive(:exist?).with('/opt/gitlab/service/consul').and_return(true)
+    end
+
+    it "uses consul" do
+      expect(subject.use_consul?).to be(true)
+    end
+
+    context '#create' do
+      it 'create the write key entry' do
+        expect(ConsulHandler::Kv).to receive(:put).with(subject.consul_key, lsn)
+
+        subject.create(lsn)
+      end
+    end
+
+    context '#delete' do
+      it 'removes the consul key' do
+        expect(ConsulHandler::Kv).to receive(:delete).with(subject.consul_key)
+
+        subject.delete
+      end
+
+      it 'raises an error if the key does not exist' do
+        expect { subject.delete }.to raise_error(Geo::PitrFileError, 'Unable to delete PITR')
+      end
+    end
+
+    context '#get' do
+      context 'existing key' do
+        before do
+          allow(ConsulHandler::Kv).to receive(:get).with(subject.consul_key).and_return(lsn)
+        end
+
+        it 'returns the correct lsn' do
+          expect(subject.get).to eq(lsn)
+        end
+      end
+
+      context 'non-existing key' do
+        it 'raises an error if the key does not exist' do
+          expect { subject.get }.to raise_error(Geo::PitrFileError, 'Unable to fetch PITR')
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/geo/promote_db_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/geo/promote_db_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL2dlby9wcm9tb3RlX2RiX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/geo/promote_db_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+require 'geo/promote_db'
+require 'gitlab_ctl/util'
+
+RSpec.describe Geo::PromoteDb, '#execute' do
+  let(:instance) { double(base_path: '/opt/gitlab/embedded') }
+
+  subject(:command) { described_class.new(instance) }
+
+  before do
+    allow($stdout).to receive(:puts)
+    allow($stdout).to receive(:print)
+
+    allow(command).to receive(:run_command).with(any_args)
+
+    allow(command).to receive(:run_command).and_return(double('error!' => nil))
+  end
+
+  context 'when PITR file does not exist' do
+    it 'does not run PITR recovery' do
+      expect(command).not_to receive(:write_recovery_settings)
+
+      command.execute
+    end
+  end
+
+  context 'postgres dir is in non-default location' do
+    it 'uses the specified postgres directory when writing ' do
+      expected_dir = '/non/default/location'
+      allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'postgresql' => { 'dir' => expected_dir } })
+
+      expect(described_class.new(instance).postgresql_dir_path).to eq(expected_dir)
+    end
+  end
+
+  context 'when PITR file exists' do
+    let(:lsn) { '16/B374D848' }
+
+    before do
+      allow(command).to receive(:lsn_from_pitr_file).and_return(lsn)
+      allow(command).to receive(:write_geo_config_file).and_return(true)
+    end
+
+    it 'writes out the recovery settings' do
+      expect(command).to receive(:write_recovery_settings).with(lsn)
+
+      expect { command.execute }.to output(
+        /Recovery to point #{lsn} and promoting.../).to_stdout
+    end
+
+    it 'writes out the geo configuration file' do
+      expect(command).to receive(:write_geo_config_file).once
+
+      command.execute
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/geo/promote_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/geo/promote_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL2dlby9wcm9tb3RlX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/geo/promote_spec.rb
@@ -0,0 +1,499 @@
+require 'spec_helper'
+require 'geo/promote'
+require 'geo/promote_db'
+require 'gitlab_ctl/util'
+
+require_relative('../../../../../files/gitlab-ctl-commands-ee/lib/patroni')
+
+RSpec.describe Geo::Promote, '#execute' do
+  let(:base_path) { '/opt/gitlab/embedded' }
+  let(:ctl) { double(base_path: base_path, data_path: '/var/opt/gitlab/postgresql/data') }
+  let(:config_path) { Dir.mktmpdir }
+  let(:gitlab_cluster_config_path) { File.join(config_path, 'gitlab-cluster.json') }
+  let(:options) { { force: false } }
+
+  subject(:command) { described_class.new(ctl, options) }
+
+  before do
+    stub_const('GitlabCluster::CONFIG_PATH', config_path)
+    stub_const('GitlabCluster::JSON_FILE', gitlab_cluster_config_path)
+
+    allow($stdin).to receive(:gets).and_return('y')
+    allow($stdout).to receive(:puts)
+    allow($stdout).to receive(:print)
+
+    allow(ctl).to receive(:log).with(any_args)
+    allow(ctl).to receive(:service_enabled?).and_return(false)
+    allow(command).to receive(:run_command).with(any_args)
+  end
+
+  around do |example|
+    example.run
+  rescue SystemExit
+  end
+
+  after do
+    FileUtils.rm_rf(config_path)
+  end
+
+  describe '#execute' do
+    context 'when not running in force mode' do
+      it 'asks for confirmation' do
+        expect { command.execute }.to output(/Are you sure you want to proceed\?/).to_stdout
+      end
+    end
+
+    context 'when running in force mode' do
+      let(:options) { { force: true } }
+
+      it 'does not ask for final confirmation' do
+        expect { command.execute }.to_not output(/Are you sure you want to proceed\?/).to_stdout
+      end
+    end
+
+    context 'when there are no services to promote' do
+      before do
+        allow(ctl).to receive(:service_enabled?).and_return(false)
+      end
+
+      it 'prints a message' do
+        expect { command.execute }.to output(
+          /No actions are required to promote this node./).to_stdout
+      end
+
+      it 'returns 0 as exit code' do
+        expect { command.execute }.to raise_error(SystemExit) do |error|
+          expect(error.status).to eq(0)
+        end
+      end
+    end
+
+    context 'when puma is enabled' do
+      before do
+        stub_service_enabled('puma')
+      end
+
+      shared_examples 'promote secondary site to primary site' do
+        context 'on a primary node' do
+          it 'does not try to promote the secondary site to primary site' do
+            stub_primary_node
+
+            allow(command).to receive(:run_reconfigure)
+            allow(command).to receive(:restart_services)
+
+            expect(command).not_to receive(:run_command).with("#{base_path}/bin/gitlab-rake geo:set_secondary_as_primary", live: true)
+
+            command.execute
+          end
+        end
+
+        context 'on a secondary node' do
+          before do
+            stub_secondary_node
+            allow(command).to receive(:run_reconfigure)
+          end
+
+          it 'promotes the secondary site to primary site' do
+            allow(command).to receive(:restart_services)
+
+            expect(command).to receive(:run_command).with("#{base_path}/bin/gitlab-rake geo:set_secondary_as_primary", live: true).once.and_return(double(error?: false))
+
+            command.execute
+          end
+
+          it 'restarts the puma service' do
+            allow(command).to receive(:promote_to_primary)
+
+            expect(ctl).to receive(:run_sv_command_for_service).with('restart', 'puma').once.and_return(double(zero?: true))
+
+            command.execute
+          end
+        end
+
+        context 'on a misconfigured node' do
+          it 'aborts promotion with an error message' do
+            stub_misconfigured_node
+
+            expect { command.execute }.to output(/Unable to detect the role of this Geo node/).to_stdout
+          end
+        end
+      end
+
+      context 'on a single-server secondary site' do
+        before do
+          stub_single_server_secondary_site
+        end
+
+        it 'toggles the Geo primary/secondary roles' do
+          allow(command).to receive(:run_reconfigure)
+          allow(command).to receive(:promote_to_primary)
+          allow(command).to receive(:restart_services)
+
+          command.execute
+
+          expect(read_file_content(gitlab_cluster_config_path)).to eq("primary" => true, "secondary" => false)
+        end
+
+        it 'runs reconfigure' do
+          allow(command).to receive(:toggle_geo_services)
+          allow(command).to receive(:promote_to_primary)
+          allow(command).to receive(:restart_services)
+
+          expect(ctl).to receive(:run_chef).with(reconfigure_cmd).once.and_return(double(success?: true))
+
+          command.execute
+        end
+
+        include_examples 'promote secondary site to primary site'
+      end
+
+      context 'on a multiple-server secondary site' do
+        before do
+          stub_multiple_server_secondary_site
+        end
+
+        include_examples 'promote secondary site to primary site'
+
+        it 'disables Geo secondary settings' do
+          allow(command).to receive(:run_reconfigure)
+          allow(command).to receive(:promote_to_primary)
+          allow(command).to receive(:restart_services)
+
+          command.execute
+
+          expect(read_file_content(gitlab_cluster_config_path)).to eq("geo_secondary" => { "enable" => false })
+        end
+
+        it 'runs reconfigure' do
+          allow(command).to receive(:promote_to_primary)
+          allow(command).to receive(:restart_services)
+
+          expect(ctl).to receive(:run_chef).with(reconfigure_cmd).once.and_return(double(success?: true))
+
+          command.execute
+        end
+      end
+    end
+
+    context 'when gitaly is enabled' do
+      before do
+        stub_service_enabled('gitaly')
+      end
+
+      it 'restarts the gitaly service' do
+        allow(command).to receive(:toggle_geo_services)
+        allow(command).to receive(:promote_to_primary)
+        allow(command).to receive(:run_reconfigure)
+
+        expect(ctl).to receive(:run_sv_command_for_service).with('restart', 'gitaly').once.and_return(double(zero?: true))
+
+        command.execute
+      end
+    end
+
+    context 'when praefect is enabled' do
+      before do
+        stub_service_enabled('praefect')
+      end
+
+      it 'restarts the praefect service' do
+        allow(command).to receive(:toggle_geo_services)
+        allow(command).to receive(:promote_to_primary)
+        allow(command).to receive(:run_reconfigure)
+
+        expect(ctl).to receive(:run_sv_command_for_service).with('restart', 'praefect').once.and_return(double(zero?: true))
+
+        command.execute
+      end
+    end
+
+    context 'when workhorse is enabled' do
+      before do
+        stub_service_enabled('gitlab-workhorse')
+      end
+
+      it 'restarts the workhorse service' do
+        allow(command).to receive(:toggle_geo_services)
+        allow(command).to receive(:promote_to_primary)
+        allow(command).to receive(:run_reconfigure)
+
+        expect(ctl).to receive(:run_sv_command_for_service).with('restart', 'gitlab-workhorse').once.and_return(double(zero?: true))
+
+        command.execute
+      end
+    end
+
+    shared_examples 'single-server secondary site' do
+      context 'on a single-server secondary site' do
+        before do
+          stub_single_server_secondary_site
+        end
+
+        it 'toggles the Geo primary/secondary roles' do
+          allow(command).to receive(:run_reconfigure)
+
+          command.execute
+
+          expect(read_file_content(gitlab_cluster_config_path)).to eq("primary" => true, "secondary" => false)
+        end
+
+        it 'runs reconfigure' do
+          expect(ctl).to receive(:run_chef).with(reconfigure_cmd).once.and_return(double(success?: true))
+
+          command.execute
+        end
+      end
+    end
+
+    shared_examples 'multiple-server secondary site' do |settings|
+      context 'on a multiple-server secondary site' do
+        before do
+          stub_multiple_server_secondary_site
+        end
+
+        it 'disables Geo secondary settings' do
+          allow(command).to receive(:run_reconfigure)
+
+          command.execute
+
+          expect(read_file_content(gitlab_cluster_config_path)).to eq(settings)
+        end
+      end
+    end
+
+    context 'when sidekiq is enabled' do
+      before do
+        stub_service_enabled('sidekiq')
+      end
+
+      include_examples 'single-server secondary site'
+      include_examples 'multiple-server secondary site', "geo_secondary" => { "enable" => false }
+    end
+
+    context 'when geo-logcursor is enabled' do
+      before do
+        allow(ctl).to receive(:service_enabled?).with('geo-logcursor').and_return(true)
+      end
+
+      include_examples 'single-server secondary site'
+      include_examples 'multiple-server secondary site', "geo_logcursor" => { "enable" => false }
+    end
+
+    context 'when geo-postgresql is enabled' do
+      before do
+        allow(ctl).to receive(:service_enabled?).with('geo-postgresql').and_return(true)
+      end
+
+      include_examples 'single-server secondary site'
+      include_examples 'multiple-server secondary site', "geo_postgresql" => { "enable" => false }
+    end
+
+    context 'when postgresql is enabled' do
+      before do
+        stub_service_enabled('postgresql')
+      end
+
+      context 'when PostgreSQL is in recovery mode' do
+        before do
+          stub_pg_is_in_recovery
+        end
+
+        it 'promotes database to begin read-write operations' do
+          allow(command).to receive(:run_reconfigure)
+
+          expect_any_instance_of(Geo::PromoteDb).to receive(:execute).once.and_return(true)
+
+          command.execute
+        end
+      end
+
+      context 'when PostgreSQL is not in recovery mode' do
+        before do
+          stub_pg_is_not_in_recovery
+        end
+
+        it 'does not promote the PostgreSQL database' do
+          allow(command).to receive(:run_reconfigure)
+
+          expect_any_instance_of(Geo::PromoteDb).not_to receive(:execute)
+
+          command.execute
+        end
+      end
+
+      it 'runs reconfigure' do
+        allow(command).to receive(:promote_database)
+
+        expect(ctl).to receive(:run_chef).with(reconfigure_cmd).once.and_return(double(success?: true))
+
+        command.execute
+      end
+    end
+
+    context 'when patroni is enabled' do
+      before do
+        stub_service_enabled('patroni')
+      end
+
+      shared_examples 'promotes a Patroni leader' do
+        context 'when PostgreSQL is in recovery mode' do
+          before do
+            stub_pg_is_in_recovery
+          end
+
+          it 'promotes database to begin read-write operations' do
+            allow(command).to receive(:disable_patroni_standby_cluster)
+            allow(command).to receive(:run_reconfigure)
+
+            expect_any_instance_of(Geo::PromoteDb).to receive(:execute).once.and_return(true)
+
+            command.execute
+          end
+        end
+
+        context 'when PostgreSQL is not in recovery mode' do
+          before do
+            stub_pg_is_not_in_recovery
+          end
+
+          it 'does not promote the PostgreSQL database' do
+            allow(command).to receive(:disable_patroni_standby_cluster)
+            allow(command).to receive(:run_reconfigure)
+
+            expect_any_instance_of(Geo::PromoteDb).not_to receive(:execute)
+
+            command.execute
+          end
+        end
+
+        it 'disables Patroni Standby cluster settings' do
+          allow(command).to receive(:promote_postgresql_read_write)
+          allow(command).to receive(:run_reconfigure)
+
+          command.execute
+
+          expect(read_file_content(gitlab_cluster_config_path)).to eq("patroni" => { "standby_cluster" => { "enable" => false } })
+        end
+
+        it 'pauses Patroni, runs reconfigure and resume Patroni' do
+          allow(command).to receive(:promote_postgresql_read_write)
+          allow(command).to receive(:disable_patroni_standby_cluster)
+
+          expect(command).to receive(:run_command).with(patroni_pause_cmd).twice.and_return(double(success?: true))
+          expect(ctl).to receive(:run_chef).with(reconfigure_cmd).twice.and_return(double(success?: true))
+          expect(command).to receive(:run_command).with(patroni_resume_cmd).twice.and_return(double(success?: true))
+
+          command.execute
+        end
+      end
+
+      context 'on a Patroni standby leader' do
+        before do
+          allow(Patroni::Client).to receive(:new).and_return(double(standby_leader?: true, leader?: false, replica?: false))
+        end
+
+        include_examples 'promotes a Patroni leader'
+      end
+
+      context 'on a Patroni leader' do
+        before do
+          allow(Patroni::Client).to receive(:new).and_return(double(standby_leader?: false, leader?: true, replica?: false))
+        end
+
+        include_examples 'promotes a Patroni leader'
+      end
+
+      context 'on a Patroni replica' do
+        before do
+          allow(Patroni::Client).to receive(:new).and_return(double(standby_leader?: false, leader?: false, replica?: true))
+        end
+
+        it 'does not promote the PostgreSQL database' do
+          allow(command).to receive(:run_reconfigure)
+
+          expect_any_instance_of(Geo::PromoteDb).not_to receive(:execute)
+
+          command.execute
+        end
+
+        it 'disables Patroni Standby cluster settings' do
+          allow(command).to receive(:promote_postgresql_read_write)
+          allow(command).to receive(:run_reconfigure)
+
+          command.execute
+
+          expect(read_file_content(gitlab_cluster_config_path)).to eq("patroni" => { "standby_cluster" => { "enable" => false } })
+        end
+
+        it 'runs reconfigure' do
+          allow(command).to receive(:promote_postgresql_read_write)
+          allow(command).to receive(:disable_patroni_standby_cluster)
+
+          expect(command).not_to receive(:run_command).with(patroni_pause_cmd)
+          expect(ctl).to receive(:run_chef).with(reconfigure_cmd).twice.and_return(double(success?: true))
+          expect(command).not_to receive(:run_command).with(patroni_resume_cmd)
+
+          command.execute
+        end
+      end
+    end
+
+    context 'when promotion succeeds' do
+      before do
+        allow(command).to receive(:ask_for_confirmation)
+        allow(command).to receive(:check_running_services)
+        allow(command).to receive(:promote_database)
+        allow(command).to receive(:toggle_geo_services)
+        allow(command).to receive(:promote_to_primary)
+        allow(command).to receive(:run_reconfigure)
+        allow(command).to receive(:restart_services)
+      end
+
+      it 'prints a success message' do
+        expect { command.execute }.to output(
+          /You successfully promoted the current node! It might take some time to reload the services, and for the changes to take effect./).to_stdout
+      end
+    end
+
+    let(:patroni_pause_cmd) { "#{base_path}/bin/gitlab-ctl patroni pause" }
+    let(:patroni_resume_cmd) { "#{base_path}/bin/gitlab-ctl patroni resume" }
+    let(:pg_is_in_recovery_cmd) { "#{base_path}/bin/gitlab-psql -c \"SELECT pg_is_in_recovery();\" -q -t" }
+    let(:reconfigure_cmd) { "#{base_path}/embedded/cookbooks/dna.json" }
+
+    def stub_service_enabled(service)
+      allow(ctl).to receive(:service_enabled?).with(service).and_return(true)
+    end
+
+    def stub_primary_node
+      allow(command).to receive(:run_command).with("#{base_path}/bin/gitlab-rake geo:site:role", live: true).and_return(double(error?: false, stdout: 'primary'))
+    end
+
+    def stub_secondary_node
+      allow(command).to receive(:run_command).with("#{base_path}/bin/gitlab-rake geo:site:role", live: true).and_return(double(error?: false, stdout: 'secondary'))
+    end
+
+    def stub_misconfigured_node
+      allow(command).to receive(:run_command).with("#{base_path}/bin/gitlab-rake geo:site:role", live: true).and_return(double(error?: true, stdout: 'misconfigured'))
+    end
+
+    def stub_single_server_secondary_site
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return('roles' => { 'geo-secondary' => { 'enable' => true } })
+    end
+
+    def stub_multiple_server_secondary_site
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return('roles' => {})
+    end
+
+    def stub_pg_is_in_recovery
+      allow(command).to receive(:run_command).with(pg_is_in_recovery_cmd, anything).and_return(double(error?: false, stdout: 't'))
+    end
+
+    def stub_pg_is_not_in_recovery
+      allow(command).to receive(:run_command).with(pg_is_in_recovery_cmd, anything).and_return(double(error?: false, stdout: 'f'))
+    end
+
+    def read_file_content(fullpath)
+      JSON.parse(File.read(fullpath))
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/geo/promote_to_primary_node_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/geo/promote_to_primary_node_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL2dlby9wcm9tb3RlX3RvX3ByaW1hcnlfbm9kZV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/geo/promote_to_primary_node_spec.rb
@@ -0,0 +1,275 @@
+require 'spec_helper'
+require 'fileutils'
+require 'geo/promote_to_primary_node'
+require 'geo/promote_db'
+require 'geo/promotion_preflight_checks'
+require 'gitlab_ctl/util'
+
+RSpec.describe Geo::PromoteToPrimaryNode, '#execute' do
+  let(:options) { { skip_preflight_checks: true } }
+
+  let(:instance) { double(base_path: '/opt/gitlab/embedded', data_path: '/var/opt/gitlab/postgresql/data') }
+
+  subject(:command) { described_class.new(instance, options) }
+
+  let(:config_path) { Dir.mktmpdir }
+
+  before do
+    allow($stdout).to receive(:puts)
+    allow($stdout).to receive(:print)
+
+    allow(command).to receive(:run_command).with(any_args)
+  end
+
+  after do
+    FileUtils.rm_rf(config_path)
+  end
+
+  describe '#promote_postgresql_to_primary' do
+    before do
+      allow(STDIN).to receive(:gets).and_return('y')
+
+      allow(command).to receive(:toggle_geo_roles).and_return(true)
+      allow(command).to receive(:reconfigure).and_return(true)
+      allow(command).to receive(:promote_to_primary).and_return(true)
+      allow(command).to receive(:success_message).and_return(true)
+    end
+
+    it 'promotes the database' do
+      expect_any_instance_of(Geo::PromoteDb).to receive(:execute)
+
+      command.execute
+    end
+  end
+
+  describe '#run_preflight_checks' do
+    before do
+      allow(STDIN).to receive(:gets).and_return('y')
+
+      allow(command).to receive(:toggle_geo_roles).and_return(true)
+      allow(command).to receive(:promote_postgresql_to_primary).and_return(true)
+      allow(command).to receive(:reconfigure).and_return(true)
+      allow(command).to receive(:promote_to_primary).and_return(true)
+      allow(command).to receive(:success_message).and_return(true)
+    end
+
+    context 'when `--skip-preflight-checks` is passed' do
+      it 'does not run execute promotion preflight checks' do
+        expect_any_instance_of(Geo::PromotionPreflightChecks).not_to receive(:execute)
+
+        command.execute
+      end
+    end
+
+    context 'when `--skip-preflight-checks` is not passed' do
+      let(:options) { { confirm_primary_is_down: true } }
+
+      before do
+        allow_any_instance_of(Geo::PromotionPreflightChecks).to receive(
+          :execute).and_return(true)
+      end
+
+      it 'runs preflight checks' do
+        expect_any_instance_of(Geo::PromotionPreflightChecks).to receive(:execute)
+
+        command.execute
+      end
+
+      it 'passes given options to preflight checks command' do
+        expect(Geo::PromotionPreflightChecks).to receive(:new).with(
+          '/opt/gitlab/embedded', options).and_call_original
+
+        command.execute
+      end
+    end
+  end
+
+  describe '#toggle_geo_roles' do
+    let(:gitlab_cluster_config_path) { File.join(config_path, 'gitlab-cluster.json') }
+
+    before do
+      stub_const('GitlabCluster::CONFIG_PATH', config_path)
+      stub_const('GitlabCluster::JSON_FILE', gitlab_cluster_config_path)
+
+      allow(STDIN).to receive(:gets).and_return('y')
+
+      allow(command).to receive(:run_preflight_checks).and_return(true)
+      allow(command).to receive(:promote_postgresql_to_primary).and_return(true)
+      allow(command).to receive(:reconfigure).and_return(true)
+      allow(command).to receive(:promote_to_primary).and_return(true)
+      allow(command).to receive(:success_message).and_return(true)
+    end
+
+    context 'when the cluster configuration file does not exist' do
+      it 'creates the file with the Geo primary role enabled and secondary role disabled' do
+        command.execute
+
+        expect(File.exist?(gitlab_cluster_config_path)).to eq(true)
+        expect(read_file_content(gitlab_cluster_config_path)).to eq("primary" => true, "secondary" => false)
+      end
+    end
+
+    context 'when the cluster configuration file exists' do
+      it 'disables the Geo secondary role' do
+        write_file_content(gitlab_cluster_config_path, primary: false, secondary: true)
+
+        command.execute
+
+        expect(read_file_content(gitlab_cluster_config_path)).to eq("primary" => true, "secondary" => false)
+      end
+    end
+
+    def read_file_content(fullpath)
+      JSON.parse(File.read(fullpath))
+    end
+
+    def write_file_content(fullpath, content)
+      File.open(fullpath, 'w') do |f|
+        f.write(content.to_json)
+        f.chmod(0600)
+      end
+    end
+  end
+
+  context 'when preflight checks pass' do
+    before do
+      allow(STDIN).to receive(:gets).and_return('y')
+
+      allow_any_instance_of(Geo::PromotionPreflightChecks).to receive(
+        :execute).and_return(true)
+
+      allow(command).to receive(:toggle_geo_roles).and_return(true)
+      allow(command).to receive(:promote_postgresql_to_primary).and_return(true)
+      allow(command).to receive(:reconfigure).and_return(true)
+      allow(command).to receive(:promote_to_primary).and_return(true)
+      allow(command).to receive(:success_message).and_return(true)
+    end
+
+    context 'when running in force mode' do
+      let(:options) { { force: true } }
+
+      it 'does not ask for final confirmation' do
+        expect { command.execute }.not_to output(
+          /WARNING\: Secondary will now be promoted to primary./).to_stdout
+      end
+    end
+
+    context 'when not running in force mode' do
+      let(:options) { { force: false } }
+
+      it 'asks for confirmation' do
+        expect { command.execute }.to output(
+          /WARNING\: Secondary will now be promoted to primary./).to_stdout
+      end
+
+      context 'when final confirmation is given' do
+        it 'calls all the subcommands' do
+          expect(command).to receive(:toggle_geo_roles)
+          expect(command).to receive(:promote_postgresql_to_primary)
+          expect(command).to receive(:reconfigure)
+          expect(command).to receive(:promote_to_primary)
+          expect(command).to receive(:success_message)
+
+          command.execute
+        end
+      end
+    end
+  end
+
+  context 'when preflight checks fail' do
+    around do |example|
+      example.run
+    rescue SystemExit
+    end
+
+    before do
+      allow(STDIN).to receive(:gets).and_return('n')
+
+      allow(command).to receive(:promote_postgresql_to_primary).and_return(true)
+      allow(command).to receive(:reconfigure).and_return(true)
+      allow(command).to receive(:promote_to_primary).and_return(true)
+
+      allow_any_instance_of(Geo::PromotionPreflightChecks).to receive(
+        :execute).and_raise(SystemExit)
+    end
+
+    context 'when running in force mode' do
+      let(:options) { { force: true } }
+
+      it 'asks for confirmation' do
+        expect { command.execute }.to output(
+          /Are you sure you want to proceed?/
+        ).to_stdout
+      end
+
+      it 'exits with 1 if user denies' do
+        allow(STDIN).to receive(:gets).and_return('n')
+
+        expect { command.execute }.to raise_error(SystemExit) do |error|
+          expect(error.status).to eq(1)
+        end
+      end
+
+      it 'calls all the subcommands if user affirms' do
+        allow(STDIN).to receive(:gets).and_return('y')
+
+        is_expected.to receive(:toggle_geo_roles)
+        is_expected.to receive(:promote_postgresql_to_primary)
+        is_expected.to receive(:reconfigure)
+        is_expected.to receive(:promote_to_primary)
+        is_expected.to receive(:success_message)
+
+        command.execute
+      end
+    end
+
+    context 'when not running in force mode' do
+      let(:options) { { force: false } }
+
+      it 'exits with 1' do
+        expect { command.execute }.to raise_error(SystemExit)
+      end
+    end
+  end
+
+  context 'when writing to the cluster configuration file fail' do
+    around do |example|
+      example.run
+    rescue SystemExit
+    end
+
+    before do
+      allow(STDIN).to receive(:gets).and_return('y')
+
+      allow(command).to receive(:run_preflight_checks).and_return(true)
+
+      allow(GitlabCluster.config).to receive(:save).and_return(false)
+    end
+
+    it 'exits with 1' do
+      expect { command.execute }.to raise_error(SystemExit)
+    end
+  end
+
+  context 'when writing to the cluster configuration file succeed' do
+    before do
+      allow(STDIN).to receive(:gets).and_return('y')
+
+      allow(command).to receive(:promote_postgresql_to_primary).and_return(true)
+      allow(command).to receive(:reconfigure).and_return(true)
+      allow(command).to receive(:promote_to_primary).and_return(true)
+      allow(command).to receive(:success_message).and_return(true)
+
+      allow(GitlabCluster.config).to receive(:save).and_return(true)
+    end
+
+    it 'calls all the subcommands' do
+      expect(command).to receive(:promote_postgresql_to_primary)
+      expect(command).to receive(:reconfigure)
+      expect(command).to receive(:promote_to_primary)
+      expect(command).to receive(:success_message)
+
+      command.execute
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/geo/promotion_preflight_checks_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/geo/promotion_preflight_checks_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL2dlby9wcm9tb3Rpb25fcHJlZmxpZ2h0X2NoZWNrc19zcGVjLnJi
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/geo/promotion_preflight_checks_spec.rb
@@ -0,0 +1,145 @@
+require 'spec_helper'
+require 'geo/promotion_preflight_checks'
+require 'gitlab_ctl/util'
+
+RSpec.describe Geo::PromotionPreflightChecks, '#execute' do
+  let(:confirmation) { 'y' }
+  let(:options) { { confirm_primary_is_down: true } }
+
+  subject(:command) { described_class.new(nil, options) }
+
+  before do
+    allow(STDIN).to receive(:gets).and_return(confirmation)
+    allow(command).to receive(:run_command).with(any_args)
+
+    shell_out_object = double.tap do |shell_out_object|
+      allow(shell_out_object).to receive(:stdout).and_return('SUCCESS')
+      allow(shell_out_object).to receive(:error?).and_return(false)
+    end
+
+    allow(command).to receive(:run_command).with(
+      'gitlab-rake gitlab:geo:check_replication_verification_status')
+      .and_return(shell_out_object)
+  end
+
+  it 'prints preflight check instructions' do
+    expect { command.execute }.to output(
+      /Ensure you have completed the following manual preflight checks/)
+      .to_stdout
+  end
+
+  context 'when manual checks are confirmed' do
+    it 'does not raise an error' do
+      expect { command.execute }.to_not raise_error
+    end
+
+    it 'runs `check_replication_verification_status` task' do
+      is_expected.to receive(:run_command).with(
+        'gitlab-rake gitlab:geo:check_replication_verification_status').once
+
+      command.execute
+    end
+
+    context 'when gitlab:geo:check_replication_verification_status fails' do
+      around do |example|
+        example.run
+      rescue SystemExit
+      end
+
+      before do
+        shell_out_object = double.tap do |shell_out_object|
+          allow(shell_out_object).to receive(:stdout).and_return('error message')
+          allow(shell_out_object).to receive(:error?).and_return(true)
+        end
+
+        allow(command).to receive(:run_command).with(
+          'gitlab-rake gitlab:geo:check_replication_verification_status')
+          .once
+          .and_return(shell_out_object)
+      end
+
+      it 'prints error message when rake task errors' do
+        expect { command.execute }.to output(
+          /error message\nERROR: Replication\/verification is incomplete./).to_stdout
+      end
+
+      it 'exits with 1 when rake task errors' do
+        expect { command.execute }.to raise_error(SystemExit) do |error|
+          expect(error.status).to eq(1)
+        end
+      end
+    end
+
+    context 'when gitlab:geo:check_replication_verification_status passes' do
+      it 'prints a success message' do
+        expect { command.execute }.to output(
+          /All preflight checks have passed. This node can now be promoted./)
+          .to_stdout
+      end
+    end
+  end
+
+  context 'when manual checks are not confirmed' do
+    let(:confirmation) { 'n' }
+
+    around do |example|
+      example.run
+    rescue SystemExit
+    end
+
+    it 'print error message' do
+      expect { command.execute }.to output(
+        /ERROR: Manual preflight checks were not performed/
+      ).to_stdout
+    end
+  end
+
+  describe 'option --confirm-primary-is-down' do
+    before do
+      allow(command).to receive(:confirm_manual_checks).and_return(true)
+    end
+
+    context 'when the option is not passed' do
+      let(:options) { {} }
+      let(:confirmation) { 'n' }
+
+      around do |example|
+        example.run
+      rescue SystemExit
+      end
+
+      it 'asks user for confirmation' do
+        expect { command.execute }.to output(
+          /Is primary down? (N\/y)/)
+          .to_stdout
+      end
+
+      it 'prints an error message when user doesn not select y/Y' do
+        expect { command.execute }.to output(
+          /ERROR: Primary node must be down./)
+          .to_stdout
+      end
+    end
+
+    context 'when the option is passed' do
+      it 'does not ask user for confirmation' do
+        expect { command.execute }.not_to output(
+          /Is primary down? (N\/y)/)
+          .to_stdout
+      end
+    end
+  end
+
+  context 'when all checks pass' do
+    before do
+      allow(command).to receive(:confirm_manual_checks).and_return(true)
+      allow(command).to receive(:confirm_primary_is_down).and_return(true)
+    end
+
+    it 'prints a success message' do
+      expect { command.execute }.to output(
+        /All preflight checks have passed. This node can now be promoted./)
+        .to_stdout
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/geo/replication_process_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/geo/replication_process_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL2dlby9yZXBsaWNhdGlvbl9wcm9jZXNzX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/geo/replication_process_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+$LOAD_PATH << './files/gitlab-ctl-commands-ee/lib'
+$LOAD_PATH << './files/gitlab-ctl-commands/lib'
+
+require 'geo/replication_process'
+require 'gitlab_ctl/util'
+
+RSpec.describe Geo::ReplicationProcess do
+  let(:error_text) { 'AN ERROR' }
+  let(:good_status) { double('Command status', error?: false) }
+  let(:bad_status) { double('Command status', error?: true, stdout: error_text) }
+  let(:pause_cmd) { %r{gitlab-psql .* -c 'SELECT pg_wal_replay_pause\(\);'} }
+  let(:resume_cmd) { %r{gitlab-psql .* -c 'SELECT pg_wal_replay_resume\(\);'} }
+  let(:instance) { double(base_path: '/opt/gitlab/embedded', data_path: '/var/opt/gitlab/postgresql/data') }
+  let(:db_name) { 'gitlab_db_name' }
+  let(:options) do
+    {
+      db_name: db_name
+    }
+  end
+
+  subject { described_class.new(instance, options) }
+
+  describe '#pause' do
+    it 'logs a message if the rake task throws an error' do
+      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-rake geo:replication:pause/).and_return(bad_status)
+      expect(GitlabCtl::Util).to receive(:run_command).with(pause_cmd).and_return(good_status)
+
+      expect do
+        subject.pause
+      end.to output(/#{error_text}/).to_stdout
+    end
+
+    it 'raises an exception if unable to pause replication' do
+      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-rake geo:replication:pause/).and_return(good_status)
+      expect(GitlabCtl::Util).to receive(:run_command).with(pause_cmd).and_return(bad_status)
+
+      expect do
+        subject.pause
+      end.to raise_error(/Unable to pause postgres replication/)
+    end
+
+    it 'provides the requested database from the options' do
+      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-rake geo:replication:pause/).and_return(good_status)
+      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-psql -d #{db_name}/).and_return(good_status)
+
+      subject.pause
+    end
+  end
+
+  describe '#resume' do
+    it 'raises an exception if unable to resume pg replication' do
+      expect(GitlabCtl::Util).to receive(:run_command).with(resume_cmd).and_return(bad_status)
+
+      expect do
+        subject.resume
+      end.to raise_error(/Unable to resume postgres replication/)
+    end
+
+    it 'raises an error if rake task to resume fails' do
+      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-rake geo:replication:resume/).and_return(bad_status)
+      expect(GitlabCtl::Util).to receive(:run_command).with(resume_cmd).and_return(good_status)
+
+      expect do
+        subject.resume
+      end.to raise_error(/Unable to resume replication from primary/)
+    end
+
+    it 'provides the requested database from the options' do
+      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-rake geo:replication:resume/).and_return(good_status)
+      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-psql -d #{db_name}/).and_return(good_status)
+
+      subject.resume
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/geo/replication_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/geo/replication_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL2dlby9yZXBsaWNhdGlvbl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/geo/replication_spec.rb
@@ -0,0 +1,143 @@
+require 'spec_helper'
+
+$LOAD_PATH << './files/gitlab-ctl-commands-ee/lib'
+$LOAD_PATH << './files/gitlab-ctl-commands/lib'
+
+require 'geo/replication'
+require 'gitlab_ctl/util'
+
+RSpec.describe Geo::Replication, '#execute' do
+  let(:command) { spy('command spy', error?: false) }
+  let(:instance) { double(base_path: '/opt/gitlab/embedded', data_path: '/var/opt/gitlab') }
+  let(:file) { spy('file spy') }
+  let(:options) do
+    {
+      now: true,
+      host: 'localhost',
+      port: 9999,
+      user: 'my-user',
+      sslmode: 'disable',
+      sslcompression: 1,
+      recovery_target_timeline: 'latest',
+      db_name: 'gitlab_db_name'
+    }
+  end
+
+  subject { described_class.new(instance, options) }
+
+  before do
+    allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'postgresql' => { 'dir' => '/var/opt/gitlab/postgresql' } })
+
+    allow(instance).to receive(:service_enabled?).and_return(true)
+    allow(STDOUT).to receive(:puts)
+    allow(subject).to receive(:print)
+    allow(File).to receive(:open).and_yield(file)
+    allow(File).to receive(:exist?).with('/var/opt/gitlab/postgresql/data/standby.signal').and_return(true)
+    allow(File).to receive(:exist?).with(anything).and_call_original
+    allow(File).to receive(:write).with('/var/opt/gitlab/postgresql/data/standby.signal', "")
+
+    allow(GitlabCtl::Util).to receive(:run_command).and_return(command)
+    allow(subject).to receive(:postgresql_user).and_return('gitlab-psql')
+    allow(subject).to receive(:postgresql_group).and_return('gitlab-psql')
+    allow(subject).to receive(:postgresql_version).and_return(12)
+  end
+
+  it 'replicates geo database' do
+    expect(subject).to receive(:ask_pass).and_return('password')
+    expect(GitlabCtl::Util).to receive(:run_command)
+      .with(%r{/embedded/bin/pg_basebackup}, anything)
+
+    subject.execute
+  end
+
+  it 'uses the db_name option' do
+    expect(subject).to receive(:ask_pass).and_return('password')
+    expect(GitlabCtl::Util).to receive(:run_command)
+      .with(%r{gitlab_db_name}, anything)
+
+    subject.execute
+  end
+
+  it 'uses the supplied postgres directory' do
+    expected_dir = '/non/default/location'
+    allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'postgresql' => { 'dir' => expected_dir } })
+
+    expect(described_class.new(instance, options).postgresql_dir_path).to eq(expected_dir)
+  end
+
+  it 'writes recovery settings to postgresql.conf and creates a standby file' do
+    allow(File).to receive(:write)
+    allow(STDIN).to receive(:gets).and_return("pass\n")
+    allow(subject).to receive(:ask_pass).and_return('password')
+
+    expect(subject).to receive(:write_recovery_settings!)
+    expect(subject).to receive(:create_standby_file!)
+
+    subject.execute
+  end
+
+  context 'when there is TTY available' do
+    before do
+      allow(STDIN).to receive(:tty?).and_return(true)
+    end
+
+    it 'asks for database password in an interactive mode' do
+      expect(STDIN).to receive(:getpass).and_return('password')
+
+      subject.execute
+    end
+  end
+
+  context 'when there is no TTY available' do
+    before do
+      allow(STDIN).to receive(:tty?).and_return(false)
+    end
+
+    it 'asks for a database password in a non-interactive mode' do
+      expect(STDIN).to receive(:gets).and_return('pass')
+
+      subject.execute
+
+      expect(file).to have_received(:write).with(/recovery_target_timeline = 'latest'/)
+    end
+
+    it 'strips the password' do
+      allow(STDIN).to receive(:gets).and_return("pass\n")
+
+      subject.execute
+
+      expect(file).to have_received(:write).with("localhost:9999:*:my-user:pass\n")
+      expect(file).to have_received(:write).with(/password=pass sslmode=disable sslcompression=1'\n/)
+    end
+  end
+
+  context 'with a custom port' do
+    let(:options) { { now: true, skip_backup: true, host: 'localhost', port: 9999, user: 'my-user', slot_name: 'foo', db_name: 'gitlab_db_name' } }
+    let(:cmd) { %q(PGPASSFILE=/var/opt/gitlab/postgresql/.pgpass /opt/gitlab/embedded/bin/gitlab-psql -h localhost -p 9999 -U my-user -d gitlab_db_name -t -c "SELECT slot_name FROM pg_create_physical_replication_slot('foo');") }
+    let(:status) { double(error?: false, stdout: '') }
+
+    it 'executes a gitlab-psql call to check replication slots' do
+      expect(subject).to receive(:check_gitlab_active?).and_return(true)
+      expect(subject).to receive(:ask_pass).and_return('mypassword')
+
+      allow(GitlabCtl::Util).to receive(:run_command).and_return(status)
+      expect(GitlabCtl::Util).to receive(:run_command).with(cmd, anything).and_return(status)
+
+      subject.execute
+    end
+  end
+
+  context 'when user has to provide a confirmation text' do
+    let(:options) { { now: false, host: 'localhost', port: 9999, user: 'my-user', slot_name: 'foo' } }
+
+    it 'asks for confirmation string' do
+      allow(subject).to receive(:ask_pass).and_return('mypass')
+      expect(STDIN).to receive(:gets).and_return('replicate')
+      expect(GitlabCtl::Util).to receive(:run_command)
+        .with("PGPASSFILE=/var/opt/gitlab/postgresql/.pgpass /opt/gitlab/embedded/embedded/bin/pg_basebackup -h localhost -p 9999 -D /var/opt/gitlab/postgresql/data -U my-user -v -P -X stream -S foo",
+              anything)
+
+      subject.execute
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/geo/replication_toggle_command_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/geo/replication_toggle_command_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL2dlby9yZXBsaWNhdGlvbl90b2dnbGVfY29tbWFuZF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/geo/replication_toggle_command_spec.rb
@@ -0,0 +1,90 @@
+require 'spec_helper'
+
+$LOAD_PATH << './files/gitlab-ctl-commands-ee/lib'
+$LOAD_PATH << './files/gitlab-ctl-commands/lib'
+
+require 'geo/replication_toggle_command'
+require 'geo/promote_to_primary_node'
+require 'gitlab_ctl/util'
+
+RSpec.describe Geo::ReplicationToggleCommand do
+  let(:status) { double('Command status', error?: false) }
+  let(:arguments) { [] }
+  let(:ctl_instance) { double('gitlab-ctl instance', base_path: '', data_path: 'data_path') }
+
+  before do
+    allow_any_instance_of(Geo::ReplicationToggleCommand).to receive(:current_lsn).and_return('16/B374D848')
+  end
+
+  describe 'pause' do
+    subject { described_class.new(ctl_instance, 'pause', arguments) }
+
+    it 'calls pause' do
+      expect_any_instance_of(Geo::ReplicationProcess).to receive(:pause)
+      expect(File).to receive(:write).with('data_path/postgresql/data/geo-pitr-file', '16/B374D848')
+
+      expect { subject.execute! }.to output(/Create Geo point-in-time recovery file/).to_stdout
+    end
+
+    it 'rescues and exits if postgres has an error' do
+      expect_any_instance_of(Geo::ReplicationProcess).to receive(:pause).and_raise(Geo::PsqlError, "Oh nose!")
+
+      expect do
+        expect { subject.execute! }.to raise_error(SystemExit)
+      end.to output(/Postgres encountered an error: Oh nose!/).to_stdout
+    end
+
+    context 'database specified' do
+      let(:arguments) { %w(--db_name=database_i_want) }
+
+      it 'uses the specified database' do
+        expect(Geo::ReplicationProcess).to receive(:new).with(any_args, { db_name: 'database_i_want' }).and_call_original
+        expect_any_instance_of(Geo::ReplicationProcess).to receive(:pause)
+        expect(File).to receive(:write).with('data_path/postgresql/data/geo-pitr-file', '16/B374D848')
+
+        expect { subject.execute! }.to output(/Create Geo point-in-time recovery file/).to_stdout
+      end
+    end
+  end
+
+  describe 'resume' do
+    subject { described_class.new(ctl_instance, 'resume', arguments) }
+
+    before do
+      allow(File).to receive(:delete).with('data_path/postgresql/data/geo-pitr-file').and_return(1)
+    end
+
+    it 'calls resume' do
+      expect_any_instance_of(Geo::ReplicationProcess).to receive(:resume)
+
+      expect { subject.execute! }.to output(/Remove Geo point-in-time recovery file/).to_stdout
+    end
+
+    it 'rescues and exits if postgres has an error' do
+      expect_any_instance_of(Geo::ReplicationProcess).to receive(:resume).and_raise(Geo::PsqlError, "Oh nose!")
+
+      expect do
+        expect { subject.execute! }.to raise_error(SystemExit)
+      end.to output(/Postgres encountered an error: Oh nose!/).to_stdout
+    end
+
+    it 'rescues and exits if rake has an error' do
+      expect_any_instance_of(Geo::ReplicationProcess).to receive(:resume).and_raise(Geo::RakeError, "Oh nose!")
+
+      expect do
+        expect { subject.execute! }.to raise_error(SystemExit)
+      end.to output(/Rake encountered an error: Oh nose!/).to_stdout
+    end
+
+    context 'database specified' do
+      let(:arguments) { %w(--db_name=database_i_want) }
+
+      it 'uses the specified database' do
+        expect(Geo::ReplicationProcess).to receive(:new).with(any_args, { db_name: 'database_i_want' }).and_call_original
+        expect_any_instance_of(Geo::ReplicationProcess).to receive(:resume)
+
+        expect { subject.execute! }.to output(/Remove Geo point-in-time recovery file/).to_stdout
+      end
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/patroni_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/patroni_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL3BhdHJvbmlfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/patroni_spec.rb
@@ -0,0 +1,225 @@
+require 'spec_helper'
+require 'omnibus-ctl'
+require 'optparse'
+
+require_relative('../../../../files/gitlab-ctl-commands/lib/gitlab_ctl')
+require_relative('../../../../files/gitlab-ctl-commands-ee/lib/patroni')
+
+RSpec.describe 'Patroni' do
+  core_commands = %w(bootstrap check-leader check-replica check-standby-leader reinitialize-replica)
+  additional_commands = %w(members pause resume failover switchover restart reload)
+  all_commands = core_commands + additional_commands
+
+  command_lines = {
+    'bootstrap' => %w(--srcdir=SRCDIR --scope=SCOPE --datadir=DATADIR),
+    'pause' => %w(-w),
+    'resume' => %w(--wait),
+    'failover' => %w(--master MASTER --candidate CANDIDATE),
+    'switchover' => %w(--master MASTER --candidate CANDIDATE --scheduled SCHEDULED),
+    'reinitialize-replica' => %w(--wait --member MEMBER),
+    'restart' => [],
+    'reload' => []
+  }
+
+  command_options = {
+    'bootstrap' => { srcdir: 'SRCDIR', scope: 'SCOPE', datadir: 'DATADIR' },
+    'pause' => { wait: true },
+    'resume' => { wait: true },
+    'failover' => { master: 'MASTER', candidate: 'CANDIDATE' },
+    'switchover' => { master: 'MASTER', candidate: 'CANDIDATE', scheduled: 'SCHEDULED' },
+    'reinitialize-replica' => { wait: true, member: 'MEMBER' },
+    'restart' => {},
+    'reload' => {}
+
+  }
+
+  patronictl_command = {
+    'members' => 'list',
+    'pause' => 'pause -w',
+    'resume' => 'resume -w',
+    'failover' => 'failover --force --master MASTER --candidate CANDIDATE',
+    'switchover' => 'switchover --force --master MASTER --candidate CANDIDATE --scheduled SCHEDULED',
+    'restart' => 'restart --force fake-scope fake-node',
+    'reload' => 'reload --force fake-scope fake-node'
+  }
+
+  describe '.parse_options' do
+    before do
+      allow(Patroni::Utils).to receive(:warn_and_exit).and_call_original
+      allow(Kernel).to receive(:exit) { |code| raise "Kernel.exit(#{code})" }
+      allow(Kernel).to receive(:warn)
+    end
+
+    it 'should throw error when global options are invalid' do
+      expect { Patroni.parse_options(%w(patroni --foo)) }.to raise_error(OptionParser::ParseError)
+    end
+
+    it 'should throw error when sub-command is not specified' do
+      expect { Patroni.parse_options(%w(patroni -v)) }.to raise_error(OptionParser::ParseError)
+    end
+
+    it 'should throw error when sub-command is not defined' do
+      expect { Patroni.parse_options(%w(patroni -v foo)) }.to raise_error(OptionParser::ParseError)
+    end
+
+    it 'should recognize global options' do
+      expect(Patroni.parse_options(%w(patroni -v -q members))).to include(quiet: true, verbose: true)
+    end
+
+    context 'when sub-command is passed' do
+      all_commands.each do |cmd|
+        it "should parse #{cmd} options" do
+          cmd_line = command_lines[cmd] || []
+          cmd_opts = command_options[cmd] || {}
+          cmd_opts[:command] = cmd
+
+          expect(Patroni.parse_options(%W(patroni #{cmd}) + cmd_line)).to include(cmd_opts)
+        end
+      end
+    end
+
+    context 'when help option is passed' do
+      it 'should show help message and exit for global help option' do
+        expect { Patroni.parse_options(%w(patroni -h)) }.to raise_error('Kernel.exit(0)')
+        expect(Patroni::Utils).to have_received(:warn_and_exit).with(/Usage help/)
+      end
+
+      all_commands.each do |cmd|
+        it "should show help message and exit for #{cmd} help option" do
+          expect { Patroni.parse_options(%W(patroni #{cmd} -h)) }.to raise_error('Kernel.exit(0)')
+          expect(Patroni::Utils).to have_received(:warn_and_exit).with(instance_of(OptionParser))
+        end
+      end
+    end
+  end
+
+  describe 'additional commands' do
+    before do
+      allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'patroni' => { 'config_dir' => '/fake' } })
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({ 'patroni' => { 'scope' => 'fake-scope', 'name' => 'fake-node' } })
+      allow(GitlabCtl::Util).to receive(:run_command)
+    end
+
+    additional_commands.each do |cmd|
+      it "should run the relevant patronictl command for #{cmd}" do
+        Patroni.send(cmd.to_sym, command_options[cmd] || {})
+
+        expect(GitlabCtl::Util).to have_received(:run_command).with(
+          "/opt/gitlab/embedded/bin/patronictl -c /fake/patroni.yaml #{patronictl_command[cmd]}",
+          user: 'root', live: false)
+      end
+    end
+  end
+
+  describe 'command output on non-Patroni node' do
+    before do
+      allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'patroni' => nil })
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({ 'patroni' => nil })
+    end
+
+    additional_commands.each do |cmd|
+      it "should raise errors when running command #{cmd}" do
+        expect { Patroni.send(cmd.to_sym, command_options[cmd]) }.to(
+          raise_error(RuntimeError, /no Patroni configuration/)
+        )
+      end
+    end
+  end
+
+  describe '.init_db' do
+    before do
+      allow(GitlabCtl::Util).to receive(:run_command)
+    end
+
+    it 'should call initdb command with the specified options' do
+      Patroni.init_db(command_options['bootstrap'])
+
+      expect(GitlabCtl::Util).to have_received(:run_command).with('/opt/gitlab/embedded/bin/initdb -D DATADIR -E UTF8')
+    end
+  end
+
+  describe '.copy_config' do
+    before do
+      allow(FileUtils).to receive(:cp_r)
+    end
+
+    it 'should call initdb command with the specified options' do
+      Patroni.copy_config(command_options['bootstrap'])
+
+      expect(FileUtils).to have_received(:cp_r).with('SRCDIR/.', 'DATADIR')
+    end
+  end
+
+  describe '.leader?, .replica?, and .standyLeader?' do
+    before do
+      allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'patroni' => { 'api_address' => 'http://localhost:8009' } })
+      allow_any_instance_of(Patroni::Client).to receive(:get).with('/leader').and_yield(Struct.new(:code).new(leader_status))
+      allow_any_instance_of(Patroni::Client).to receive(:get).with('/replica').and_yield(Struct.new(:code).new(replica_status))
+      allow_any_instance_of(Patroni::Client).to receive(:get).with('/standby-leader').and_yield(Struct.new(:code).new(standby_leader_status))
+    end
+
+    context 'when node is leader' do
+      let(:leader_status) { '200' }
+      let(:replica_status) { '503' }
+      let(:standby_leader_status) { '503' }
+
+      it 'should identify node role' do
+        expect(Patroni.leader?({})).to be(true)
+        expect(Patroni.replica?({})).to be(false)
+        expect(Patroni.standby_leader?({})).to be(false)
+      end
+    end
+
+    context 'when node is replica' do
+      let(:leader_status) { '503' }
+      let(:replica_status) { '200' }
+      let(:standby_leader_status) { '503' }
+
+      it 'should identify node role' do
+        expect(Patroni.leader?({})).to be(false)
+        expect(Patroni.replica?({})).to be(true)
+        expect(Patroni.standby_leader?({})).to be(false)
+      end
+    end
+
+    context 'when node is standby leader' do
+      let(:leader_status) { '503' }
+      let(:replica_status) { '503' }
+      let(:standby_leader_status) { '200' }
+
+      it 'should identify node role' do
+        expect(Patroni.leader?({})).to be(false)
+        expect(Patroni.replica?({})).to be(false)
+        expect(Patroni.standby_leader?({})).to be(true)
+      end
+    end
+  end
+
+  describe '.reinitialize_replica' do
+    before do
+      allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'patroni' => { 'config_dir' => '/fake' } })
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({ 'patroni' => { 'scope' => 'fake-scope', 'name' => 'fake-node' } })
+      allow(GitlabCtl::Util).to receive(:run_command)
+    end
+
+    context 'when member option is set' do
+      it 'calls reinit command with the specified options to reinitialize the cluster member' do
+        Patroni.reinitialize_replica(command_options['reinitialize-replica'])
+
+        expect(GitlabCtl::Util).to have_received(:run_command).with(
+          '/opt/gitlab/embedded/bin/patronictl -c /fake/patroni.yaml reinit --force --wait fake-scope MEMBER',
+          user: 'root', live: true)
+      end
+    end
+
+    context 'when member option is not set' do
+      it 'calls reinit command with the specified options to reinitialize the current cluster member' do
+        Patroni.reinitialize_replica(command_options['reinitialize-replica'].slice(:wait))
+
+        expect(GitlabCtl::Util).to have_received(:run_command).with(
+          '/opt/gitlab/embedded/bin/patronictl -c /fake/patroni.yaml reinit --force --wait fake-scope fake-node',
+          user: 'root', live: true)
+      end
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/pgbouncer_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/pgbouncer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL3BnYm91bmNlcl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/pgbouncer_spec.rb
@@ -0,0 +1,120 @@
+require 'spec_helper'
+
+$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands-ee/lib')
+
+require 'pgbouncer'
+
+RSpec.describe Pgbouncer::Databases do
+  let(:fake_ohai) do
+    {
+      'gitlab' => {
+        'gitlab-rails' => {
+          'db_database' => 'fake_database'
+        },
+      },
+      'pgbouncer' => {
+        'databases_ini' => '/fakedata/pgbouncer/databases.ini',
+        'databases_json' => '/fakedata/pgbouncer/databases.json'
+      }
+    }
+  end
+
+  let(:fake_databases_json) do
+    {
+      fakedb: {
+        host: 'fakehost',
+        user: 'fakeuser',
+        port: 9999,
+        password: 'dslgkjdfgklfsd'
+      }
+    }.to_json.to_s
+  end
+
+  before do
+    allow(GitlabCtl::Util).to receive(:get_fqdn).and_return('fakehost')
+    allow(Dir).to receive(:exist?).and_call_original
+    allow(Dir).to receive(:exist?).with('/fakedata/pgbouncer').and_return(true)
+    allow(File).to receive(:exist?).with('/fakedata/pgbouncer/databases.json').and_return(true)
+    allow(File).to receive(:read).and_call_original
+    allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return(fake_ohai)
+    allow(File).to receive(:read).with('/fakedata/pgbouncer/databases.json').and_return(fake_databases_json)
+    @obj = Pgbouncer::Databases.new({}, '/fakeinstall', '/fakedata')
+  end
+
+  context 'default behavior' do
+    it 'allows creating a new object' do
+      expect(@obj.class).to be(Pgbouncer::Databases)
+    end
+
+    it 'creates the database object' do
+      results = {
+        "fakedb" => "host=fakehost port=9999 auth_user=fakeuser"
+      }
+      expect(@obj.databases).to eq(results)
+    end
+
+    it 'renders the template' do
+      expect(@obj.render).to eq(
+        "[databases]\n\nfakedb = host=fakehost port=9999 auth_user=fakeuser\n\n"
+      )
+    end
+  end
+
+  context 'with options' do
+    before do
+      options = {
+        'databases_ini' => '/another/databases.ini',
+        'databases_json' => '/another/databases.json',
+        'port' => 8888,
+        'user' => 'fakeuser'
+      }
+      allow(File).to receive(:exist?).with('/another/databases.json').and_return(true)
+      allow(File).to receive(:read).with('/another/databases.json').and_return(fake_databases_json)
+      @obj = Pgbouncer::Databases.new(options, '/fakeinstall', '/fakedata')
+    end
+
+    it 'sets the custom options' do
+      expect(@obj.ini_file).to eq('/another/databases.ini')
+      expect(@obj.json_file).to eq('/another/databases.json')
+    end
+
+    it 'renders the template' do
+      expect(@obj.render).to eq(
+        "[databases]\n\nfakedb = host=fakehost port=8888 auth_user=fakeuser\n\n"
+      )
+    end
+  end
+
+  context 'with empty databases.json' do
+    before do
+      allow(File).to receive(:read).with('/fakedata/pgbouncer/databases.json').and_return({}.to_s)
+      @obj = Pgbouncer::Databases.new({}, '/fakeinstall', '/fakedata')
+    end
+
+    it 'should generate an databases.ini with sane defaults' do
+      expect(@obj.render).to eq(
+        "[databases]\n\nfake_database = \n\n"
+      )
+    end
+  end
+
+  context 'with pgbouncer listening' do
+    before do
+      allow(@obj).to receive(:show_databases).and_return("nyan")
+    end
+
+    it 'should be running' do
+      expect(@obj.running?).to be true
+    end
+  end
+
+  context 'with pgbouncer not listening' do
+    before do
+      allow(@obj).to receive(:show_databases).and_raise(GitlabCtl::Errors::ExecutionError.new("nya", "nya", "neko"))
+    end
+
+    it 'should not be running' do
+      expect(@obj.running?).to be false
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/lib/postgresql/ee_spec.rb b/spec/chef/gitlab-ctl-commands-ee/lib/postgresql/ee_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvbGliL3Bvc3RncmVzcWwvZWVfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/lib/postgresql/ee_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
+$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands-ee/lib')
+
+require 'gitlab_ctl/util'
+require 'postgresql/ee'
+
+RSpec.describe GitlabCtl::PostgreSQL::EE do
+  describe "#get_primary" do
+    context 'when Consul disabled' do
+      before do
+        allow(GitlabCtl::Util).to receive(:get_node_attributes)
+          .and_return({})
+      end
+
+      it 'should raise an error' do
+        expect { described_class.get_primary } .to raise_error(/Consul agent is not enabled/)
+      end
+    end
+
+    context 'when PostgreSQL service name is not defined' do
+      before do
+        allow(GitlabCtl::Util).to receive(:get_node_attributes)
+          .and_return(
+            {
+              'consul' => {
+                'enable' => true,
+              }
+            }
+          )
+      end
+
+      it 'should raise an error' do
+        expect { described_class.get_primary } .to raise_error(/PostgreSQL service name is not defined/)
+      end
+    end
+
+    context 'when required settings are available' do
+      before do
+        allow(GitlabCtl::Util).to receive(:get_node_attributes)
+          .and_return(
+            {
+              'consul' => {
+                'enable' => true,
+              },
+              'patroni' => {
+                'scope' => 'fake'
+              }
+            }
+          )
+        allow_any_instance_of(Resolv::DNS).to receive(:getresources)
+          .with('master.fake.service.consul', Resolv::DNS::Resource::IN::SRV)
+          .and_return([Struct.new(:target, :port).new('fake.address', 6432)])
+        allow_any_instance_of(Resolv::DNS).to receive(:getaddress)
+          .with('fake.address')
+          .and_return('1.2.3.4')
+      end
+
+      it 'should get the list of PostgreSQL endpoints' do
+        expect(described_class.get_primary).to eq ['1.2.3.4:6432']
+      end
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/promote_db_spec.rb b/spec/chef/gitlab-ctl-commands-ee/promote_db_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvcHJvbW90ZV9kYl9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/promote_db_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'chef_helper'
+require 'geo/promote_db'
+
+RSpec.describe 'gitlab-ctl promote-db' do
+  let(:klass) { Geo::PromoteDb }
+  let(:command_name) { 'promote-db' }
+  let(:command_script) { 'promote_db' }
+
+  include_context 'ctl'
+
+  it_behaves_like 'gitlab geo promotion commands', 'promote-db'
+
+  # rubocop:disable Style/MutableConstant, Lint/ConstantDefinitionInBlock
+  it 'prints the deprecation message' do
+    # ARGV contains the commands that were passed to rspec, which are
+    # invalid for the omnibus-ctl commands
+    oldargv = ARGV
+    ARGV = []
+
+    expect_any_instance_of(klass).to receive(:execute)
+
+    expect { ctl.send(command_script) }.to output(
+      /WARNING: As of GitLab 14.5, this command is deprecated/).to_stdout
+
+    ARGV = oldargv
+  end
+  # rubocop:enable Style/MutableConstant, Lint/ConstantDefinitionInBlock
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/promote_to_primary_node_spec.rb b/spec/chef/gitlab-ctl-commands-ee/promote_to_primary_node_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvcHJvbW90ZV90b19wcmltYXJ5X25vZGVfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/promote_to_primary_node_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'geo/promote_to_primary_node'
+
+RSpec.describe 'gitlab-ctl promote-to-primary-node' do
+  let(:klass) { Geo::PromoteToPrimaryNode }
+  let(:command_name) { 'promote-to-primary-node' }
+  let(:command_script) { 'promote_to_primary_node' }
+
+  include_context 'ctl'
+
+  it_behaves_like 'gitlab geo promotion commands', 'promote-to-primary-node'
+
+  it_behaves_like 'geo promotion command accepts option',
+                  '--confirm-primary-is-down',
+                  { confirm_primary_is_down: true }
+
+  it_behaves_like 'geo promotion command accepts option',
+                  '--skip-preflight-checks',
+                  { skip_preflight_checks: true }
+
+  it_behaves_like 'geo promotion command accepts option',
+                  '--force',
+                  { force: true }
+
+  # rubocop:disable Style/MutableConstant, Lint/ConstantDefinitionInBlock
+  it 'prints the deprecation message' do
+    # ARGV contains the commands that were passed to rspec, which are
+    # invalid for the omnibus-ctl commands
+    oldargv = ARGV
+    ARGV = [] # rubocop:disable Style/MutableConstant
+
+    expect_any_instance_of(klass).to receive(:execute)
+
+    expect { ctl.send(command_script) }.to output(
+      /WARNING: As of GitLab 14.5, this command is deprecated/).to_stdout
+
+    ARGV = oldargv
+  end
+  # rubocop:enable Style/MutableConstant, Lint/ConstantDefinitionInBlock
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/promotion_preflight_checks_spec.rb b/spec/chef/gitlab-ctl-commands-ee/promotion_preflight_checks_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvcHJvbW90aW9uX3ByZWZsaWdodF9jaGVja3Nfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/promotion_preflight_checks_spec.rb
@@ -0,0 +1,19 @@
+require 'chef_helper'
+require 'geo/promotion_preflight_checks'
+
+RSpec.shared_context 'promotion-preflight-checks' do
+  let(:command_name) { 'promotion-preflight-checks' }
+  let(:klass) { Geo::PromotionPreflightChecks }
+  let(:command_script) { 'promotion_preflight_checks' }
+end
+
+RSpec.describe 'gitlab-ctl promotion-preflight-checks' do
+  include_context 'promotion-preflight-checks'
+  include_context 'ctl'
+
+  it_behaves_like 'gitlab geo promotion commands'
+
+  it_behaves_like 'geo promotion command accepts option',
+                  '--confirm-primary-is-down',
+                  { confirm_primary_is_down: true }
+end
diff --git a/spec/chef/gitlab-ctl-commands-ee/replicate_geo_database_spec.rb b/spec/chef/gitlab-ctl-commands-ee/replicate_geo_database_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMtZWUvcmVwbGljYXRlX2dlb19kYXRhYmFzZV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands-ee/replicate_geo_database_spec.rb
@@ -0,0 +1,143 @@
+require 'spec_helper'
+require 'omnibus-ctl'
+
+RSpec.describe 'gitlab-ctl replicate-geo-database' do
+  subject { Omnibus::Ctl.new('testing-ctl') }
+
+  let(:required_arguments) do
+    %w(--host=gitlab-primary.geo
+       --slot-name=gitlab_primary_geo)
+  end
+
+  before do
+    allow_any_instance_of(Omnibus::Ctl).to receive(:require).and_call_original
+    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
+      '/opt/testing-ctl/embedded/service/omnibus-ctl-ee/lib/geo/replication'
+    ) do
+      require_relative('../../../files/gitlab-ctl-commands-ee/lib/geo/replication')
+    end
+
+    subject.load_file('files/gitlab-ctl-commands-ee/replicate_geo_database.rb')
+  end
+
+  it 'appends a geo replication command' do
+    expect(subject.get_all_commands_hash).to include('replicate-geo-database')
+  end
+
+  describe "#replicate_geo_database" do
+    it 'executes the geo replication command' do
+      stub_command_arguments(required_arguments)
+
+      expect(Geo::Replication).to receive(:new).and_call_original
+        .with(subject, hash_including(host: 'gitlab-primary.geo',
+                                      slot_name: 'gitlab_primary_geo'))
+
+      expect_any_instance_of(Geo::Replication).to receive(:execute)
+
+      replicate_geo_database
+    end
+
+    it 'applies defaults to optional arguments' do
+      stub_command_arguments(required_arguments)
+
+      expect(Geo::Replication).to receive(:new).and_call_original
+        .with(subject, hash_including(host: 'gitlab-primary.geo',
+                                      slot_name: 'gitlab_primary_geo',
+                                      user: 'gitlab_replicator',
+                                      port: 5432,
+                                      password: nil,
+                                      now: false,
+                                      force: false,
+                                      skip_backup: false,
+                                      skip_replication_slot: false,
+                                      backup_timeout: 1800,
+                                      sslmode: 'verify-full',
+                                      sslcompression: 0,
+                                      recovery_target_timeline: 'latest',
+                                      db_name: 'gitlabhq_production'))
+
+      expect_any_instance_of(Geo::Replication).to receive(:execute)
+
+      replicate_geo_database
+    end
+
+    it 'requires the host argument' do
+      stub_command_arguments(%w(--slot-name=gitlab_primary_geo))
+
+      expect(Geo::Replication).not_to receive(:new)
+
+      # Important to catch this SystemExit or else RSpec exits
+      expect { subject.replicate_geo_database }.to raise_error(SystemExit).and(
+        output(/missing argument: host/).to_stdout
+      )
+    end
+
+    it 'requires the slot-name argument' do
+      stub_command_arguments(%w(--host=gitlab-primary.geo))
+
+      expect(Geo::Replication).not_to receive(:new)
+
+      # Important to catch this SystemExit or else RSpec exits
+      expect { subject.replicate_geo_database }.to raise_error(SystemExit).and(
+        output(/missing argument: --slot-name/).to_stdout
+      )
+    end
+
+    context 'with SSL compression enabled' do
+      it 'enables SSL compression' do
+        stub_command_arguments(
+          required_arguments +
+            %w(--sslmode=disable
+               --sslcompression=1
+               --no-wait))
+
+        expect(Geo::Replication).to receive(:new).and_call_original
+          .with(subject, hash_including(host: 'gitlab-primary.geo',
+                                        slot_name: 'gitlab_primary_geo',
+                                        sslmode: 'disable',
+                                        sslcompression: 1,
+                                        now: true))
+
+        expect_any_instance_of(Geo::Replication).to receive(:execute)
+
+        replicate_geo_database
+      end
+    end
+
+    context 'with db_name specified' do
+      it 'sets the db_name option' do
+        stub_command_arguments(
+          required_arguments +
+            %w(--db-name=custom_db_name))
+
+        expect(Geo::Replication).to receive(:new).and_call_original
+          .with(subject, hash_including(host: 'gitlab-primary.geo',
+                                        slot_name: 'gitlab_primary_geo',
+                                        db_name: 'custom_db_name'))
+
+        expect_any_instance_of(Geo::Replication).to receive(:execute)
+
+        replicate_geo_database
+      end
+    end
+  end
+
+  def stub_command_arguments(arguments)
+    expect_any_instance_of(Omnibus::Ctl::GeoReplicationCommand)
+      .to receive(:arguments).and_return(arguments)
+  end
+
+  def replicate_geo_database
+    # GeoReplicationCommand is designed to `exit 1` when there is a problem
+    # during option parsing. Tests that trigger this exit will also silently
+    # exit RSpec immediately, *and* they will *not* fail.
+    #
+    # So if a test expects a successful call, we *must* catch at least
+    # SystemExit for the test to be valid. And since other errors are always
+    # possible, and should also fail the test, we don't need to specify
+    # SystemExit here.
+    #
+    # In short, this is necessary for a failable test.
+    expect { subject.replicate_geo_database }.not_to raise_error
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands/gitlab_ctl_spec.rb b/spec/chef/gitlab-ctl-commands/gitlab_ctl_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMvZ2l0bGFiX2N0bF9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands/gitlab_ctl_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+require 'omnibus-ctl'
+
+RSpec.describe 'gitlab-ctl' do
+  before do
+    allow_any_instance_of(Omnibus::Ctl).to receive(:require).and_call_original
+    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
+      '/opt/testing-ctl/embedded/service/omnibus-ctl/lib/gitlab_ctl'
+    ) do
+      require_relative('../../../files/gitlab-ctl-commands/lib/gitlab_ctl')
+    end
+  end
+
+  let(:ctl_dir) { "#{File.dirname(__FILE__)}/../../../files/gitlab-ctl-commands" }
+
+  it 'should be able to load all files' do
+    # ARGV contains the commands that were passed to rspec, which are
+    # invalid for the omnibus-ctl commands
+    oldargv = ARGV
+    ARGV = [] # rubocop:disable Style/MutableConstant
+    expect do
+      ctl = Omnibus::Ctl.new('testing-ctl')
+      ctl.load_files(@ctl_dir)
+    end.not_to raise_error
+    ARGV = oldargv
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands/lib/gitlab_ctl_backup_spec.rb b/spec/chef/gitlab-ctl-commands/lib/gitlab_ctl_backup_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMvbGliL2dpdGxhYl9jdGxfYmFja3VwX3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands/lib/gitlab_ctl_backup_spec.rb
@@ -0,0 +1,318 @@
+require 'chef_helper'
+
+$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
+
+require 'gitlab_ctl'
+
+RSpec.describe GitlabCtl::Backup do
+  let(:backup_dir_path) { '/etc/gitlab/config_backup' }
+
+  # Valid backup files match the regular expression
+  let(:future_valid_backup_files) do
+    ['gitlab_config_2302886428_2042_12_22.tar']
+  end
+
+  let(:past_valid_backup_files) do
+    [
+      'gitlab_config_1603388428_2020_10_22.tar',
+      'gitlab_config_1606070428_2020_11_22.tar',
+      'gitlab_config_1608662428_2020_12_22.tar',
+    ]
+  end
+
+  let(:valid_backup_files) do
+    future_valid_backup_files + past_valid_backup_files
+  end
+
+  let(:invalid_backup_files) do
+    [
+      'lab_config_1600793789_2020_09_22.tar',
+      'gitlab_config_manual.tar',
+      'my_cool_backup.tar'
+    ]
+  end
+
+  let(:all_backup_files) do
+    valid_backup_files + invalid_backup_files
+  end
+
+  let(:warning_message) { "WARNING: In GitLab 14.0 we will begin removing all configuration backups older than" }
+
+  before do
+    allow(GitlabCtl::Backup).to receive(:print_warning?).and_return(false)
+    allow(File).to receive(:exist?).and_return(true)
+    allow(FileUtils).to receive(:chmod)
+    allow(FileUtils).to receive(:chown)
+    allow(FileUtils).to receive(:mkdir)
+    allow_any_instance_of(Kernel).to receive(:system).and_return(true)
+    allow_any_instance_of(Kernel).to receive(:exit!)
+    allow(Dir).to receive(:chdir).and_yield
+    allow(FileUtils).to receive(:rm)
+    allow(Time).to receive(:now).and_return(Time.utc(2021))
+    # Don't let messages output during test
+    allow(STDOUT).to receive(:write)
+  end
+
+  context 'with default settings' do
+    let(:options) { { delete_old_backups: nil } }
+
+    before do
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({})
+    end
+
+    it 'should default to deleting old backups' do
+      backup = GitlabCtl::Backup.new
+      expect(backup.wants_pruned).to eq(true)
+    end
+
+    context 'when the backup path is readable by non-root' do
+      before do
+        allow_any_instance_of(GitlabCtl::Backup).to receive(:secure?).and_return(false)
+      end
+
+      it 'should warn the administrator' do
+        expect { GitlabCtl::Backup.perform }.to output(/WARNING: #{backup_dir_path} may be read by non-root users/).to_stderr
+      end
+    end
+
+    context 'when the backup path is readable by only root' do
+      let(:archive_name) { 'gitlab_config_8675309_1981_11_16.tar' }
+
+      before do
+        allow_any_instance_of(GitlabCtl::Backup).to receive(:secure?).and_return(true)
+        allow_any_instance_of(GitlabCtl::Backup).to receive(:archive_name).and_return(archive_name)
+      end
+
+      it 'should use proper tar command' do
+        expect_any_instance_of(Kernel).to receive(:system).with(
+          *%W(tar --absolute-names --dereference --verbose --create --file /etc/gitlab/config_backup/#{archive_name}
+              --exclude /etc/gitlab/config_backup -- /etc/gitlab)
+        )
+        GitlabCtl::Backup.perform
+      end
+
+      it 'should set proper file mode on archive file' do
+        expect(FileUtils).to receive(:chmod).with(0600, %r{#{backup_dir_path}/#{archive_name}})
+        GitlabCtl::Backup.perform
+      end
+
+      it 'should notify about archive creation starting' do
+        expect { GitlabCtl::Backup.perform }.to output(
+          %r{Creating configuration backup archive: #{archive_name}}
+        ).to_stdout
+      end
+
+      it 'should put notify about archive creation completion' do
+        expect { GitlabCtl::Backup.perform }.to output(
+          %r{Configuration backup archive complete: #{backup_dir_path}/#{archive_name}}
+        ).to_stdout
+      end
+
+      it 'should be able to accept a backup directory path argument' do
+        custom_dir = "/TanukisOfUnusualSize"
+        options = { backup_path: custom_dir }
+        expect { GitlabCtl::Backup.perform(options) }.to output(
+          %r{Configuration backup archive complete: #{custom_dir}/#{archive_name}}
+        ).to_stdout
+      end
+
+      context 'when etc backup path does not exist' do
+        before do
+          allow(File).to receive(:exist?).with(backup_dir_path).and_return(false)
+        end
+
+        it 'should log proper message' do
+          expect { GitlabCtl::Backup.perform }.to output(
+            %r{Could not find '#{backup_dir_path}' directory\. Creating\.}).to_stdout
+        end
+
+        it 'should create directory' do
+          expect(FileUtils).to receive(:mkdir).with(backup_dir_path, mode: 0700)
+          GitlabCtl::Backup.perform
+        end
+
+        it 'should set proper owner and group' do
+          expect(FileUtils).to receive(:chown).with('root', 'root', backup_dir_path)
+          GitlabCtl::Backup.perform
+        end
+
+        context 'when /etc/gitlab is NFS share' do
+          before do
+            allow(STDERR).to receive(:write)
+            allow(FileUtils).to receive(:chown).with('root', 'root', backup_dir_path).and_raise(Errno::EPERM)
+          end
+
+          it 'should put proper output to STDERR' do
+            expect { GitlabCtl::Backup.perform }.to output(
+              /Warning: Could not change owner of #{backup_dir_path} to 'root:root'. As a result your backups may be accessible to some non-root users./).to_stderr
+          end
+        end
+      end
+    end
+
+    context 'when etc path does not exist' do
+      let(:etc_path) { '/etc/gitlab' }
+
+      before do
+        allow(File).to receive(:exist?).with(etc_path).and_return(false)
+      end
+
+      it "should abort with proper message" do
+        expect { GitlabCtl::Backup.perform }.to output(/Could not find '#{etc_path}' directory. Is your package installed correctly?/).to_stderr.and raise_error
+      end
+    end
+  end
+
+  context 'when backup_keep_time is non-zero' do
+    let(:options) { { delete_old_backups: true } }
+    let(:node_config) do
+      {
+        'gitlab' => {
+          'gitlab-rails' => {
+            'backup_keep_time' => 1
+          }
+        }
+      }
+    end
+
+    before do
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(node_config)
+      allow(GitlabCtl::Backup).to receive(:secure?).and_return(true)
+      @backup = GitlabCtl::Backup.new(options)
+    end
+
+    context 'when no valid files exist' do
+      before do
+        allow(Dir).to receive(:glob).and_return(invalid_backup_files)
+        allow_any_instance_of(Kernel).to receive(:warn)
+      end
+
+      it 'should find no files to remove' do
+        expect(@backup.removable_archives.length).to equal(0)
+        expect(FileUtils).not_to have_received(:rm)
+        expect { @backup.prune }.to output(/done. Removed 0 older configuration backups./).to_stdout
+      end
+    end
+
+    context 'when files exist to remove' do
+      before do
+        allow(Dir).to receive(:glob).and_return(all_backup_files)
+      end
+
+      it 'should identify the correct files to remove' do
+        removed = past_valid_backup_files.map do |x|
+          File.join(backup_dir_path, x)
+        end
+
+        expect(@backup.removable_archives).to match_array(removed)
+        @backup.remove_backups
+        removed.each do |f|
+          expect(FileUtils).to have_received(:rm).with(f)
+        end
+      end
+
+      context 'when file removal fails' do
+        let(:failed_file) { File.join(backup_dir_path, past_valid_backup_files[1]) }
+        let(:message) { "Permission denied @ unlink_internal - #{failed_file}" }
+        let(:removed_files) do
+          files = past_valid_backup_files.map do |f|
+            File.join(backup_dir_path, f)
+          end
+
+          files.select! { |f| f != failed_file }
+        end
+
+        before do
+          allow(FileUtils).to receive(:rm).with(failed_file).and_raise(Errno::EACCES, message)
+        end
+
+        it 'removes the remaining expected files' do
+          allow_any_instance_of(Kernel).to receive(:warn)
+          @backup.remove_backups
+          removed_files.each do |filename|
+            expect(FileUtils).to have_received(:rm).with(filename)
+          end
+        end
+
+        it 'sets the proper file removal count' do
+          allow_any_instance_of(Kernel).to receive(:warn)
+          expect { @backup.remove_backups }.to output(/done. Removed #{removed_files.length} older configuration backups./).to_stdout
+        end
+
+        it 'prints the error from file that could not be removed' do
+          expect { @backup.remove_backups }.to output(a_string_matching(message)).to_stderr
+        end
+      end
+    end
+
+    context 'when the only valid files are after backup_keep_time' do
+      before do
+        allow(Dir).to receive(:glob).and_return(future_valid_backup_files)
+      end
+
+      it 'should find no files to remove' do
+        allow_any_instance_of(Kernel).to receive(:warn)
+        expect(@backup.removable_archives.length).to equal(0)
+        expect(FileUtils).not_to have_received(:rm)
+        expect { @backup.prune }.to output(/done. Removed 0 older configuration backups./).to_stdout
+      end
+    end
+  end
+
+  context 'when backup_keep_time is zero' do
+    let(:options) { { delete_old_backups: nil } }
+    let(:node_config) do
+      {
+        'gitlab' => {
+          'gitlab-rails' => {
+            'backup_keep_time' => 0
+          }
+        }
+      }
+    end
+
+    before do
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(node_config)
+      allow(GitlabCtl::Backup).to receive(:secure?).and_return(true)
+      allow(Dir).to receive(:glob).and_return(all_backup_files)
+      @backup = GitlabCtl::Backup.new(options)
+    end
+
+    it 'should skip performing a backup' do
+      expect { @backup.prune }.to output(/Keeping all older configuration backups/).to_stdout
+      expect(FileUtils).not_to have_received(:rm)
+    end
+  end
+
+  context 'when node attributes invocation fails' do
+    let(:options) { { delete_old_backups: nil } }
+
+    before do
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_raise(GitlabCtl::Errors::NodeError, "Oh no, node failed")
+      allow_any_instance_of(Kernel).to receive(:warn)
+      allow(Dir).to receive(:glob).and_return(all_backup_files)
+    end
+
+    it 'should not remove old archives' do
+      backup = GitlabCtl::Backup.new(options)
+      expect { backup.prune }.to output(/Keeping all older configuration backups/).to_stdout
+      expect(FileUtils).not_to have_received(:rm)
+    end
+  end
+
+  context 'when node attributes returns an empty hash' do
+    let(:options) { { delete_old_backups: nil } }
+
+    before do
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({})
+      allow_any_instance_of(Kernel).to receive(:warn)
+      allow(Dir).to receive(:glob).and_return(all_backup_files)
+    end
+
+    it 'should not remove old archives' do
+      backup = GitlabCtl::Backup.new(options)
+      expect { backup.prune }.to output(/Keeping all older configuration backups/).to_stdout
+      expect(FileUtils).not_to have_received(:rm)
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands/lib/gitlab_ctl_pg_upgrade_spec.rb b/spec/chef/gitlab-ctl-commands/lib/gitlab_ctl_pg_upgrade_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMvbGliL2dpdGxhYl9jdGxfcGdfdXBncmFkZV9zcGVjLnJi
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands/lib/gitlab_ctl_pg_upgrade_spec.rb
@@ -0,0 +1,145 @@
+require 'spec_helper'
+
+$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
+
+require 'gitlab_ctl'
+
+def stub_postgresql_json_attributes(config = nil)
+  options = config || {}
+  options['port'] ||= '5432'
+  node_data = { postgresql: options.transform_keys(&:to_sym) }
+  public_data = { 'postgresql' => options }
+  allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(node_data)
+  allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return(public_data)
+end
+
+RSpec.describe GitlabCtl::PgUpgrade do
+  before do
+    @fake_default_dir = '/fake/data/postgresql/data'
+    allow(GitlabCtl::Util).to receive(:get_command_output).with(
+      "/fakebasedir/embedded/bin/pg_ctl --version"
+    ).and_return('fakeoldverision')
+  end
+
+  context 'with a default configuration' do
+    before do
+      stub_postgresql_json_attributes
+      @dbw = GitlabCtl::PgUpgrade.new('/fakebasedir', '/fake/data', 'fakenewversion', nil, 123)
+    end
+
+    it 'should create a new object' do
+      expect(@dbw).to be_instance_of(GitlabCtl::PgUpgrade)
+    end
+
+    it 'should allow for a custom base directory' do
+      expect(@dbw.base_path).to eq('/fakebasedir')
+    end
+
+    it 'should use the specified timeout' do
+      expect(@dbw.timeout).to eq(123)
+    end
+
+    it 'should set tmp_data_dir to data_dir if tmp_dir is nil on initialization' do
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({})
+
+      expect(@dbw.tmp_data_dir).to eq(@dbw.data_dir)
+    end
+
+    it 'should return the appropriate data version' do
+      stub_postgresql_json_attributes
+      allow(File).to receive(:read).with(
+        File.join(@fake_default_dir, 'PG_VERSION')
+      ).and_return("99.99\n")
+
+      expect(@dbw.fetch_data_version).to eq('99.99')
+    end
+
+    context 'when determining if there is enough free space to perform an upgrade' do
+      before do
+        stub_postgresql_json_attributes
+      end
+
+      it 'detects when there is not enough available disk space for upgrade' do
+        allow(GitlabCtl::Util).to receive(:get_command_output).with(
+          "du -s --block-size=1m #{@dbw.data_dir}", nil, 123
+        ).and_return("200000\n#{@dbw.data_dir}")
+
+        allow(GitlabCtl::Util).to receive(:get_command_output).with(
+          "df -P --block-size=1m #{@dbw.data_dir} | awk '{print $4}'", nil, 123
+        ).and_return("Available\n220000")
+
+        expect(@dbw.enough_free_space?(@dbw.data_dir)).to eq(false)
+      end
+
+      it 'detects when there is enough available disk space for upgrade' do
+        allow(GitlabCtl::Util).to receive(:get_command_output).with(
+          "du -s --block-size=1m #{@dbw.data_dir}", nil, 123
+        ).and_return("200000\n#{@dbw.data_dir}")
+
+        allow(GitlabCtl::Util).to receive(:get_command_output).with(
+          "df -P --block-size=1m #{@dbw.data_dir} | awk '{print $4}'", nil, 123
+        ).and_return("Available\n250000")
+
+        expect(@dbw.enough_free_space?(@dbw.data_dir)).to eq(true)
+      end
+    end
+
+    context 'when a failed upgrade attempt happened' do
+      before do
+        allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({})
+      end
+
+      let(:target_data_dir) { "future_data_dir" }
+      describe '#upgrade_artifact_exists?' do
+        it 'returns false when the directory does not exist' do
+          expect(@dbw.upgrade_artifact_exists?(target_data_dir)).to be false
+        end
+
+        it 'returns false when the directory exists with no data' do
+          allow(File).to receive(:exist?).with(target_data_dir).and_return(true)
+          allow(Dir).to receive(:empty?).with(target_data_dir).and_return(true)
+          expect(@dbw.upgrade_artifact_exists?(target_data_dir)).to be false
+        end
+
+        it 'returns true when the directory exists with data' do
+          allow(File).to receive(:exist?).with(target_data_dir).and_return(true)
+          allow(Dir).to receive(:empty?).with(target_data_dir).and_return(false)
+          expect(@dbw.upgrade_artifact_exists?(target_data_dir)).to be true
+        end
+      end
+    end
+  end
+
+  it 'should use the configured port when running pg_upgrade' do
+    stub_postgresql_json_attributes({ 'port' => '1959' })
+    @dbw = GitlabCtl::PgUpgrade.new('/fakebasedir', '/fake/data', 'fakenewversion', nil, 123)
+    expect(@dbw.port).to eq('1959')
+  end
+
+  it 'should call pg_command with the appropriate command' do
+    stub_postgresql_json_attributes({ 'username' => 'arbitrary-user-name' })
+    @dbw = GitlabCtl::PgUpgrade.new('/fakebasedir', '/fake/data', 'fakenewversion', nil, 123)
+    expect(GitlabCtl::Util).to receive(
+      :get_command_output
+    ).with('su - arbitrary-user-name -c "fake command"', nil, 123)
+    @dbw.run_pg_command('fake command')
+  end
+
+  context 'when an explicit data directory is specified' do
+    it 'should use it as data_dir' do
+      stub_postgresql_json_attributes({ 'dir' => 'randomdir' })
+      @dbw = GitlabCtl::PgUpgrade.new('/fakebasedir', '/fake/data', 'fakenewversion', nil, 123)
+
+      expect(@dbw.data_dir).to eq('randomdir/data')
+    end
+  end
+
+  context 'when an explicit data directory is not specified' do
+    it 'should use find data_dir using dir/data' do
+      stub_postgresql_json_attributes({ 'dir' => 'parentdir' })
+      @dbw = GitlabCtl::PgUpgrade.new('/fakebasedir', '/fake/data', 'fakenewversion', nil, 123)
+
+      expect(@dbw.data_dir).to eq('parentdir/data')
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands/lib/postgresql/replication_spec.rb b/spec/chef/gitlab-ctl-commands/lib/postgresql/replication_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMvbGliL3Bvc3RncmVzcWwvcmVwbGljYXRpb25fc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands/lib/postgresql/replication_spec.rb
@@ -0,0 +1,98 @@
+require 'spec_helper'
+$LOAD_PATH << File.join(__dir__, '../../../../files/gitlab-ctl-commands/lib')
+
+require 'postgresql/replication'
+
+RSpec.describe PostgreSQL::Replication do
+  let(:ctl) { spy('gitlab ctl') }
+  let(:util) { spy('gitlab ctl util', error?: false) }
+  let(:attributes) { spy('node attributes spy') }
+
+  subject { described_class.new(ctl) }
+
+  before do
+    stub_const('GitlabCtl::Util', util)
+
+    allow(util).to receive(:get_node_attributes)
+      .and_return(attributes)
+  end
+
+  it 'asks for the password' do
+    expect(subject).to receive(:ask_password).and_return('pass').once
+
+    subject.set_password!
+  end
+
+  it 'sets a password' do
+    allow(subject).to receive(:ask_password).and_return('mypass')
+
+    subject.set_password!
+
+    expect(util).to have_received(:run_command)
+      .with(%r{WITH ENCRYPTED PASSWORD 'mypass'})
+  end
+
+  context 'when postgresql is enabled and patroni disabled' do
+    before do
+      allow(ctl).to receive(:service_enabled?).with('postgresql').and_return(true)
+      allow(ctl).to receive(:service_enabled?).with('patroni').and_return(false)
+    end
+
+    it 'sets a password for user defined in `sql_replication_user` attribute' do
+      allow(subject).to receive(:ask_password).and_return('mypass')
+      allow(attributes).to receive(:to_s).and_return('myuser')
+
+      subject.set_password!
+
+      expect(util).to have_received(:run_command)
+        .with(%r{ALTER USER myuser WITH ENCRYPTED PASSWORD 'mypass'})
+    end
+  end
+
+  it 'raises an error if replication user is not configured' do
+    allow(subject).to receive(:ask_password).and_return('mypass')
+    allow(attributes).to receive(:dig).and_return(nil)
+
+    expect { subject.set_password! }.to raise_error(ArgumentError)
+  end
+
+  context 'when postgresql is disabled and patroni enabled' do
+    before do
+      allow(ctl).to receive(:service_enabled?).with('postgresql').and_return(false)
+      allow(ctl).to receive(:service_enabled?).with('patroni').and_return(true)
+    end
+
+    it 'sets a password for user defined in `sql_replication_user` attribute' do
+      allow(subject).to receive(:ask_password).and_return('mypass')
+      allow(attributes).to receive(:to_s).and_return('myuser')
+
+      subject.set_password!
+
+      expect(util).to have_received(:run_command)
+        .with(%r{ALTER USER myuser WITH ENCRYPTED PASSWORD 'mypass'})
+    end
+  end
+
+  context 'when postgresql and patroni services are disabled' do
+    around do |example|
+      example.run
+    rescue SystemExit
+    end
+
+    before do
+      allow(ctl).to receive(:service_enabled?).with('postgresql').and_return(false)
+      allow(ctl).to receive(:service_enabled?).with('patroni').and_return(false)
+    end
+
+    it 'prints an error message' do
+      expect { subject.set_password! }.to output(
+        /There is no PostgreSQL instance enabled in Omnibus/).to_stdout
+    end
+
+    it 'exits with 1' do
+      expect { subject.set_password! }.to raise_error(SystemExit) do |error|
+        expect(error.status).to eq(1)
+      end
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands/lib/postgresql_spec.rb b/spec/chef/gitlab-ctl-commands/lib/postgresql_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMvbGliL3Bvc3RncmVzcWxfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands/lib/postgresql_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
+
+require 'postgresql'
+
+RSpec.describe GitlabCtl::PostgreSQL do
+  describe "#postgresql_username" do
+    before do
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(
+        {
+          'postgresql' => {
+            'username' => 'bar'
+          }
+        }
+      )
+    end
+    it 'detects username correctly' do
+      expect(described_class.postgresql_username).to eq('bar')
+    end
+  end
+
+  describe '#postgresql_group' do
+    before do
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(
+        {
+          'postgresql' => {
+            'group' => 'foo'
+          }
+        }
+      )
+    end
+
+    it 'returns the correct group' do
+      expect(described_class.postgresql_group).to eq('foo')
+    end
+  end
+
+  describe '#postgresql_version' do
+    before do
+      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(
+        {
+          'postgresql' => {
+            'dir' => '/var/opt/gitlab'
+          }
+        }
+      )
+    end
+
+    context 'when PG_VERSION file exists' do
+      before do
+        allow(File).to receive(:exist?).and_return(true)
+        allow(File).to receive(:read).and_return('12\n')
+      end
+
+      it 'returns the version' do
+        expect(described_class.postgresql_version).to eq(12)
+      end
+    end
+
+    context 'when PG_VERSION file exists' do
+      before do
+        allow(File).to receive(:exist?).and_return(false)
+      end
+
+      it 'returns nil' do
+        expect(described_class.postgresql_version).to be_nil
+      end
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands/lib/praefect_spec.rb b/spec/chef/gitlab-ctl-commands/lib/praefect_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMvbGliL3ByYWVmZWN0X3NwZWMucmI=
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands/lib/praefect_spec.rb
@@ -0,0 +1,207 @@
+require 'spec_helper'
+require 'optparse'
+
+require_relative('../../../../files/gitlab-ctl-commands/lib/praefect')
+
+RSpec.describe Praefect do
+  describe '.parse_options!' do
+    before do
+      allow(Kernel).to receive(:exit) { |code| raise "Kernel.exit(#{code})" }
+    end
+
+    shared_examples 'unknown option is specified' do
+      it 'throws an error' do
+        expect { Praefect.parse_options!(%W(praefect #{command} --unknown)) }.to raise_error(OptionParser::InvalidOption, /unknown/)
+      end
+    end
+
+    it 'throws an error when command is not specified' do
+      expect { Praefect.parse_options!(%w(praefect)) }.to raise_error(OptionParser::ParseError, /Praefect command is not specified/)
+    end
+
+    it 'throws an error when unknown command is specified' do
+      expect { Praefect.parse_options!(%w(praefect unknown-command)) }.to raise_error(OptionParser::ParseError, /Unknown Praefect command: unknown-command/)
+    end
+
+    shared_examples 'parses repository options' do
+      it 'throws an error when an argument for --virtual-storage-name is not specified' do
+        expect { Praefect.parse_options!(%W(praefect #{command} --dir dir --virtual-storage-name)) }.to raise_error(OptionParser::MissingArgument, /virtual-storage-name/)
+      end
+
+      it 'throws an error when --virtual-storage-name is not specified' do
+        expect { Praefect.parse_options!(%W(praefect #{command} --dir dir)) }.to raise_error(OptionParser::ParseError, /Option --virtual-storage-name must be specified/)
+      end
+
+      it 'throws an error when an argument for --repository-relative-path is not specified' do
+        expect { Praefect.parse_options!(%W(praefect #{command} --dir dir --repository-relative-path)) }.to raise_error(OptionParser::MissingArgument, /repository-relative-path/)
+      end
+
+      it 'throws an error when --repository-relative-path is not specified' do
+        expect { Praefect.parse_options!(%W(praefect #{command} --dir dir --virtual-storage-name name)) }.to raise_error(OptionParser::ParseError, /Option --repository-relative-path must be specified/)
+      end
+
+      it 'successfully parses correct params' do
+        expected_options = { command: command, dir: 'dir', virtual_storage_name: 'name', repository_relative_path: 'path' }
+
+        expect(Praefect.parse_options!(%W(praefect #{command} --dir dir --virtual-storage-name name --repository-relative-path path))).to eq(expected_options)
+      end
+    end
+
+    context 'when command is remove-repository' do
+      let(:command) { 'remove-repository' }
+
+      it_behaves_like 'parses repository options'
+      it_behaves_like 'unknown option is specified'
+
+      it 'successfully parses apply' do
+        expect(Praefect.parse_options!(%W(praefect #{command}
+                                          --dir dir
+                                          --virtual-storage-name name
+                                          --repository-relative-path path
+                                          --apply))).to include(apply: true)
+
+        expect(Praefect.parse_options!(%W(praefect #{command}
+                                          --dir dir
+                                          --virtual-storage-name name
+                                          --repository-relative-path path
+                                          --apply something))).to include(apply: true)
+
+        expect(Praefect.parse_options!(%W(praefect #{command}
+                                          --dir dir
+                                          --virtual-storage-name name
+                                          --repository-relative-path path))).not_to include(:apply)
+      end
+    end
+
+    context 'when command is check' do
+      let(:command) { 'check' }
+
+      it_behaves_like 'unknown option is specified'
+    end
+
+    context 'when command is track-repository' do
+      let(:command) { 'track-repository' }
+
+      it_behaves_like 'parses repository options'
+      it_behaves_like 'unknown option is specified'
+
+      it 'successfully parses authoritative-storage' do
+        expected_options = { command: command,
+                             dir: 'dir',
+                             virtual_storage_name: 'name',
+                             repository_relative_path: 'path',
+                             authoritative_storage: 'storage-1',
+                             replicate_immediately: true }
+
+        expect(Praefect.parse_options!(%W(praefect #{command}
+                                          --dir dir
+                                          --virtual-storage-name name
+                                          --repository-relative-path path
+                                          --authoritative-storage storage-1
+                                          --replicate-immediately))).to eq(expected_options)
+      end
+    end
+
+    context 'when help option is passed' do
+      it 'shows help message and exit for global help option' do
+        expect(Kernel).to receive(:puts) do |msg|
+          expect(msg.to_s).to match(/gitlab-ctl praefect command/)
+        end
+
+        expect { Praefect.parse_options!(%w(praefect -h)) }.to raise_error('Kernel.exit(0)')
+      end
+
+      ['remove-repository', 'track-repository'].each do |cmd|
+        it "shows help message and exit for #{cmd} help option" do
+          expect(Kernel).to receive(:puts) do |msg|
+            expect(msg.to_s).to match(/gitlab-ctl praefect #{cmd}/)
+          end
+
+          expect { Praefect.parse_options!(%W(praefect #{cmd} -h)) }.to raise_error('Kernel.exit(0)')
+        end
+      end
+    end
+  end
+
+  describe '#execute' do
+    let(:repository_options) { { virtual_storage_name: 'storage-name', repository_relative_path: 'repository-path' } }
+
+    context 'when package is not installed correctly' do
+      it 'aborts the execution if a path does not exit' do
+        allow(File).to receive(:exists?).twice.and_return(false)
+        expect(Kernel).to receive(:abort).and_raise('aborted')
+
+        expect { Praefect.execute({}) }.to raise_error('aborted')
+      end
+    end
+
+    shared_examples 'executes the command' do
+      it 'successfully executes' do
+        allow(File).to receive(:exist?).and_return(true)
+        expect(Kernel).not_to receive(:abort)
+        args = [
+          Praefect::EXEC_PATH, '-config', 'dir/config.toml', command,
+        ]
+
+        args += command_args
+
+        expect(Kernel).to receive(:system).with(*args).and_return('result!')
+        expect(Kernel).not_to receive(:exit!)
+
+        common_options = {
+          command: command,
+          dir: 'dir'
+        }
+
+        Praefect.execute(common_options.merge(command_options))
+      end
+    end
+
+    context 'check command' do
+      let(:command) { 'check' }
+      let(:command_args) { [] }
+      let(:command_options) { {} }
+
+      it_behaves_like 'executes the command'
+    end
+
+    context 'remove-repository command' do
+      let(:command) { 'list-untracked-repositories' }
+      let(:command_args) { [] }
+      let(:command_options) { {} }
+
+      it_behaves_like 'executes the command'
+    end
+
+    context 'repository commands' do
+      let(:command_args) { ['-virtual-storage', 'storage-name', '-repository', 'repository-path'] }
+      let(:command_options) { repository_options }
+
+      context 'remove-repository command' do
+        let(:command) { 'remove-repository' }
+
+        it_behaves_like 'executes the command'
+      end
+
+      context 'track-repository command' do
+        let(:command) { 'track-repository' }
+        let(:command_args) { ['-virtual-storage', 'storage-name', '-repository', 'repository-path', '-authoritative-storage', 'storage-1'] }
+        let(:command_options) { repository_options.merge(authoritative_storage: 'storage-1') }
+
+        it_behaves_like 'executes the command'
+
+        context 'with replicate-immediately' do
+          let(:command_args) do
+            ['-virtual-storage', 'storage-name',
+             '-repository', 'repository-path',
+             '-authoritative-storage', 'storage-1',
+             '-replicate-immediately']
+          end
+          let(:command_options) { repository_options.merge(authoritative_storage: 'storage-1', replicate_immediately: true) }
+
+          it_behaves_like 'executes the command'
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands/lib/upgrade_check_spec.rb b/spec/chef/gitlab-ctl-commands/lib/upgrade_check_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMvbGliL3VwZ3JhZGVfY2hlY2tfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands/lib/upgrade_check_spec.rb
@@ -0,0 +1,39 @@
+require 'chef_helper'
+
+$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
+
+require 'gitlab_ctl/upgrade_check'
+
+RSpec.describe GitlabCtl::UpgradeCheck do
+  let(:latest) { '13.1.3' }
+  let(:previous_major) { '12.5.10' }
+  let(:previous_major_latest) { '12.10.13' }
+  let(:current_major) { '13.0.8' }
+  let(:old) { '11.5.11' }
+
+  context 'not an upgrade' do
+    it 'returns true' do
+      expect(described_class.valid?(nil, latest)).to be true
+    end
+  end
+
+  context 'valid upgrade paths' do
+    it 'returns true for an upgrade from the current major version' do
+      expect(described_class.valid?(current_major, latest)).to be true
+    end
+  end
+
+  context 'invalid upgrade paths' do
+    it 'returns false for an upgrade from 11.5 to the latest' do
+      expect(described_class.valid?(old, latest)).to be false
+    end
+
+    it 'returns false for an upgrade from 12.5 to the latest' do
+      expect(described_class.valid?(previous_major, latest)).to be false
+    end
+
+    it 'returns false upgrading from previous_major_latest to the latest' do
+      expect(described_class.valid?(previous_major_latest, latest)).to be false
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ctl-commands/lib/util_spec.rb b/spec/chef/gitlab-ctl-commands/lib/util_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL2dpdGxhYi1jdGwtY29tbWFuZHMvbGliL3V0aWxfc3BlYy5yYg==
--- /dev/null
+++ b/spec/chef/gitlab-ctl-commands/lib/util_spec.rb
@@ -0,0 +1,262 @@
+require 'spec_helper'
+
+$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
+
+require 'gitlab_ctl'
+
+RSpec.describe GitlabCtl::Util do
+  context 'when there is no TTY available' do
+    before do
+      allow(STDIN).to receive(:tty?).and_return(false)
+    end
+
+    it 'asks for a database password in a non-interactive mode' do
+      allow(STDIN).to receive(:gets).and_return('pass')
+
+      expect(described_class.get_password).to eq('pass')
+    end
+
+    it 'strips a new line character from the password' do
+      allow(STDIN).to receive(:tty?).and_return(false)
+      allow(STDIN).to receive(:gets).and_return("mypass\n")
+
+      expect(described_class.get_password).to eq('mypass')
+    end
+  end
+
+  context 'when there is TTY available' do
+    before do
+      allow(STDIN).to receive(:tty?).and_return(true)
+    end
+
+    it 'asks for confirmation' do
+      expect(STDIN).to receive(:getpass).twice.and_return("mypass")
+
+      expect(described_class.get_password(do_confirm: true)).to eq('mypass')
+    end
+
+    it 'skips confirmation' do
+      expect(STDIN).to receive(:getpass).and_return("mypass")
+
+      expect(described_class.get_password(do_confirm: false)).to eq('mypass')
+    end
+  end
+
+  describe '#roles' do
+    let(:fake_base_path) { '/foo' }
+    let(:fake_hostname) { 'fakehost.fakedomain' }
+    let(:fake_node_file) { "#{fake_base_path}/embedded/nodes/#{fake_hostname}.json" }
+
+    before do
+      hostname = double('hostname')
+      allow(hostname).to receive(:stdout).and_return(fake_hostname)
+      allow(GitlabCtl::Util).to receive(:run_command).with('hostname -f').and_return(hostname)
+      allow(File).to receive(:exist?).with(fake_node_file).and_return(true)
+    end
+
+    it 'returns an empty list when no roles are defined' do
+      empty_node = {
+        normal: {
+          roles: {}
+        }
+      }
+      allow(File).to receive(:read).with(fake_node_file).and_return(empty_node.to_json)
+      expect(described_class.roles(fake_base_path)).to eq([])
+    end
+
+    it 'returns a list of roles that are defined' do
+      roled_node = {
+        normal: {
+          roles: {
+            role_one: {
+              enable: true
+            },
+            role_two: {
+              enable: false
+            },
+            role_three: {
+              enable: true
+            }
+          }
+        }
+      }
+      allow(File).to receive(:read).with(fake_node_file).and_return(roled_node.to_json)
+      expect(described_class.roles(fake_base_path)).to eq(%w[role_one role_three])
+    end
+
+    it 'when no roles are defined' do
+      roled_node = {}
+      allow(File).to receive(:read).with(fake_node_file).and_return(roled_node.to_json)
+
+      expect(described_class.roles(fake_base_path)).to eq([])
+    end
+
+    it 'when roles is not a Hash' do
+      roled_node = { roles: "test" }
+      allow(File).to receive(:read).with(fake_node_file).and_return(roled_node.to_json)
+
+      expect(described_class.roles(fake_base_path)).to eq([])
+    end
+  end
+
+  describe '#parse_json_file' do
+    it 'fails on malformed JSON file' do
+      malformed_json = <<~MSG
+      {
+      'foo': 'bar'
+      MSG
+      allow(File).to receive(:read).with('/tmp/foo').and_return(malformed_json)
+
+      expect { GitlabCtl::Util.parse_json_file('/tmp/foo') }.to raise_error(GitlabCtl::Errors::NodeError, "Error reading /tmp/foo, has reconfigure been run yet?")
+    end
+
+    it 'do not fail on empty json file' do
+      allow(File).to receive(:read).with('/tmp/foo').and_return('{}')
+
+      expect(GitlabCtl::Util.parse_json_file('/tmp/foo')).to eq({})
+    end
+
+    it 'fails on incomplete but valid node attribute file' do
+      incomplete_node_attributes = <<~MSG
+      {
+        "name": "a-random-server"
+      }
+      MSG
+      allow(File).to receive(:read).with('/opt/gitlab/embedded/nodes/12345.json').and_return(incomplete_node_attributes)
+
+      expect { GitlabCtl::Util.parse_json_file('/opt/gitlab/embedded/nodes/12345.json') }.to raise_error(GitlabCtl::Errors::NodeError, "Attributes not found in /opt/gitlab/embedded/nodes/12345.json, has reconfigure been run yet?")
+    end
+  end
+
+  describe '#parse_duration' do
+    it 'should raise error for nil, empty, or malformed inputs' do
+      expect { GitlabCtl::Util.parse_duration(nil) }.to raise_error ArgumentError, 'invalid value for duration: ``'
+      expect { GitlabCtl::Util.parse_duration('') }.to raise_error ArgumentError, 'invalid value for duration: ``'
+      expect { GitlabCtl::Util.parse_duration('foo') }.to raise_error ArgumentError, 'invalid value for duration: `foo`'
+      expect { GitlabCtl::Util.parse_duration('123foo') }.to raise_error ArgumentError, 'invalid value for duration: `123foo`'
+      expect { GitlabCtl::Util.parse_duration('foo123') }.to raise_error ArgumentError, 'invalid value for duration: `foo123`'
+    end
+
+    it 'should parse unformatted inputs into milliseconds' do
+      expect(GitlabCtl::Util.parse_duration('123')).to eq(123)
+      expect(GitlabCtl::Util.parse_duration('123.456')).to eq(123)
+    end
+
+    it 'should recognize and parse different duration units' do
+      expect(GitlabCtl::Util.parse_duration('123.456ms')).to eq(123)
+      expect(GitlabCtl::Util.parse_duration('123.456s')).to eq(123.456 * 1000)
+      expect(GitlabCtl::Util.parse_duration('123.456m')).to eq(123.456 * 1000 * 60)
+      expect(GitlabCtl::Util.parse_duration('123.456h')).to eq(123.456 * 1000 * 60 * 60)
+      expect(GitlabCtl::Util.parse_duration('123.456d')).to eq(123.456 * 1000 * 60 * 60 * 24)
+    end
+
+    it 'should parse mixed unit inputs in any order' do
+      expect(GitlabCtl::Util.parse_duration('1.1d2.2h3.3m4.4s5.5ms')).to eq(
+        1.1 * 1000 * 60 * 60 * 24 +
+        2.2 * 1000 * 60 * 60 +
+        3.3 * 1000 * 60 +
+        4.4 * 1000 +
+        5
+      )
+      expect(GitlabCtl::Util.parse_duration('5.5ms4.4s3.3m2.2h1.1d')).to eq(
+        1.1 * 1000 * 60 * 60 * 24 +
+        2.2 * 1000 * 60 * 60 +
+        3.3 * 1000 * 60 +
+        4.4 * 1000 +
+        5
+      )
+    end
+
+    it 'should break and return when input is partially valid' do
+      expect(GitlabCtl::Util.parse_duration('1h2m3foo')).to eq(
+        1 * 1000 * 60 * 60 +
+        2 * 1000 * 60
+      )
+      expect(GitlabCtl::Util.parse_duration('1h2m3')).to eq(
+        1 * 1000 * 60 * 60 +
+        2 * 1000 * 60
+      )
+    end
+  end
+
+  describe '#chef_run' do
+    it 'should accept and use custom log files' do
+      allow(described_class).to receive(:run_command).and_return('success')
+      expect(described_class).to receive(:run_command).with(/-L log_ago_and_far_away/)
+      described_class.chef_run('config', 'attributes', 'log_ago_and_far_away')
+    end
+
+    it 'should not pass the log file path unless set' do
+      allow(described_class).to receive(:run_command).and_return('success')
+      expect(described_class).not_to receive(:run_command).with(/ -L /)
+      described_class.chef_run('config', 'attributes')
+    end
+  end
+
+  describe '#get_node_attributes' do
+    context 'when node file is missing' do
+      before do
+        allow(File).to receive(:exist?).with(%r{/opt/gitlab/embedded/nodes.*json}).and_return(false)
+        allow(Dir).to receive(:glob?).with(%r{/opt/gitlab/embedded/}).and_return([])
+      end
+
+      it 'raises an error' do
+        expect { GitlabCtl::Util.get_node_attributes }.to raise_error(GitlabCtl::Errors::NodeError, "Node attributes JSON file not found in /opt/gitlab/embedded/nodes, has reconfigure been run yet?")
+      end
+    end
+  end
+
+  describe "#public_attributes_missing?" do
+    context 'when the public attributes file is missing' do
+      before do
+        allow(File).to receive(:exist?).with(%r{/var/opt/gitlab/public_attributes.json}).and_return(false)
+      end
+
+      it 'returns true' do
+        expect(GitlabCtl::Util.public_attributes_missing?).to be true
+      end
+    end
+  end
+
+  describe "#public_attributes_broken?" do
+    context 'when the public attributes file is broken' do
+      before do
+        allow(File).to receive(:exist?).with(%r{/var/opt/gitlab/public_attributes.json}).and_return(true)
+      end
+
+      it 'returns true when the attribute is missing' do
+        allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({})
+        expect(GitlabCtl::Util.public_attributes_broken?).to be true
+      end
+
+      it 'returns false by default when the gitlab attribute is found' do
+        allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ "gitlab" => "hithere" })
+        expect(GitlabCtl::Util.public_attributes_broken?).to be false
+      end
+
+      context 'when using an alternate key to check for broken' do
+        it 'returns true when the key is missing' do
+          allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({})
+          expect(GitlabCtl::Util.public_attributes_broken?('funky')).to be true
+        end
+
+        it 'returns false when the key is present' do
+          allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ "funky" => "llama" })
+          expect(GitlabCtl::Util.public_attributes_broken?('funky')).to be false
+        end
+      end
+    end
+  end
+
+  describe '#get_public_node_attributes' do
+    context 'when node file is missing' do
+      before do
+        allow(GitlabCtl::Util).to receive(:public_attributes_missing?).and_return(true)
+      end
+
+      it 'returns empty hash' do
+        expect(GitlabCtl::Util.get_public_node_attributes).to eq({})
+      end
+    end
+  end
+end
diff --git a/spec/chef/gitlab-ee/libraries/gitlab_geo_helper_spec.rb b/spec/chef/gitlab-ee/libraries/gitlab_geo_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi1lZS9saWJyYXJpZXMvZ2l0bGFiX2dlb19oZWxwZXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab-ee/libraries/gitlab_geo_helper_spec.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require_relative '../../../../files/gitlab-cookbooks/gitlab-ee/libraries/gitlab_geo_helper.rb'
-require 'chef_helper'
-
-RSpec.describe GitlabGeoHelper do
-  cached(:chef_run) { converge_config(is_ee: true) }
-  subject { described_class.new(chef_run.node) }
-
-  describe '#migrated?' do
-    let(:state_file) { '/tmp/geo-db-migrate-xyz-123' }
-
-    before do
-      allow(subject).to receive(:db_migrate_status_file) { state_file }
-    end
-
-    it 'returns true when migration file exists and content is zero' do
-      stub_file_content(state_file, '0')
-
-      expect(subject.migrated?).to eq true
-    end
-
-    it 'returns false when migration file doesnt exist' do
-      allow(File).to receive(:exist?).with(state_file) { false }
-
-      expect(subject.migrated?).to eq false
-    end
-
-    it 'returns false when content is not zero' do
-      stub_file_content(state_file, '1')
-
-      expect(subject.migrated?).to eq false
-    end
-  end
-
-  describe '#db_migrate_status_file' do
-    let(:connection_digest) { '32596e8376077c3ef8d5cf52f15279ba' }
-    let(:revision) { '8b21d70' }
-
-    before do
-      allow(subject).to receive(:connection_digest) { connection_digest }
-      allow(subject).to receive(:revision) { revision }
-    end
-
-    it 'contains connection digest' do
-      expect(subject.db_migrate_status_file).to include(connection_digest)
-    end
-
-    it 'contains revision number' do
-      expect(subject.db_migrate_status_file).to include(revision)
-    end
-
-    it 'follows specific pattern' do
-      expect(subject.db_migrate_status_file).to include("geo-db-migrate-#{connection_digest}-#{revision}")
-    end
-  end
-
-  def stub_file_content(fullpath, content)
-    allow(File).to receive(:exist?).with(fullpath) { true }
-    allow(IO).to receive(:read).with(fullpath) { content }
-  end
-end
diff --git a/spec/chef/gitlab-ee/recipes/default_spec.rb b/spec/chef/gitlab-ee/recipes/default_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi1lZS9yZWNpcGVzL2RlZmF1bHRfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab-ee/recipes/default_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab-ee::default' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'postgresql is enabled' do
-    context 'pgbouncer will not connect to postgresql' do
-      it 'should always include the pgbouncer_user recipe' do
-        expect(chef_run).to include_recipe('pgbouncer::user')
-      end
-    end
-
-    context 'pgbouncer will connect to postgresql' do
-      before do
-        stub_gitlab_rb(
-          {
-            postgresql: {
-              pgbouncer_user_password: 'fakepassword'
-            }
-          }
-        )
-      end
-
-      it 'should include the pgbouncer_user recipe' do
-        expect(chef_run).to include_recipe('pgbouncer::user')
-      end
-    end
-  end
-end
diff --git a/spec/chef/gitlab-ee/recipes/geo-logcursor_spec.rb b/spec/chef/gitlab-ee/recipes/geo-logcursor_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi1lZS9yZWNpcGVzL2dlby1sb2djdXJzb3Jfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab-ee/recipes/geo-logcursor_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Copyright:: Copyright (c) 2017 GitLab Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef_helper'
-
-RSpec.describe 'gitlab-ee::geo-logcursor' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe 'when disabled' do
-    it_behaves_like 'disabled runit service', 'geo-logcursor'
-  end
-
-  describe 'when enabled' do
-    before do
-      stub_gitlab_rb(
-        geo_logcursor: {
-          enable: true,
-        }
-      )
-    end
-
-    it_behaves_like 'enabled runit service', 'geo-logcursor', 'root', 'root'
-  end
-end
diff --git a/spec/chef/gitlab-ee/recipes/geo-postgresql_spec.rb b/spec/chef/gitlab-ee/recipes/geo-postgresql_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi1lZS9yZWNpcGVzL2dlby1wb3N0Z3Jlc3FsX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab-ee/recipes/geo-postgresql_spec.rb
+++ /dev/null
@@ -1,388 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'geo postgresql' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
-  let(:postgresql_conf) { '/var/opt/gitlab/geo-postgresql/data/postgresql.conf' }
-  let(:runtime_conf) { '/var/opt/gitlab/geo-postgresql/data/runtime.conf' }
-  let(:pg_hba_conf) { '/var/opt/gitlab/geo-postgresql/data/pg_hba.conf' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow_any_instance_of(GeoPgHelper).to receive(:version).and_return(PGVersion.new('best_version'))
-    allow_any_instance_of(GeoPgHelper).to receive(:running_version).and_return(PGVersion.new('best_version'))
-    allow_any_instance_of(GeoPgHelper).to receive(:database_version).and_return(PGVersion.new('best_version'))
-
-    # Workaround for Chef reloading instances across different examples
-    allow_any_instance_of(GeoPgHelper).to receive(:bootstrapped?).and_return(true)
-    allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).and_call_original
-    allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).with('geo-postgresql').and_return(true)
-  end
-
-  context 'when geo postgres is enabled' do
-    before do
-      stub_gitlab_rb(geo_postgresql: { enable: true })
-    end
-
-    it 'includes the postgresql::bin recipe' do
-      expect(chef_run).to include_recipe('postgresql::bin')
-    end
-
-    it 'includes the postgresql_user recipe' do
-      expect(chef_run).to include_recipe('postgresql::user')
-    end
-
-    it 'includes the postgresql_sysctl recipe' do
-      expect(chef_run).to include_recipe('postgresql::sysctl')
-    end
-
-    it 'does not warn the user that a restart is needed by default' do
-      allow_any_instance_of(GeoPgHelper).to receive(:is_running?).and_return(true)
-      expect(chef_run).not_to run_ruby_block('warn pending geo-postgresql restart')
-    end
-
-    it 'notifies restarts postgresql when the postgresql runit run file changes' do
-      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).and_call_original
-      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).with('geo-postgresql').and_return(true)
-
-      psql_service = chef_run.service('geo-postgresql')
-      expect(psql_service).not_to subscribe_to('template[/opt/gitlab/sv/geo-postgresql/run]').on(:restart).delayed
-    end
-
-    it 'includes runtime.conf in postgresql.conf' do
-      expect(chef_run).to render_file(postgresql_conf)
-        .with_content(/include 'runtime.conf'/)
-    end
-
-    it 'creates the gitlab_geo role in the geo-postgresql database, without a password' do
-      expect(chef_run).to create_postgresql_user('gitlab_geo').with(password: nil)
-    end
-
-    it 'creates gitlabhq_geo_production database' do
-      params = {
-        owner: 'gitlab_geo'
-      }
-      expect(chef_run).to create_postgresql_database('gitlabhq_geo_production').with(params)
-    end
-
-    context 'with default settings' do
-      it_behaves_like 'enabled runit service', 'geo-postgresql', 'root', 'root'
-
-      context 'when rendering postgresql.conf' do
-        it 'correctly sets the shared_preload_libraries default setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['shared_preload_libraries']).to be_nil
-
-          expect(chef_run).to render_file(postgresql_conf)
-            .with_content(/shared_preload_libraries = ''/)
-        end
-
-        it 'sets archive settings' do
-          expect(chef_run).to render_file(
-            postgresql_conf
-          ).with_content(/archive_mode = off/)
-        end
-
-        it 'sets the max_replication_slots setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['max_replication_slots']).to eq(0)
-
-          expect(chef_run).to render_file(
-            postgresql_conf
-          ).with_content(/max_replication_slots = 0/)
-        end
-
-        it 'sets the synchronous_commit setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['synchronous_standby_names']).to eq('')
-
-          expect(chef_run).to render_file(
-            postgresql_conf
-          ).with_content(/synchronous_standby_names = ''/)
-        end
-
-        it 'does not set dynamic_shared_memory_type by default' do
-          expect(chef_run).not_to render_file(
-            postgresql_conf
-          ).with_content(/^dynamic_shared_memory_type = /)
-        end
-
-        it 'sets the max_locks_per_transaction setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['max_locks_per_transaction'])
-            .to eq(128)
-
-          expect(chef_run).to render_file(
-            postgresql_conf
-          ).with_content(/max_locks_per_transaction = 128/)
-        end
-      end
-      context 'when rendering runtime.conf' do
-        it 'correctly sets the log_line_prefix default setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['log_line_prefix']).to be_nil
-
-          expect(chef_run).to render_file(runtime_conf)
-            .with_content(/log_line_prefix = ''/)
-        end
-
-        it 'sets max_standby settings' do
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/max_standby_archive_delay = 30s/)
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/max_standby_streaming_delay = 30s/)
-        end
-
-        it 'sets archive settings' do
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/archive_command = ''/)
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/archive_timeout = 0/)
-        end
-
-        it 'sets the synchronous_commit setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['synchronous_commit']).to eq('on')
-
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/synchronous_commit = on/)
-        end
-
-        it 'sets the hot_standby_feedback setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['hot_standby_feedback'])
-            .to eq('off')
-
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/hot_standby_feedback = off/)
-        end
-
-        it 'sets the random_page_cost setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['random_page_cost'])
-            .to eq(2.0)
-
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/random_page_cost = 2\.0/)
-        end
-
-        it 'sets the log_temp_files setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['log_temp_files'])
-            .to eq(-1)
-
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/log_temp_files = -1/)
-        end
-
-        it 'sets the log_checkpoints setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['log_checkpoints'])
-            .to eq('off')
-
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/log_checkpoints = off/)
-        end
-
-        it 'sets idle_in_transaction_session_timeout' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['idle_in_transaction_session_timeout'])
-            .to eq('60000')
-
-          expect(chef_run).to render_file(runtime_conf)
-            .with_content(/idle_in_transaction_session_timeout = 60000/)
-        end
-
-        it 'sets effective_io_concurrency' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['effective_io_concurrency'])
-            .to eq(1)
-
-          expect(chef_run).to render_file(runtime_conf)
-            .with_content(/effective_io_concurrency = 1/)
-        end
-
-        it 'sets max_worker_processes' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['max_worker_processes'])
-            .to eq(8)
-
-          expect(chef_run).to render_file(runtime_conf)
-            .with_content(/max_worker_processes = 8/)
-        end
-
-        it 'sets max_parallel_workers_per_gather' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['max_parallel_workers_per_gather'])
-            .to eq(0)
-
-          expect(chef_run).to render_file(runtime_conf)
-            .with_content(/max_parallel_workers_per_gather = 0/)
-        end
-
-        it 'sets log_lock_waits' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['log_lock_waits'])
-            .to eq(1)
-
-          expect(chef_run).to render_file(runtime_conf)
-            .with_content(/log_lock_waits = 1/)
-        end
-
-        it 'sets deadlock_timeout' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['deadlock_timeout'])
-            .to eq('5s')
-
-          expect(chef_run).to render_file(runtime_conf)
-            .with_content(/deadlock_timeout = '5s'/)
-        end
-
-        it 'sets track_io_timing' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['track_io_timing'])
-            .to eq('off')
-
-          expect(chef_run).to render_file(runtime_conf)
-            .with_content(/track_io_timing = 'off'/)
-        end
-      end
-    end
-
-    context 'with user specified settings' do
-      before do
-        stub_gitlab_rb(geo_postgresql: {
-                         enable: true,
-                         dynamic_shared_memory_type: 'none',
-                         custom_pg_hba_entries: {
-                           foo: [
-                             type: 'host',
-                             database: 'foo',
-                             user: 'bar',
-                             cidr: '127.0.0.1/32',
-                             method: 'trust'
-                           ]
-                         },
-                         sql_user_password: 'fakepasswordhash',
-                         shared_preload_libraries: 'pg_stat_statements',
-                         log_line_prefix: '%a',
-                         max_standby_archive_delay: '60s',
-                         max_standby_streaming_delay: '120s',
-                         archive_mode: 'on',
-                         archive_command: 'command',
-                         archive_timeout: '120',
-                       },
-                       postgresql: {
-                         username: 'foo',
-                         group: 'bar'
-                       }
-                      )
-      end
-
-      it 'notifies geo-postgresql reload' do
-        runtime_resource = chef_run.template(runtime_conf)
-        expect(runtime_resource).to notify('execute[reload geo-postgresql]').to(:run).immediately
-      end
-
-      it 'creates the gitlab_geo role in the geo-postgresql database with the specified password' do
-        expect(chef_run).to create_postgresql_user('gitlab_geo').with(password: 'md5fakepasswordhash')
-      end
-
-      context 'when rendering postgresql.conf' do
-        it 'sets the dynamic_shared_memory_type' do
-          expect(chef_run).to render_file(
-            postgresql_conf
-          ).with_content(/^dynamic_shared_memory_type = none/)
-        end
-
-        it 'correctly sets the shared_preload_libraries setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['shared_preload_libraries']).to eql('pg_stat_statements')
-
-          expect(chef_run).to render_file(postgresql_conf)
-            .with_content(/shared_preload_libraries = 'pg_stat_statements'/)
-        end
-
-        it 'sets archive settings' do
-          expect(chef_run).to render_file(
-            postgresql_conf
-          ).with_content(/archive_mode = on/)
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/archive_command = 'command'/)
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/archive_timeout = 120/)
-        end
-      end
-
-      context 'when rendering runtime.conf' do
-        it 'correctly sets the log_line_prefix setting' do
-          expect(chef_run.node['gitlab']['geo-postgresql']['log_line_prefix']).to eql('%a')
-
-          expect(chef_run).to render_file(runtime_conf)
-            .with_content(/log_line_prefix = '%a'/)
-        end
-
-        it 'sets max_standby settings' do
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/max_standby_archive_delay = 60s/)
-          expect(chef_run).to render_file(
-            runtime_conf
-          ).with_content(/max_standby_streaming_delay = 120s/)
-        end
-
-        context 'when rendering pg_hba.conf' do
-          it 'creates a standard pg_hba.conf' do
-            expect(chef_run).to render_file(pg_hba_conf)
-              .with_content('local   all         all                               peer map=gitlab')
-          end
-
-          it 'adds users custom entries to pg_hba.conf' do
-            expect(chef_run).to render_file(pg_hba_conf)
-              .with_content('host foo bar 127.0.0.1/32 trust')
-          end
-        end
-      end
-    end
-
-    context 'when geo postgres is disabled' do
-      before do
-        stub_gitlab_rb(geo_postgresql: { enable: false })
-      end
-
-      it_behaves_like 'disabled runit service', 'geo-postgresql'
-    end
-  end
-end
-
-RSpec.describe 'geo postgresql when version mismatches occur' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
-  let(:postgresql_conf) { '/var/opt/gitlab/geo-postgresql/data/postgresql.conf' }
-  let(:runtime_conf) { '/var/opt/gitlab/geo-postgresql/data/runtime.conf' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    stub_gitlab_rb(geo_postgresql: { enable: true })
-  end
-
-  context 'running version differs from installed version' do
-    before do
-      allow_any_instance_of(GeoPgHelper).to receive(:version).and_return(PGVersion.new('expectation'))
-      allow_any_instance_of(GeoPgHelper).to receive(:running_version).and_return(PGVersion.new('reality'))
-    end
-
-    it 'warns the user that a restart is needed' do
-      allow_any_instance_of(GeoPgHelper).to receive(:is_running?).and_return(true)
-      expect(chef_run).to run_ruby_block('warn pending geo-postgresql restart')
-    end
-
-    it 'does not warns the user that a restart is needed when geo-postgres is stopped' do
-      expect(chef_run).not_to run_ruby_block('warn pending geo-postgresql restart')
-    end
-  end
-
-  context 'running version differs from data version' do
-    before do
-      allow_any_instance_of(GeoPgHelper).to receive(:version).and_return(PGVersion.new('expectation'))
-      allow_any_instance_of(GeoPgHelper).to receive(:running_version).and_return(PGVersion.new('expectation'))
-      allow_any_instance_of(GeoPgHelper).to receive(:database_version).and_return(PGVersion.new('reality'))
-    end
-
-    it 'does not warn the user that a restart is needed' do
-      allow_any_instance_of(GeoPgHelper).to receive(:is_running?).and_return(true)
-      expect(chef_run).not_to run_ruby_block('warn pending geo-postgresql restart')
-    end
-  end
-end
diff --git a/spec/chef/gitlab-ee/recipes/geo-secondary_disable_spec.rb b/spec/chef/gitlab-ee/recipes/geo-secondary_disable_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi1lZS9yZWNpcGVzL2dlby1zZWNvbmRhcnlfZGlzYWJsZV9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab-ee/recipes/geo-secondary_disable_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab-ee::geo-secondary_disable' do
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe 'when geo_secondary_role is disabled' do
-    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-    before { stub_gitlab_rb(geo_secondary_role: { enable: false }) }
-
-    it 'removes database_geo.yml symlink' do
-      expect(chef_run).to delete_templatesymlink('Removes database_geo.yml symlink')
-                            .with(link_to: '/var/opt/gitlab/gitlab-rails/etc/database_geo.yml',
-                                  link_from: '/opt/gitlab/embedded/service/gitlab-rails/config/database_geo.yml')
-    end
-  end
-end
diff --git a/spec/chef/gitlab-ee/recipes/geo-secondary_spec.rb b/spec/chef/gitlab-ee/recipes/geo-secondary_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi1lZS9yZWNpcGVzL2dlby1zZWNvbmRhcnlfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab-ee/recipes/geo-secondary_spec.rb
+++ /dev/null
@@ -1,388 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab-ee::geo-secondary' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab-ee::default') }
-  let(:database_geo_yml) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/database_geo.yml') }
-  let(:database_geo_yml_content) { ChefSpec::Renderer.new(chef_run, database_geo_yml).content }
-  let(:generated_yml_content) { YAML.safe_load(database_geo_yml_content, [], [], true, symbolize_names: true) }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe 'when geo_secondary_role is disabled' do
-    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-    before { stub_gitlab_rb(geo_secondary_role: { enable: false }) }
-
-    it 'does not render the geo-secondary files' do
-      expect(chef_run).not_to render_file('/opt/gitlab/embedded/service/gitlab-rails/config/database_geo.yml')
-      expect(chef_run).not_to render_file('/var/opt/gitlab/gitlab-rails/etc/database_geo.yml')
-    end
-  end
-
-  describe 'when geo_secondary_role is disabled but geo-postgresql enabled' do
-    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-    before do
-      stub_gitlab_rb(geo_secondary_role: { enable: false },
-                     geo_postgresql: { enable: true })
-    end
-
-    it 'does not render the geo-secondary files' do
-      expect(chef_run).not_to render_file('/opt/gitlab/embedded/service/gitlab-rails/config/database_geo.yml')
-      expect(chef_run).not_to render_file('/var/opt/gitlab/gitlab-rails/etc/database_geo.yml')
-    end
-  end
-
-  describe 'when gitlab_rails is disabled, but geo_secondary_role is enabled' do
-    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-    before do
-      stub_gitlab_rb(geo_secondary_role: { enable: true },
-                     gitlab_rails: { enable: false })
-    end
-
-    it 'does not render the geo-secondary files' do
-      expect(chef_run).not_to create_templatesymlink('Create a database_geo.yml and create a symlink to Rails root')
-    end
-  end
-
-  describe 'when gitlab_rails is enabled' do
-    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-    before do
-      stub_gitlab_rb(geo_secondary_role: { enable: true },
-                     geo_postgresql: { enable: true },
-                     puma: { enable: false },
-                     sidekiq: { enable: false },
-                     geo_logcursor: { enable: false },
-                     gitlab_rails: { enable: true })
-    end
-
-    it 'allows gitlab_rails to be overriden' do
-      expect(chef_run.node['gitlab']['gitlab-rails']['enable']).to be true
-    end
-  end
-
-  context 'when gitaly is enabled' do
-    describe 'when gitlab_rails enable is not set' do
-      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-      before do
-        stub_gitlab_rb(geo_secondary_role: { enable: true },
-                       geo_secondary: { enable: true },
-                       geo_postgresql: { enable: false },
-                       puma: { enable: false },
-                       sidekiq: { enable: false },
-                       geo_logcursor: { enable: false },
-                       gitaly: { enable: true })
-      end
-
-      it 'ensures gitlab_rails is enabled' do
-        chef_run
-        expect(Gitlab['gitlab_rails']['enable']).to be true
-      end
-    end
-
-    describe 'when gitlab_rails enable is provided' do
-      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-      before do
-        stub_gitlab_rb(geo_secondary_role: { enable: true },
-                       geo_secondary: { enable: true },
-                       geo_postgresql: { enable: false },
-                       puma: { enable: false },
-                       sidekiq: { enable: false },
-                       geo_logcursor: { enable: false },
-                       gitaly: { enable: true },
-                       gitlab_rails: { enable: false })
-      end
-
-      it 'does not override gitlab_rails enable' do
-        chef_run
-        expect(Gitlab['gitlab_rails']['enable']).to be false
-      end
-    end
-  end
-
-  context 'when geo_secondary_role is enabled but geo-postgresql is disabled' do
-    before do
-      stub_gitlab_rb(geo_secondary_role: { enable: true },
-                     geo_postgresql: { enable: false },
-                     geo_secondary: {
-                       db_host: '1.1.1.1',
-                       db_password: 'password',
-                       db_port: '5431'
-                     })
-    end
-
-    describe 'migrations' do
-      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-      it 'includes the database migration recipe' do
-        expect(chef_run).to include_recipe('gitlab-ee::geo_database_migrations')
-      end
-    end
-
-    describe 'database.yml' do
-      it 'creates the database_geo.yml template not using many database structure' do
-        expect(generated_yml_content).to eq(
-          production: {
-            adapter: 'postgresql',
-            application_name: nil,
-            collation: nil,
-            connect_timeout: nil,
-            database: 'gitlabhq_geo_production',
-            encoding: 'unicode',
-            host: '1.1.1.1',
-            keepalives: nil,
-            keepalives_count: nil,
-            keepalives_idle: nil,
-            keepalives_interval: nil,
-            load_balancing: {
-              hosts: []
-            },
-            password: 'password',
-            port: 5431,
-            prepared_statements: false,
-            socket: nil,
-            sslca: nil,
-            sslcompression: 0,
-            sslmode: nil,
-            sslrootcert: nil,
-            statement_limit: nil,
-            tcp_user_timeout: nil,
-            username: 'gitlab_geo',
-            variables: {
-              statement_timeout: nil,
-            }
-          }
-        )
-      end
-
-      context 'when SSL compression is enabled' do
-        before do
-          stub_gitlab_rb(geo_secondary: { db_sslcompression: 1 })
-        end
-
-        it 'uses provided value in database.yml' do
-          expect(generated_yml_content[:production][:sslcompression]).to eq(1)
-        end
-      end
-    end
-  end
-
-  context 'when geo_secondary_role is enabled' do
-    before do
-      stub_gitlab_rb(geo_secondary_role: { enable: true })
-
-      # Make sure other calls to `File.symlink?` are allowed.
-      allow(File).to receive(:symlink?).and_call_original
-      %w(
-        alertmanager
-        gitlab-exporter
-        gitlab-workhorse
-        logrotate
-        nginx
-        node-exporter
-        postgres-exporter
-        postgresql
-        prometheus
-        redis
-        redis-exporter
-        sidekiq
-        puma
-        gitaly
-        geo-postgresql
-        gitlab-pages
-        geo-logcursor
-        patroni
-      ).map { |svc| stub_should_notify?(svc, true) }
-    end
-
-    describe 'database.yml' do
-      let(:templatesymlink) { chef_run.templatesymlink('Create a database_geo.yml and create a symlink to Rails root') }
-
-      it 'creates the database_geo.yml template not using many database structure' do
-        expect(generated_yml_content).to eq(
-          production: {
-            adapter: 'postgresql',
-            application_name: nil,
-            collation: nil,
-            connect_timeout: nil,
-            database: 'gitlabhq_geo_production',
-            encoding: 'unicode',
-            host: '/var/opt/gitlab/geo-postgresql',
-            keepalives: nil,
-            keepalives_count: nil,
-            keepalives_idle: nil,
-            keepalives_interval: nil,
-            load_balancing: {
-              hosts: []
-            },
-            password: nil,
-            port: 5431,
-            prepared_statements: false,
-            socket: nil,
-            sslca: nil,
-            sslcompression: 0,
-            sslmode: nil,
-            sslrootcert: nil,
-            statement_limit: nil,
-            tcp_user_timeout: nil,
-            username: 'gitlab_geo',
-            variables: {
-              statement_timeout: nil,
-            }
-          }
-        )
-      end
-
-      it 'template triggers dependent services notifications' do
-        expect(templatesymlink).to notify('ruby_block[Restart geo-secondary dependent services]').to(:run).delayed
-      end
-    end
-
-    describe 'PostgreSQL gitlab-geo.conf' do
-      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-      let(:geo_conf) { '/var/opt/gitlab/postgresql/data/gitlab-geo.conf' }
-      let(:postgresql_conf) { '/var/opt/gitlab/postgresql/data/postgresql.conf' }
-
-      context 'when postgresql enabled on the node' do
-        it 'renders gitlab-geo.conf' do
-          expect(chef_run).to render_file(geo_conf)
-        end
-      end
-
-      context 'when postgresql disabled on the node' do
-        before { stub_gitlab_rb(postgresql: { enable: false }) }
-
-        it 'does not render gitlab-geo.conf' do
-          expect(chef_run).not_to render_file(geo_conf)
-        end
-      end
-    end
-
-    describe 'Restart geo-secondary dependent services' do
-      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-      let(:ruby_block) { chef_run.ruby_block('Restart geo-secondary dependent services') }
-
-      it 'does not run' do
-        expect(chef_run).not_to run_ruby_block('Restart geo-secondary dependent services')
-        expect(ruby_block).to do_nothing
-      end
-
-      it 'ruby_block triggers dependent services notifications' do
-        allow(ruby_block).to receive(:notifies)
-        ruby_block.block.call
-
-        %w(
-          puma
-          geo-logcursor
-        ).each do |svc|
-          expect(ruby_block).to have_received(:notifies).with(:restart, "runit_service[#{svc}]")
-        end
-        expect(ruby_block).to have_received(:notifies).with(:restart, "sidekiq_service[sidekiq]")
-      end
-    end
-
-    describe 'include_recipe' do
-      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-      it 'includes the Geo tracking DB recipe' do
-        expect(chef_run).to include_recipe('gitlab-ee::geo-postgresql')
-      end
-
-      it 'includes the Geo secondary recipes for Rails' do
-        expect(chef_run).to include_recipe('gitlab-ee::geo-secondary')
-        expect(chef_run).to include_recipe('gitlab-ee::geo_database_migrations')
-      end
-
-      it 'does not include the Geo database migrations recipe if Rails not needed' do
-        stub_gitlab_rb(geo_secondary_role: { enable: true },
-                       nginx: { enable: false },
-                       puma: { enable: false },
-                       sidekiq: { enable: false },
-                       gitaly: { enable: false },
-                       postgresql: { enable: false },
-                       geo_logcursor: { enable: false },
-                       redis: { enable: true })
-
-        expect(chef_run).not_to include_recipe('gitlab-ee::geo_database_migrations')
-      end
-    end
-
-    describe 'migrations' do
-      let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-      it 'runs the migrations' do
-        expect(chef_run).to run_rails_migration('gitlab-geo tracking')
-      end
-    end
-
-    describe 'rails_needed?' do
-      let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab-ee::default') }
-
-      context 'manually enabled services' do
-        before do
-          stub_gitlab_rb(
-            # Everything but puma is disabled
-            puma: { enable: true },
-            sidekiq: { enable: false },
-            geo_logcursor: { enable: false },
-            gitaly: { enable: false }
-          )
-        end
-
-        it 'should need rails' do
-          expect(chef_run).to include_recipe('gitlab::gitlab-rails')
-        end
-      end
-
-      context 'manually disabled services' do
-        before do
-          stub_gitlab_rb(
-            gitaly: { enable: false },
-            puma: { enable: false },
-            sidekiq: { enable: false },
-            geo_logcursor: { enable: false }
-          )
-        end
-
-        it 'should not need rails' do
-          expect(chef_run).not_to include_recipe('gitlab::gitlab-rails')
-        end
-      end
-    end
-  end
-
-  context 'puma worker_processes' do
-    let(:chef_run) do
-      ChefSpec::SoloRunner.new do |node|
-        node.automatic['cpu']['total'] = 16
-        node.automatic['memory']['total'] = '8388608KB' # 8GB
-      end.converge('gitlab-ee::default')
-    end
-
-    it 'reduces the number of puma workers on secondary node' do
-      stub_gitlab_rb(geo_secondary_role: { enable: true })
-
-      expect(chef_run.node['gitlab']['puma']['worker_processes']).to eq 5
-    end
-
-    it 'uses the specified number of puma workers' do
-      stub_gitlab_rb(geo_secondary_role: { enable: true },
-                     puma: { worker_processes: 1 })
-
-      expect(chef_run.node['gitlab']['puma']['worker_processes']).to eq 1
-    end
-
-    it 'does not reduce the number of puma workers on primary node' do
-      stub_gitlab_rb(geo_primary_role: { enable: true })
-
-      expect(chef_run.node['gitlab']['puma']['worker_processes']).to eq 6
-    end
-  end
-end
diff --git a/spec/chef/gitlab-ee/recipes/geo_database_migrations_spec.rb b/spec/chef/gitlab-ee/recipes/geo_database_migrations_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi1lZS9yZWNpcGVzL2dlb19kYXRhYmFzZV9taWdyYXRpb25zX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab-ee/recipes/geo_database_migrations_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require 'chef_helper'
-
-# NOTE: These specs do not verify whether the code actually ran
-# Nor whether the resource inside of the recipe was notified correctly.
-# At this moment they only verify whether the expected commands are passed
-# to the bash block.
-#
-
-RSpec.describe 'gitlab-ee::geo-database-migrations' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when migration should run' do
-    before do
-      stub_default_not_listening?(false)
-      stub_gitlab_rb(geo_secondary_role: { enable: true })
-
-      # Make sure other calls to `File.symlink?` are allowed.
-      allow(File).to receive(:symlink?).and_call_original
-      %w(
-        alertmanager
-        gitlab-exporter
-        gitlab-workhorse
-        logrotate
-        nginx
-        node-exporter
-        postgres-exporter
-        postgresql
-        prometheus
-        redis
-        redis-exporter
-        sidekiq
-        puma
-        gitaly
-        geo-postgresql
-        gitlab-pages
-        geo-logcursor
-      ).map { |svc| stub_should_notify?(svc, true) }
-    end
-
-    it 'runs the migrations with expected attributes' do
-      expect(chef_run).to run_rails_migration('gitlab-geo tracking') do |resource|
-        expect(resource.dependent_services).to include_array(%w(runit_service[puma] sidekiq_service[sidekiq]))
-        expect(resource.rake_task).to eq('geo:db:migrate')
-        expect(resource.logfile_prefix).to eq('gitlab-geo-db-migrate')
-        expect(resource.helper).to be_a(GitlabGeoHelper)
-      end
-    end
-
-    it 'starts geo-postgresql if its not running' do
-      stub_not_listening?('geo-postgresql', true)
-
-      expect(chef_run.rails_migration('gitlab-geo tracking')).to notify('execute[start geo-postgresql]').to(:run)
-    end
-  end
-
-  context 'with auto_migrate off' do
-    it 'skips running the migrations' do
-      stub_gitlab_rb({
-                       geo_secondary_role: { enable: true },
-                       geo_secondary: { auto_migrate: false }
-                     })
-
-      expect(chef_run).to include_recipe('gitlab-ee::geo_database_migrations')
-      expect(chef_run).not_to run_rails_migration('gitlab-geo tracking')
-    end
-  end
-end
diff --git a/spec/chef/gitlab-ee/recipes/geo_spec.rb b/spec/chef/gitlab-ee/recipes/geo_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi1lZS9yZWNpcGVzL2dlb19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab-ee/recipes/geo_spec.rb
+++ /dev/null
@@ -1,166 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab-ee::geo' do
-  let(:node) { chef_run.node }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  shared_examples 'default services' do
-    it 'remain enabled', :aggregate_failures do
-      chef_run
-
-      default_services = Services.find_by_group(Services::DEFAULT_GROUP)
-      omnibus_helper = OmnibusHelper.new(node)
-
-      expect(default_services.count).to be > 0
-      default_services.each do |service|
-        expect(omnibus_helper.service_enabled?(service.tr('_', '-'))).to be(true), "#{service} was not enabled"
-      end
-    end
-  end
-
-  context 'when geo_primary_role enabled' do
-    cached(:chef_run) do
-      RSpec::Mocks.with_temporary_scope do
-        stub_gitlab_rb(geo_primary_role: { enable: true })
-      end
-      ChefSpec::SoloRunner.converge('gitlab-ee::default')
-    end
-
-    it_behaves_like 'default services'
-
-    context 'in geo_logcursor settings' do
-      it 'is not enabled' do
-        expect(node['gitlab']['geo-logcursor']['enable']).to eq(nil)
-      end
-    end
-
-    context 'in postgres settings' do
-      let(:config_attrs) { node['postgresql'] }
-
-      it 'defines sql_replication_user' do
-        expect(config_attrs['sql_replication_user']).to eq('gitlab_replicator')
-      end
-
-      it 'defines wal_level' do
-        expect(config_attrs['wal_level']).to eq('hot_standby')
-      end
-
-      it 'defines wal_log_hints' do
-        expect(config_attrs['wal_log_hints']).to eq('off')
-      end
-
-      it 'defines max_wal_senders' do
-        expect(config_attrs['max_wal_senders']).to eq(10)
-      end
-
-      it 'defines wal_keep_segments' do
-        expect(config_attrs['wal_keep_segments']).to eq(50)
-      end
-
-      it 'defines max_replication_slots' do
-        expect(config_attrs['max_replication_slots']).to eq(1)
-      end
-
-      it 'defines hot_standby' do
-        expect(config_attrs['hot_standby']).to eq('on')
-      end
-    end
-  end
-
-  context 'geo_secondary_role enabled' do
-    cached(:chef_run) do
-      RSpec::Mocks.with_temporary_scope do
-        stub_gitlab_rb(geo_secondary_role: { enable: true })
-      end
-      ChefSpec::SoloRunner.converge('gitlab-ee::default')
-    end
-
-    it_behaves_like 'default services'
-
-    context 'in geo_postgres settings' do
-      it 'is enabled' do
-        expect(node['gitlab']['geo-postgresql']['enable']).to eq(true)
-      end
-    end
-
-    context 'in geo_logcursor settings' do
-      it 'is enabled' do
-        expect(node['gitlab']['geo-logcursor']['enable']).to eq(true)
-      end
-    end
-
-    context 'in postgres settings' do
-      let(:config_attrs) { node['postgresql'] }
-
-      it 'defines wal_level' do
-        expect(config_attrs['wal_level']).to eq('hot_standby')
-      end
-
-      it 'defines wal_log_hints' do
-        expect(config_attrs['wal_log_hints']).to eq('off')
-      end
-
-      it 'defines max_wal_senders' do
-        expect(config_attrs['max_wal_senders']).to eq(10)
-      end
-
-      it 'defines wal_keep_segments' do
-        expect(config_attrs['wal_keep_segments']).to eq(10)
-      end
-
-      it 'defines max_replication_slots' do
-        expect(config_attrs['max_replication_slots']).to eq(0)
-      end
-
-      it 'defines hot_standby' do
-        expect(config_attrs['hot_standby']).to eq('on')
-      end
-
-      it 'defines standby settings' do
-        expect(config_attrs['max_standby_archive_delay']).to eq('60s')
-        expect(config_attrs['max_standby_streaming_delay']).to eq('60s')
-      end
-    end
-
-    context 'postgresql 13' do
-      let(:runtime_conf) { '/var/opt/gitlab/geo-postgresql/data/runtime.conf' }
-
-      before do
-        allow_any_instance_of(GeoPgHelper).to receive(:version).and_return(PGVersion.new('13.0'))
-        allow_any_instance_of(GeoPgHelper).to receive(:database_version).and_return(PGVersion.new('13.0'))
-      end
-
-      it 'configures wal_keep_size instead of wal_keep_segments' do
-        expect(chef_run).to render_file(runtime_conf).with_content { |content|
-          expect(content).to include("wal_keep_size")
-          expect(content).not_to include("wal_keep_segments")
-        }
-      end
-    end
-
-    context 'postgresql 12' do
-      let(:runtime_conf) { '/var/opt/gitlab/geo-postgresql/data/runtime.conf' }
-
-      before do
-        allow_any_instance_of(GeoPgHelper).to receive(:version).and_return(PGVersion.new('12.0'))
-        allow_any_instance_of(GeoPgHelper).to receive(:database_version).and_return(PGVersion.new('12.0'))
-      end
-
-      it 'configures wal_keep_segments instead of wal_keep_size' do
-        expect(chef_run).to render_file(runtime_conf).with_content { |content|
-          expect(content).to include("wal_keep_segments")
-          expect(content).to_not include("wal_keep_size")
-        }
-      end
-    end
-
-    context 'in gitlab-rails' do
-      it 'disables auto_migrate' do
-        expect(node['gitlab']['gitlab-rails']['auto_migrate']).to eq(false)
-      end
-    end
-  end
-end
diff --git a/spec/chef/gitlab-ee/recipes/sentinel_spec.rb b/spec/chef/gitlab-ee/recipes/sentinel_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi1lZS9yZWNpcGVzL3NlbnRpbmVsX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab-ee/recipes/sentinel_spec.rb
+++ /dev/null
@@ -1,186 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::redis' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(sentinel_service runit_service)).converge('gitlab-ee::default') }
-  let(:redis_master_ip) { '1.1.1.1' }
-  let(:redis_announce_ip) { '10.10.10.10' }
-  let(:redis_master_password) { 'blahblahblah' }
-  let(:sentinel_conf) { '/var/opt/gitlab/sentinel/sentinel.conf' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe 'When sentinel is disabled' do
-    before do
-      stub_gitlab_rb(
-        redis: {
-          master_ip: redis_master_ip,
-          announce_ip: redis_announce_ip,
-          master_password: redis_master_password
-        },
-        redis_sentinel_role: {
-          enable: false,
-        }
-      )
-    end
-
-    it_behaves_like 'disabled runit service', 'sentinel', 'root', 'root'
-  end
-
-  describe 'When sentinel is enabled' do
-    context 'default values' do
-      before do
-        stub_gitlab_rb(
-          redis: {
-            master_ip: redis_master_ip,
-            announce_ip: redis_announce_ip,
-            master_password: redis_master_password
-          },
-          redis_sentinel_role: {
-            enable: true,
-          }
-        )
-      end
-      it 'creates redis user and group' do
-        expect(chef_run).to create_account('user and group for sentinel').with(username: 'gitlab-redis', groupname: 'gitlab-redis')
-      end
-
-      it 'renders sentinel config file with default values' do
-        expect(chef_run).to render_file('/var/opt/gitlab/sentinel/sentinel.conf')
-          .with_content { |content|
-            expect(content).to match(%r{bind 0.0.0.0})
-            expect(content).to match(%r{port 26379})
-            expect(content).to match(%r{sentinel announce-ip 10.10.10.10})
-            expect(content).to match(%r{sentinel monitor gitlab-redis 1.1.1.1 6379 1})
-            expect(content).to match(%r{sentinel down-after-milliseconds gitlab-redis 10000})
-            expect(content).to match(%r{sentinel failover-timeout gitlab-redis 60000})
-            expect(content).to match(%r{sentinel auth-pass gitlab-redis blahblahblah})
-            expect(content).not_to match(%r{^tls})
-            expect(content).to match(%r{SENTINEL resolve-hostnames no})
-            expect(content).to match(%r{SENTINEL announce-hostnames no})
-          }
-      end
-
-      it_behaves_like 'enabled runit service', 'sentinel', 'root', 'root'
-
-      context 'user overrides sentinel_use_hostnames' do
-        before do
-          stub_gitlab_rb(
-            sentinel: {
-              use_hostnames: true
-            }
-          )
-        end
-
-        it 'uses hostnames' do
-          expect(chef_run).to render_file(sentinel_conf).with_content { |content|
-            expect(content).to match(%r{SENTINEL resolve-hostnames yes})
-            expect(content).to match(%r{SENTINEL announce-hostnames yes})
-          }
-        end
-      end
-    end
-
-    context 'user specified values' do
-      before do
-        stub_gitlab_rb(
-          redis_sentinel_role: {
-            enable: true,
-          },
-          redis: {
-            username: 'foo',
-            group: 'bar',
-            master_ip: redis_master_ip,
-            announce_ip: 'fake.hostname.local',
-            master_password: redis_master_password
-          }
-        )
-      end
-      it 'creates redis user and group' do
-        expect(chef_run).to create_account('user and group for sentinel').with(username: 'foo', groupname: 'bar')
-      end
-
-      it_behaves_like 'enabled runit service', 'sentinel', 'root', 'root'
-
-      it 'uses hostnames' do
-        expect(chef_run).to render_file(sentinel_conf).with_content { |content|
-          expect(content).to match(%r{SENTINEL resolve-hostnames yes})
-          expect(content).to match(%r{SENTINEL announce-hostnames yes})
-        }
-      end
-
-      context 'user overrides sentinel_use_hostnames' do
-        before do
-          stub_gitlab_rb(
-            sentinel: {
-              use_hostnames: false
-            }
-          )
-        end
-
-        it 'does not use hostnames' do
-          expect(chef_run).to render_file(sentinel_conf).with_content { |content|
-            expect(content).to match(%r{SENTINEL resolve-hostnames no})
-            expect(content).to match(%r{SENTINEL announce-hostnames no})
-          }
-        end
-      end
-    end
-
-    context 'with tls settings specified' do
-      before do
-        stub_gitlab_rb(
-          redis: {
-            master_ip: redis_master_ip,
-            announce_ip: redis_announce_ip,
-            master_password: redis_master_password,
-          },
-          redis_sentinel_role: {
-            enable: true
-          },
-          sentinel: {
-            tls_port: 6380,
-            tls_cert_file: '/etc/gitlab/ssl/redis.crt',
-            tls_key_file: '/etc/gitlab/ssl/redis.key',
-            tls_dh_params_file: '/etc/gitlab/ssl/redis-dhparams',
-            tls_ca_cert_file: '/etc/gitlab/ssl/redis-ca.crt',
-            tls_ca_cert_dir: '/opt/gitlab/embedded/ssl/certs',
-            tls_auth_clients: 'no',
-            tls_replication: 'yes',
-            tls_cluster: 'yes',
-            tls_protocols: 'TLSv1.2 TLSv1.3',
-            tls_ciphers: 'DEFAULT:!MEDIUM',
-            tls_ciphersuites: 'TLS_CHACHA20_POLY1305_SHA256',
-            tls_prefer_server_ciphers: 'yes',
-            tls_session_caching: 'no',
-            tls_session_cache_size: 10000,
-            tls_session_cache_timeout: 120
-          }
-        )
-      end
-
-      it 'renders sentinel config file with specified tls values' do
-        expect(chef_run).to render_file('/var/opt/gitlab/sentinel/sentinel.conf')
-          .with_content { |content|
-            expect(content).to match(%r{^tls-port 6380$})
-            expect(content).to match(%r{^tls-cert-file /etc/gitlab/ssl/redis.crt$})
-            expect(content).to match(%r{^tls-key-file /etc/gitlab/ssl/redis.key$})
-            expect(content).to match(%r{^tls-dh-params-file /etc/gitlab/ssl/redis-dhparams$})
-            expect(content).to match(%r{^tls-ca-cert-file /etc/gitlab/ssl/redis-ca.crt$})
-            expect(content).to match(%r{^tls-ca-cert-dir /opt/gitlab/embedded/ssl/certs$})
-            expect(content).to match(%r{^tls-auth-clients no$})
-            expect(content).to match(%r{^tls-replication yes$})
-            expect(content).to match(%r{^tls-cluster yes$})
-            expect(content).to match(%r{^tls-protocols "TLSv1.2 TLSv1.3"$})
-            expect(content).to match(%r{^tls-ciphers DEFAULT:!MEDIUM$})
-            expect(content).to match(%r{^tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256$})
-            expect(content).to match(%r{^tls-prefer-server-ciphers yes$})
-            expect(content).to match(%r{^tls-session-caching no$})
-            expect(content).to match(%r{^tls-session-cache-size 10000$})
-            expect(content).to match(%r{^tls-session-cache-timeout 120$})
-          }
-      end
-    end
-  end
-end
diff --git a/spec/chef/gitlab-ee/resources/pgbouncer_user_spec.rb b/spec/chef/gitlab-ee/resources/pgbouncer_user_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi1lZS9yZXNvdXJjZXMvcGdib3VuY2VyX3VzZXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab-ee/resources/pgbouncer_user_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require 'chef_helper'
-RSpec.describe 'pgbouncer_user' do
-  let(:runner) { ChefSpec::SoloRunner.new(step_into: ['pgbouncer_user']) }
-
-  context 'create geo' do
-    let(:chef_run) { runner.converge('test_gitlab_ee::pgbouncer_user_create_geo') }
-    it 'should create the pgbouncer user' do
-      expect(chef_run).to create_postgresql_user('pgbouncer-geo').with(
-        helper: an_instance_of(GeoPgHelper),
-        username: 'pgbouncer-geo',
-        password: 'md5fakepassword-geo'
-      )
-    end
-
-    it 'should create the pg_shadow_lookup function' do
-      postgresql_user = chef_run.postgresql_user('pgbouncer-geo')
-      expect(postgresql_user).to notify('execute[Add pgbouncer auth function]')
-      resource = chef_run.execute('Add pgbouncer auth function')
-      expect(resource.command).to match(%r{^/opt/gitlab/bin/gitlab-geo-psql -d fakedb-geo})
-    end
-
-    it 'should not try and recreate the function' do
-      expect(chef_run).not_to run_execute('Add pgbouncer auth function')
-    end
-  end
-
-  context 'create rails' do
-    let(:chef_run) { runner.converge('test_gitlab_ee::pgbouncer_user_create_rails') }
-    it 'should create the pgbouncer user' do
-      expect(chef_run).to create_postgresql_user('pgbouncer-rails').with(
-        helper: an_instance_of(PgHelper),
-        username: 'pgbouncer-rails',
-        password: 'md5fakepassword-rails'
-      )
-    end
-
-    it 'should create the pg_shadow_lookup function' do
-      postgresql_user = chef_run.postgresql_user('pgbouncer-rails')
-      expect(postgresql_user).to notify('execute[Add pgbouncer auth function]')
-      resource = chef_run.execute('Add pgbouncer auth function')
-      expect(resource.command).to match(%r{^/opt/gitlab/bin/gitlab-psql -d fakedb-rails})
-    end
-
-    it 'should not try and recreate the function' do
-      expect(chef_run).not_to run_execute('Add pgbouncer auth function')
-    end
-  end
-end
diff --git a/spec/chef/gitlab-pages/recipes/gitlab-pages_spec.rb b/spec/chef/gitlab-pages/recipes/gitlab-pages_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi1wYWdlcy9yZWNpcGVzL2dpdGxhYi1wYWdlc19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab-pages/recipes/gitlab-pages_spec.rb
+++ /dev/null
@@ -1,333 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-pages' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service env_dir)).converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow(Etc).to receive(:getpwnam).with('git').and_return(spy('getpwnam spy', uid: 1000, gid: 1000))
-  end
-
-  context 'with default values' do
-    it 'does not include Pages recipe' do
-      expect(chef_run).not_to include_recipe('gitlab-pages::enable')
-      expect(chef_run).to include_recipe('gitlab-pages::disable')
-    end
-  end
-
-  context 'with Pages enabled' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'https://gitlab.example.com',
-        pages_external_url: 'https://pages.example.com'
-      )
-    end
-
-    it 'includes Pages recipe' do
-      expect(chef_run).to include_recipe('gitlab-pages::enable')
-    end
-
-    it 'creates a VERSION file and restarts the service' do
-      expect(chef_run).to create_version_file('Create version file for Gitlab Pages').with(
-        version_file_path: '/var/opt/gitlab/gitlab-pages/VERSION',
-        version_check_cmd: '/opt/gitlab/embedded/bin/gitlab-pages --version'
-      )
-
-      expect(chef_run.version_file('Create version file for Gitlab Pages')).to notify('runit_service[gitlab-pages]').to(:restart)
-    end
-
-    it 'renders the env dir files' do
-      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-pages/env/SSL_CERT_DIR')
-        .with_content('/opt/gitlab/embedded/ssl/certs')
-    end
-
-    it 'renders the pages service run file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-pages/run").with_content(%r{-config="/var/opt/gitlab/gitlab-pages/gitlab-pages-config"})
-    end
-
-    it 'renders the pages log run file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-pages/log/run").with_content(%r{exec svlogd /var/log/gitlab/gitlab-pages})
-    end
-
-    it 'deletes old admin.secret file' do
-      expect(chef_run).to delete_file("/var/opt/gitlab/gitlab-pages/admin.secret")
-    end
-
-    it 'renders pages config file' do
-      default_content = <<~EOS
-       pages-domain=pages.example.com
-       pages-root=/var/opt/gitlab/gitlab-rails/shared/pages
-       api-secret-key=/var/opt/gitlab/gitlab-pages/.gitlab_pages_secret
-       listen-proxy=localhost:8090
-       log-format=json
-       use-http2=true
-       artifacts-server=https://gitlab.example.com/api/v4
-       artifacts-server-timeout=10
-       gitlab-server=https://gitlab.example.com
-      EOS
-
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-pages/gitlab-pages-config").with_content(default_content)
-    end
-
-    it 'skips rendering the auth settings when access control is disabled' do
-      stub_gitlab_rb(
-        external_url: 'https://gitlab.example.com',
-        pages_external_url: 'https://pages.example.com',
-        gitlab_pages: {
-          access_control: false,
-          auth_secret: 'auth_secret'
-        }
-      )
-
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-pages/gitlab-pages-config").with_content { |content|
-        expect(content).not_to match(%r{auth-secret=auth_secret})
-      }
-    end
-
-    context 'when access control is enabled' do
-      context 'when access control secrets are not specified' do
-        before do
-          stub_gitlab_rb(
-            external_url: 'https://gitlab.example.com',
-            pages_external_url: 'https://pages.example.com',
-            gitlab_pages: {
-              access_control: true,
-              auth_secret: 'auth_secret'
-            }
-          )
-
-          allow(GitlabPages).to receive(:authorize_with_gitlab) {
-            Gitlab['gitlab_pages']['gitlab_secret'] = 'app_secret'
-            Gitlab['gitlab_pages']['gitlab_id'] = 'app_id'
-          }
-        end
-
-        it 'authorizes Pages with GitLab' do
-          expect(chef_run).to run_ruby_block('authorize pages with gitlab')
-            .at_converge_time
-          expect(chef_run).to run_ruby_block('re-populate GitLab Pages configuration options')
-            .at_converge_time
-          expect(GitlabPages).to receive(:authorize_with_gitlab)
-
-          chef_run.ruby_block('authorize pages with gitlab').block.call
-          chef_run.ruby_block('re-populate GitLab Pages configuration options').block.call
-
-          expect(chef_run).to render_file("/var/opt/gitlab/gitlab-pages/gitlab-pages-config").with_content { |content|
-            expect(content).to match(%r{auth-client-id=app_id})
-            expect(content).to match(%r{auth-client-secret=app_secret})
-            expect(content).to match(%r{auth-redirect-uri=https://projects.pages.example.com/auth})
-            expect(content).to match(%r{auth-secret=auth_secret})
-          }
-        end
-      end
-
-      context 'when access control secrets are specified' do
-        before do
-          stub_gitlab_rb(
-            external_url: 'https://gitlab.example.com',
-            pages_external_url: 'https://pages.example.com',
-            gitlab_pages: {
-              access_control: true,
-              gitlab_id: 'app_id',
-              gitlab_secret: 'app_secret',
-              auth_secret: 'auth_secret',
-              auth_redirect_uri: 'https://projects.pages.example.com/auth',
-              auth_scope: 'read_api'
-            }
-          )
-        end
-
-        it 'does not attempt to authorize with GitLab' do
-          expect(chef_run).not_to run_ruby_block('authorize pages with gitlab')
-        end
-
-        it 'renders pages config file' do
-          expect(chef_run).to render_file("/var/opt/gitlab/gitlab-pages/gitlab-pages-config").with_content { |content|
-            expect(content).to match(%r{auth-client-id=app_id})
-            expect(content).to match(%r{auth-client-secret=app_secret})
-            expect(content).to match(%r{auth-redirect-uri=https://projects.pages.example.com/auth})
-            expect(content).to match(%r{auth-secret=auth_secret})
-            expect(content).to match(%r{auth-scope=read_api})
-          }
-        end
-      end
-    end
-
-    context 'with custom port' do
-      before do
-        stub_gitlab_rb(
-          pages_external_url: 'https://pages.example.com:8443',
-          gitlab_pages: {
-            access_control: true
-          }
-        )
-      end
-
-      it 'sets the correct port number' do
-        expect(chef_run).to render_file("/var/opt/gitlab/gitlab-pages/gitlab-pages-config").with_content { |content|
-          expect(content).to match(%r{auth-redirect-uri=https://projects.pages.example.com:8443/auth})
-        }
-      end
-    end
-
-    context 'with custom values' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          pages_external_url: 'https://pages.example.com',
-          gitlab_pages: {
-            external_http: ['external_pages.example.com', 'localhost:9000'],
-            external_https: ['external_pages.example.com', 'localhost:9001'],
-            external_https_proxyv2: ['external_pages.example.com', 'localhost:9002'],
-            metrics_address: 'localhost:1234',
-            redirect_http: true,
-            dir: '/var/opt/gitlab/pages',
-            cert: '/etc/gitlab/pages.crt',
-            artifacts_server_url: "https://gitlab.elsewhere.com/api/v5",
-            artifacts_server_timeout: 60,
-            status_uri: '/@status',
-            max_connections: 7500,
-            max_uri_length: 2048,
-            propagate_correlation_id: true,
-            log_format: 'text',
-            log_verbose: true,
-            gitlab_id: 'app_id',
-            gitlab_secret: 'app_secret',
-            auth_secret: 'auth_secret',
-            auth_redirect_uri: 'https://projects.pages.example.com/auth',
-            auth_scope: 'read_api',
-            access_control: true,
-            insecure_ciphers: true,
-            tls_min_version: "tls1.0",
-            tls_max_version: "tls1.2",
-            sentry_enabled: true,
-            sentry_dsn: 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40',
-            sentry_environment: 'production',
-            headers: ['X-XSS-Protection: 1; mode=block', 'X-Content-Type-Options: nosniff', 'Test: Header'],
-            server_shutdown_timeout: "30s",
-            gitlab_client_http_timeout: "10s",
-            gitlab_client_jwt_expiry: "30s",
-            zip_cache_expiration: "120s",
-            zip_cache_cleanup: "1m",
-            zip_cache_refresh: "60s",
-            zip_open_timeout: "45s",
-            zip_http_client_timeout: "30m",
-            internal_gitlab_server: "https://int.gitlab.example.com",
-            gitlab_cache_expiry: "1m",
-            gitlab_cache_refresh: "500ms",
-            gitlab_cache_cleanup: "100ms",
-            gitlab_retrieval_timeout: "3s",
-            gitlab_retrieval_interval: "500ms",
-            gitlab_retrieval_retries: 5,
-            rate_limit_source_ip: 100,
-            rate_limit_source_ip_burst: 50,
-            rate_limit_domain: 1000,
-            rate_limit_domain_burst: 10000,
-            rate_limit_tls_source_ip: 101,
-            rate_limit_tls_source_ip_burst: 51,
-            rate_limit_tls_domain: 1001,
-            rate_limit_tls_domain_burst: 10001,
-            enable_disk: true,
-            env: {
-              GITLAB_CONTINUOUS_PROFILING: "stackdriver?service=gitlab-pages",
-            },
-          }
-        )
-      end
-
-      it 'renders pages config file in the specified directory' do
-        expected_content = <<~EOS
-            pages-domain=pages.example.com
-            pages-root=/var/opt/gitlab/gitlab-rails/shared/pages
-            api-secret-key=/var/opt/gitlab/pages/.gitlab_pages_secret
-            auth-client-id=app_id
-            auth-redirect-uri=https://projects.pages.example.com/auth
-            auth-client-secret=app_secret
-            auth-secret=auth_secret
-            auth-scope=read_api
-            zip-cache-expiration=120s
-            zip-cache-cleanup=1m
-            zip-cache-refresh=60s
-            zip-open-timeout=45s
-            zip-http-client-timeout=30m
-            listen-proxy=localhost:8090
-            metrics-address=localhost:1234
-            pages-status=/@status
-            max-conns=7500
-            max-uri-length=2048
-            propagate-correlation-id=true
-            log-format=text
-            log-verbose
-            sentry-dsn=https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40
-            sentry-environment=production
-            redirect-http=true
-            use-http2=true
-            artifacts-server=https://gitlab.elsewhere.com/api/v5
-            artifacts-server-timeout=60
-            gitlab-server=https://gitlab.example.com
-            internal-gitlab-server=https://int.gitlab.example.com
-            insecure-ciphers
-            tls-min-version=tls1.0
-            tls-max-version=tls1.2
-            server-shutdown-timeout=30s
-            gitlab-client-http-timeout=10s
-            gitlab-client-jwt-expiry=30s
-            listen-http=external_pages.example.com,localhost:9000
-            listen-https=external_pages.example.com,localhost:9001
-            listen-https-proxyv2=external_pages.example.com,localhost:9002
-            root-cert=/etc/gitlab/pages.crt
-            root-key=/etc/gitlab/ssl/pages.example.com.key
-            gitlab-cache-expiry=1m
-            gitlab-cache-refresh=500ms
-            gitlab-cache-cleanup=100ms
-            gitlab-retrieval-timeout=3s
-            gitlab-retrieval-timeout=500ms
-            gitlab-retrieval-retries=5
-            enable-disk=true
-            rate-limit-source-ip=100
-            rate-limit-source-ip-burst=50
-            rate-limit-domain=1000
-            rate-limit-domain-burst=10000
-            rate-limit-tls-source-ip=101
-            rate-limit-tls-source-ip-burst=51
-            rate-limit-tls-domain=1001
-            rate-limit-tls-domain-burst=10001
-        EOS
-
-        expect(chef_run).to render_file("/var/opt/gitlab/pages/gitlab-pages-config").with_content(expected_content)
-      end
-
-      it 'specifies headers as arguments in the run file' do
-        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-pages/run").with_content(%r{-header="X-XSS-Protection: 1; mode=block"})
-        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-pages/run").with_content(%r{-header="X-Content-Type-Options: nosniff"})
-        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-pages/run").with_content(%r{-header="Test: Header"})
-      end
-
-      it 'renders the env dir files' do
-        expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-pages/env/GITLAB_CONTINUOUS_PROFILING")
-          .with_content('stackdriver?service=gitlab-pages')
-        expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-pages/env/SSL_CERT_DIR")
-          .with_content('/opt/gitlab/embedded/ssl/certs')
-      end
-    end
-
-    describe 'logrotate settings' do
-      context 'default values' do
-        it_behaves_like 'configured logrotate service', 'gitlab-pages', 'git', 'git'
-      end
-
-      context 'specified username and group' do
-        before do
-          stub_gitlab_rb(
-            user: {
-              username: 'foo',
-              group: 'bar'
-            }
-          )
-        end
-
-        it_behaves_like 'configured logrotate service', 'gitlab-pages', 'foo', 'bar'
-      end
-    end
-  end
-end
diff --git a/spec/chef/gitlab/libraries/account_helper_spec.rb b/spec/chef/gitlab/libraries/account_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi9saWJyYXJpZXMvYWNjb3VudF9oZWxwZXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab/libraries/account_helper_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe AccountHelper do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-  it 'returns a list of users' do
-    expect(AccountHelper.new(chef_run.node).users).to eq(
-      %w(git gitlab-www gitlab-redis gitlab-psql mattermost registry gitlab-prometheus gitlab-consul)
-    )
-  end
-
-  it 'returns a list of groups' do
-    expect(AccountHelper.new(chef_run.node).groups).to eq(
-      %w(git gitlab-www gitlab-redis gitlab-psql mattermost registry gitlab-consul gitlab-prometheus)
-    )
-  end
-end
diff --git a/spec/chef/gitlab/libraries/rails_migration_helper_spec.rb b/spec/chef/gitlab/libraries/rails_migration_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi9saWJyYXJpZXMvcmFpbHNfbWlncmF0aW9uX2hlbHBlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab/libraries/rails_migration_helper_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe RailsMigrationHelper do
-  cached(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-  subject(:helper) { described_class.new(chef_run.node) }
-
-  context '#migrated?' do
-    it 'returns true if status file exists and content is "0"' do
-      allow(helper).to receive(:db_migrate_status_file) { File.join(fixture_path, 'migration/successful-migration-status-file') }
-
-      expect(helper.migrated?).to be_truthy
-    end
-
-    it 'returns false when status file doesnt exist' do
-      allow(helper).to receive(:db_migrate_status_file) { File.join(fixture_path, 'migration/non-existent-migration') }
-
-      expect(helper.migrated?).to be_falsey
-    end
-
-    it 'returns false when content in status file is different from "0"' do
-      allow(helper).to receive(:db_migrate_status_file) { File.join(fixture_path, 'migration/failed-migration-status-file') }
-
-      expect(helper.migrated?).to be_falsey
-    end
-  end
-
-  context '#db_migrate_status_file' do
-    it 'returns a path including status_file_prefix connection_digest and revision' do
-      fake_connection_digest = '63467bb60aa187d2a6830aa8f1b5cbe0'
-      fake_revision = 'f9631484e7f'
-      expected_status_file = "/var/opt/gitlab/gitlab-rails/upgrade-status/db-migrate-#{fake_connection_digest}-#{fake_revision}"
-
-      allow(helper).to receive(:connection_digest) { fake_connection_digest }
-      allow(helper).to receive(:revision) { fake_revision }
-
-      expect(helper.db_migrate_status_file).to eq(expected_status_file)
-    end
-  end
-end
diff --git a/spec/chef/gitlab/recipes/show_config_spec.rb b/spec/chef/gitlab/recipes/show_config_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi9yZWNpcGVzL3Nob3dfY29uZmlnX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab/recipes/show_config_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::show_config' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::show_config') }
-
-  it 'outputs user-defined gitlab.rb configuration to stdout' do
-    allow(Gitlab).to receive(:[]).and_call_original
-    stub_gitlab_rb(
-      patroni: { scope: 'test-cluster' }
-    )
-
-    expect { chef_run }.to output(/"scope": "test-cluster"/).to_stdout
-  end
-
-  it 'outputs GitlabCluster defined attributes to stdout' do
-    GitlabCluster.config.set('primary', true)
-
-    expect { chef_run }.to output(/"primary": true/).to_stdout
-  end
-end
diff --git a/spec/chef/gitlab/resources/puma_config_spec.rb b/spec/chef/gitlab/resources/puma_config_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi9yZXNvdXJjZXMvcHVtYV9jb25maWdfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab/resources/puma_config_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'puma_config' do
-  let(:runner) do
-    ChefSpec::SoloRunner.new(step_into: %w(puma_config))
-  end
-
-  context 'create' do
-    let(:chef_run) { runner.converge('test_gitlab::puma_config_create') }
-
-    it 'creates necessary directories' do
-      expect(chef_run).to create_directory('/var/opt/gitlab/gitlab-rails/etc')
-    end
-
-    it 'renders puma.rb file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with_content { |content|
-        expect(content).to match(%r(^environment 'production'))
-        expect(content).to match(%r(^rackup '/opt/gitlab/embedded/service/gitlab-rails/config.ru'))
-        expect(content).to match(%r(^pidfile '/opt/gitlab/var/puma/puma.pid'))
-        expect(content).to match(%r(^state_path '/opt/gitlab/var/puma/puma.state'))
-        expect(content).to match(%r(^bind 'unix:///var/opt/gitlab/gitlab-rails/sockets/gitlab.socket'))
-        expect(content).to match(%r(^bind 'tcp://127.0.0.1:8080'))
-        expect(content).to match(%r(^directory '/var/opt/gitlab/gitlab-rails/working'))
-        expect(content).to match(%r(^require_relative "/opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/cluster/lifecycle_events"$))
-        expect(content).to match(/^options = { workers: 2 }$/)
-        expect(content).to match(%r(Gitlab::Cluster::PumaWorkerKillerInitializer.start\(options\)))
-        expect(content).to match(/^preload_app!$/)
-        expect(content).to match(%r(^require_relative "/opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/puma_logging/json_formatter"$))
-      }
-    end
-  end
-
-  context 'create with default puma config' do
-    let(:chef_run) { runner.converge('test_gitlab::puma_config_create') }
-
-    it 'renders puma.rb file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with_content { |content|
-        expect(content).to match(%r(rackup '/opt/gitlab/embedded/service/gitlab-rails))
-      }
-    end
-  end
-
-  context 'create with custom Puma settings' do
-    let(:chef_run) { runner.converge('test_gitlab::puma_config_custom') }
-
-    it 'renders puma.rb file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with_content { |content|
-        expect(content).to match(%r(Gitlab::Cluster::PumaWorkerKillerInitializer.start\(options, puma_per_worker_max_memory_mb: 1000\)))
-        expect(content).to match(%r(rackup '/opt/custom/gitlab/embedded/service/gitlab-rails))
-      }
-    end
-  end
-end
diff --git a/spec/chef/gitlab/resources/rails_migration_spec.rb b/spec/chef/gitlab/resources/rails_migration_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2dpdGxhYi9yZXNvdXJjZXMvcmFpbHNfbWlncmF0aW9uX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/gitlab/resources/rails_migration_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'rails_migration' do
-  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(rails_migration)) }
-
-  before do
-    allow_any_instance_of(PgHelper).to receive(:postgresql_user).and_return('fakeuser')
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'run' do
-    let(:chef_run) { runner.converge('test_gitlab::rails_migration_run') }
-    let(:bash_block) { chef_run.bash('migrate gitlab-test database') }
-    let(:migration_block) { chef_run.rails_migration('gitlab-test') }
-
-    context 'when database has not been migrated' do
-      before do
-        allow_any_instance_of(GitlabGeoHelper).to receive(:migrated?).and_return(false)
-      end
-
-      it 'executes the bash script migration' do
-        expect(chef_run).to run_bash('migrate gitlab-test database')
-      end
-
-      it 'restarts dependent_services' do
-        expect(bash_block).to notify('ruby_block[test-dependent]').to(:restart)
-      end
-    end
-
-    context 'when database has been migrated already' do
-      it 'doesnt execute the bash script' do
-        expect_any_instance_of(RailsMigrationHelper).to receive(:migrated?) { true }
-
-        expect(chef_run).not_to run_bash('migrate gitlab-test database')
-      end
-    end
-
-    context 'bash script' do
-      it 'defines log file based on logfile_prefix' do
-        log_file = %(log_file="/var/log/gitlab/gitlab-rails/gitlab-test-db-migrate-$(date +%Y-%m-%d-%H-%M-%S).log")
-
-        expect(bash_block.code).to include(log_file)
-      end
-
-      it 'passes appropriate environment variables through' do
-        expect(bash_block.environment).to match(hash_including('SOME_ENV'))
-      end
-
-      it 'triggers provided rake_task' do
-        migrate = %(/opt/gitlab/bin/gitlab-rake gitlab:db:configure 2>& 1 | tee ${log_file})
-
-        expect(bash_block.code).to match(/#{migrate}/)
-      end
-
-      it 'pipes exit code to db-migrate file' do
-        fake_connection_digest = '63467bb60aa187d2a6830aa8f1b5cbe0'
-        fake_revision = 'f9631484e7f'
-        pipe = %(echo $STATUS > /var/opt/gitlab/gitlab-rails/upgrade-status/db-migrate-#{fake_connection_digest}-#{fake_revision})
-
-        expect_any_instance_of(RailsMigrationHelper).to receive(:connection_digest) { fake_connection_digest }
-        expect_any_instance_of(RailsMigrationHelper).to receive(:revision) { fake_revision }
-
-        expect(bash_block.code).to include(pipe)
-      end
-    end
-  end
-end
diff --git a/spec/chef/letsencrypt/resources/letsencrypt_certificate_spec.rb b/spec/chef/letsencrypt/resources/letsencrypt_certificate_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2xldHNlbmNyeXB0L3Jlc291cmNlcy9sZXRzZW5jcnlwdF9jZXJ0aWZpY2F0ZV9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/letsencrypt/resources/letsencrypt_certificate_spec.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::letsencrypt' do
-  let(:chef_run) do
-    ChefSpec::SoloRunner.new(step_into: %(letsencrypt_certificate)).converge('gitlab::default')
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    stub_gitlab_rb(
-      external_url: 'https://fakehost.example.com',
-      letsencrypt: {
-        enable: true,
-      }
-    )
-  end
-
-  context 'with NGINX running' do
-    before do
-      allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
-      allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('nginx').and_return(true)
-    end
-
-    it 'creates a staging certificate' do
-      expect(chef_run).to create_acme_certificate('staging').with(
-        crt: '/etc/gitlab/ssl/fakehost.example.com.crt-staging',
-        key: '/etc/gitlab/ssl/fakehost.example.com.key-staging',
-        wwwroot: '/var/opt/gitlab/nginx/www',
-        dir: 'https://acme-staging-v02.api.letsencrypt.org/directory',
-        sensitive: true
-      )
-    end
-
-    it "updates the node['acme']['private_key'] attribute" do
-      expect(chef_run).to run_ruby_block('reset private key')
-    end
-
-    it 'creates a production certificate' do
-      expect(chef_run).to create_acme_certificate('production').with(
-        crt: '/etc/gitlab/ssl/fakehost.example.com.crt',
-        key: '/etc/gitlab/ssl/fakehost.example.com.key',
-        wwwroot: '/var/opt/gitlab/nginx/www',
-        sensitive: true
-      )
-    end
-
-    context 'specifying a different key_size' do
-      before do
-        allow(File).to receive(:file?).and_call_original
-        allow(File).to receive(:file?).with('/etc/gitlab/ssl/fakehost.example.com.key').and_return(true)
-        allow(File).to receive(:read).with(anything).and_call_original
-        allow(File).to receive(:read).with('/etc/gitlab/ssl/fakehost.example.com.key').and_return(OpenSSL::PKey::RSA.new(2048))
-
-        stub_gitlab_rb(
-          external_url: 'https://fakehost.example.com',
-          letsencrypt: {
-            enable: true,
-            key_size: 3072
-          }
-        )
-      end
-
-      it 'deletes and recreates the SSL files' do
-        expect(chef_run).to delete_file('/etc/gitlab/ssl/fakehost.example.com.key')
-        expect(chef_run).to delete_file('/etc/gitlab/ssl/fakehost.example.com.crt')
-
-        expect(chef_run).to create_acme_certificate('production').with(
-          crt: '/etc/gitlab/ssl/fakehost.example.com.crt',
-          key: '/etc/gitlab/ssl/fakehost.example.com.key',
-          key_size: 3072,
-          wwwroot: '/var/opt/gitlab/nginx/www',
-          sensitive: true
-        )
-      end
-    end
-
-    it 'reloads nginx' do
-      prod_cert = chef_run.acme_certificate('production')
-      expect(prod_cert).to notify('execute[reload nginx]').to(:run)
-    end
-
-    context 'with extra options' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://fakehost.example.com',
-          letsencrypt: {
-            enable: true,
-            alt_names: %w(one.example.com two.example.com),
-            contact_emails: %w(foo@bar.com one@two.com)
-          }
-        )
-      end
-
-      it 'adds alt_names to the certificate resource' do
-        expect(chef_run).to create_acme_certificate('production')
-                              .with(
-                                alt_names: %w(one.example.com two.example.com),
-                                contact: %w(mailto:foo@bar.com mailto:one@two.com)
-                              )
-      end
-    end
-  end
-
-  context 'when NGINX is not running' do
-    before do
-      allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
-      allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('nginx').and_return(false)
-    end
-
-    it 'does not attempt to create a certificate' do
-      expect(chef_run).not_to create_acme_certificate('staging')
-      expect(chef_run).not_to create_acme_certificate('production')
-    end
-  end
-end
diff --git a/spec/chef/logrotate/recipes/folders_and_configs_spec.rb b/spec/chef/logrotate/recipes/folders_and_configs_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2xvZ3JvdGF0ZS9yZWNpcGVzL2ZvbGRlcnNfYW5kX2NvbmZpZ3Nfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/logrotate/recipes/folders_and_configs_spec.rb
+++ /dev/null
@@ -1,143 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::logrotate_folder_and_configs_spec' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when logrotate is enabled' do
-    it 'creates default set of directories' do
-      expect(chef_run.node['logrotate']['dir'])
-        .to eql('/var/opt/gitlab/logrotate')
-      expect(chef_run.node['logrotate']['log_directory'])
-        .to eql('/var/log/gitlab/logrotate')
-
-      expect(chef_run).to create_directory('/var/opt/gitlab/logrotate').with(
-        owner: nil,
-        group: nil,
-        mode: '0700'
-      )
-      expect(chef_run).to create_directory('/var/log/gitlab/logrotate').with(
-        owner: nil,
-        group: nil,
-        mode: '0700'
-      )
-      expect(chef_run).to create_directory('/var/opt/gitlab/logrotate/logrotate.d').with(
-        owner: nil,
-        group: nil,
-        mode: '0700'
-      )
-    end
-
-    it 'creates logrotate directories in alternative locations' do
-      stub_gitlab_rb(logrotate: { dir: "/tmp/logrotate", log_directory: "/tmp/logs/logrotate" })
-
-      expect(chef_run).to create_directory('/tmp/logrotate').with(
-        owner: nil,
-        group: nil,
-        mode: '0700'
-      )
-      expect(chef_run).to create_directory('/tmp/logs/logrotate').with(
-        owner: nil,
-        group: nil,
-        mode: '0700'
-      )
-      expect(chef_run).to create_directory('/tmp/logrotate/logrotate.d').with(
-        owner: nil,
-        group: nil,
-        mode: '0700'
-      )
-    end
-
-    it 'creates default set of configuration templates' do
-      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitlab-rails')
-      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitlab-shell')
-      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitlab-workhorse')
-      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitlab-pages')
-      expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitaly')
-    end
-
-    it 'populates configuration template with default values' do
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/daily/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/rotate 30/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/compress/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/copytruncate/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/notifempty/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/postrotate/)
-      expect(chef_run).not_to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/size/)
-      expect(chef_run).not_to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/maxsize/)
-      expect(chef_run).not_to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/dateext/)
-    end
-
-    it 'populates configuration template with custom values when logrotate is disabled' do
-      stub_gitlab_rb(logging:
-        { logrotate_frequency: "weekly",
-          logrotate_size: "50",
-          logrotate_maxsize: "50",
-          logrotate_rotate: "50",
-          logrotate_compress: "nocompress",
-          logrotate_method: "copy",
-          logrotate_postrotate: "/usr/bin/killall -HUP nginx",
-          logrotate_dateformat: "-%Y-%m-%d", }, logrotate: { enable: false })
-
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/weekly/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/rotate 50/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/nocompress/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/copy/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/postrotate/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/size 50/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/maxsize 50/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/dateext/)
-      expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/nginx')
-        .with_content(/dateformat -%Y-%m-%d/)
-    end
-
-    context 'when services not under gitlab key are specified' do
-      it 'populates files correctly' do
-        stub_gitlab_rb(
-          logrotate: {
-            services: ['gitlab-rails', 'gitaly']
-          },
-          gitaly: {
-            log_directory: '/my/log/directory'
-          }
-        )
-        expect(chef_run).to create_template('/var/opt/gitlab/logrotate/logrotate.d/gitlab-rails')
-        expect(chef_run).to render_file('/var/opt/gitlab/logrotate/logrotate.d/gitaly')
-          .with_content(/my\/log\/directory\/\*\.log/)
-      end
-    end
-
-    context 'when services that are not supported are specified' do
-      it 'raises an error' do
-        stub_gitlab_rb(
-          logrotate: {
-            services: ['gitlab-rails', 'foo-bar']
-          }
-        )
-
-        expect { chef_run }.to raise_error("Service foo-bar was specified in logrotate['services'], but is not a valid service.")
-      end
-    end
-  end
-end
diff --git a/spec/chef/logrotate/recipes/logrotate_spec.rb b/spec/chef/logrotate/recipes/logrotate_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL2xvZ3JvdGF0ZS9yZWNpcGVzL2xvZ3JvdGF0ZV9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/logrotate/recipes/logrotate_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'logrotate' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when logrotate is enabled' do
-    let(:config_template) { chef_run.template('/opt/gitlab/sv/logrotate/log/config') }
-
-    it_behaves_like "enabled runit service", "logrotate", "root", "root"
-
-    it 'populates the files with expected configuration' do
-      expect(config_template).to notify('ruby_block[reload_log_service]')
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/run')
-        .with_content(/cd \/var\/opt\/gitlab\/logrotate/)
-      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/run')
-        .with_content(/exec \/opt\/gitlab\/embedded\/bin\/chpst -P \/usr\/bin\/env/)
-      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/run')
-        .with_content(/\/opt\/gitlab\/embedded\/bin\/gitlab-logrotate-wrapper/)
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/log/run')
-        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/logrotate/)
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/log/config')
-        .with_content(/s209715200/)
-      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/log/config')
-        .with_content(/n30/)
-      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/log/config')
-        .with_content(/t86400/)
-      expect(chef_run).to render_file('/opt/gitlab/sv/logrotate/log/config')
-        .with_content(/!gzip/)
-    end
-
-    it 'executes start command' do
-      expect(chef_run).to run_execute('/opt/gitlab/bin/gitlab-ctl start logrotate').with(retries: 20)
-    end
-  end
-
-  context 'when logrotate is disabled' do
-    before do
-      stub_gitlab_rb(logrotate: { enable: false })
-    end
-
-    it_behaves_like "disabled runit service", "logrotate"
-
-    it 'does not execute the start command' do
-      expect(chef_run).not_to run_execute('/opt/gitlab/bin/gitlab-ctl start logrotate').with(retries: 20)
-    end
-  end
-end
diff --git a/spec/chef/monitoring/libraries/prometheus_helper_spec.rb b/spec/chef/monitoring/libraries/prometheus_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL21vbml0b3JpbmcvbGlicmFyaWVzL3Byb21ldGhldXNfaGVscGVyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/monitoring/libraries/prometheus_helper_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# Copyright:: Copyright (c) 2017 GitLab Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef_helper'
-
-RSpec.describe PrometheusHelper do
-  let(:chef_run) { ChefSpec::SoloRunner.new }
-  subject { described_class.new(chef_run.node) }
-
-  context 'flags for prometheus' do
-    context 'with default options' do
-      it 'returns the correct default config string' do
-        chef_run.converge('gitlab::default')
-        expect(subject.flags('prometheus')).to eq(
-          '--web.listen-address=localhost:9090 --storage.tsdb.path=/var/opt/gitlab/prometheus/data --config.file=/var/opt/gitlab/prometheus/prometheus.yml')
-      end
-    end
-
-    context 'with custom options' do
-      before { allow(Gitlab).to receive(:[]).and_call_original }
-
-      it 'does not return the correct string if any attributes have been changed' do
-        chef_run.node.normal['monitoring']['prometheus']['home'] = '/fake/dir'
-        chef_run.converge('gitlab::default')
-
-        expect(subject.flags('prometheus')).to eq(
-          '--web.listen-address=localhost:9090 --storage.tsdb.path=/fake/dir/data --config.file=/fake/dir/prometheus.yml')
-      end
-    end
-  end
-end
diff --git a/spec/chef/monitoring/libraries/prometheus_spec.rb b/spec/chef/monitoring/libraries/prometheus_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL21vbml0b3JpbmcvbGlicmFyaWVzL3Byb21ldGhldXNfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/monitoring/libraries/prometheus_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe Prometheus do
-  before { Services.add_services('gitlab', Services::BaseServices.list) }
-
-  it 'should return a list of known services' do
-    expect(Prometheus.services).to match_array(%w(
-                                                 prometheus
-                                                 alertmanager
-                                                 grafana
-                                                 node-exporter
-                                                 redis-exporter
-                                                 postgres-exporter
-                                                 gitlab-exporter
-                                               ))
-  end
-end
diff --git a/spec/chef/monitoring/recipes/alertmanager_spec.rb b/spec/chef/monitoring/recipes/alertmanager_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL21vbml0b3JpbmcvcmVjaXBlcy9hbGVydG1hbmFnZXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/monitoring/recipes/alertmanager_spec.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-require 'chef_helper'
-
-alertmanager_yml_output = <<-ALERTMANAGERYML
-  ---
-  global:
-    smtp_from: gitlab-omnibus
-    smtp_smarthost: testhost:25
-  templates: []
-  route:
-    receiver: default-receiver
-    routes: []
-  receivers:
-  - name: default-receiver
-    email_configs:
-    - to: admin@example.com
-  inhibit_rules: []
-ALERTMANAGERYML
-
-RSpec.describe 'monitoring::alertmanager' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  let(:default_vars) do
-    {
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when alertmanager is enabled' do
-    let(:config_template) { chef_run.template('/opt/gitlab/sv/alertmanager/log/config') }
-
-    before do
-      stub_gitlab_rb(
-        prometheus: { enable: true },
-        alertmanager: {
-          enable: true,
-          admin_email: 'admin@example.com',
-        },
-        gitlab_rails: {
-          gitlab_email_from: 'gitlab-omnibus',
-          smtp_enable: true,
-          smtp_address: 'testhost',
-          smtp_port: 25,
-        }
-      )
-    end
-
-    it_behaves_like 'enabled runit service', 'alertmanager', 'root', 'root'
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/alertmanager/env').with_variables(default_vars)
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(config_template).to notify('ruby_block[reload_log_service]')
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/alertmanager/run')
-        .with_content { |content|
-          expect(content).to match(/exec chpst -P/)
-          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/alertmanager/)
-          expect(content).to match(/alertmanager.yml/)
-        }
-
-      expect(chef_run).to render_file('/var/opt/gitlab/alertmanager/alertmanager.yml')
-        .with_content(alertmanager_yml_output.gsub(/^ {2}/, ''))
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/alertmanager/log/run')
-        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/alertmanager/)
-    end
-
-    it 'creates default set of directories' do
-      expect(chef_run).to create_directory('/var/log/gitlab/alertmanager').with(
-        owner: 'gitlab-prometheus',
-        group: nil,
-        mode: '0700'
-      )
-      expect(chef_run).to create_directory('/var/opt/gitlab/alertmanager').with(
-        owner: 'gitlab-prometheus',
-        group: nil,
-        mode: '0750'
-      )
-    end
-
-    it 'should create a gitlab-prometheus user account' do
-      expect(chef_run).to create_account('Prometheus user and group').with(username: 'gitlab-prometheus')
-    end
-
-    it 'sets a default listen address' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/alertmanager/run')
-        .with_content(/web.listen-address=localhost:9093/)
-    end
-  end
-
-  context 'with user provided settings' do
-    before do
-      stub_gitlab_rb(
-        prometheus: { enable: true },
-        alertmanager: {
-          listen_address: ':9093',
-          enable: true,
-          env: {
-            'USER_SETTING' => 'asdf1234'
-          },
-          global: {
-            'smtp_from' => 'override_value'
-          }
-        },
-        gitlab_rails: {
-          smtp_enable: true,
-          smtp_address: 'other-testhost',
-          smtp_port: 465,
-          smtp_from: 'default_value'
-        }
-      )
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/alertmanager/run')
-        .with_content(/web.listen-address=:9093/)
-    end
-
-    it 'keeps the defaults that the user did not override' do
-      expect(chef_run).to render_file('/var/opt/gitlab/alertmanager/alertmanager.yml')
-        .with_content(/receiver: default-receiver/)
-    end
-
-    it 'renders alertmanager.yml with the non-default value' do
-      expect(chef_run).to render_file('/var/opt/gitlab/alertmanager/alertmanager.yml')
-        .with_content(/smtp_smarthost: other-testhost:465/)
-    end
-
-    it 'renders alertmanager.yml with the user override value' do
-      expect(chef_run).to render_file('/var/opt/gitlab/alertmanager/alertmanager.yml')
-        .with_content(/smtp_from: override_value/)
-    end
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/alertmanager/env').with_variables(
-        default_vars.merge(
-          {
-            'USER_SETTING' => 'asdf1234'
-          }
-        )
-      )
-    end
-  end
-end
diff --git a/spec/chef/monitoring/recipes/gitlab-exporter_spec.rb b/spec/chef/monitoring/recipes/gitlab-exporter_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL21vbml0b3JpbmcvcmVjaXBlcy9naXRsYWItZXhwb3J0ZXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/monitoring/recipes/gitlab-exporter_spec.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'monitoring::gitlab-exporter' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  let(:default_env_vars) do
-    {
-      'LD_PRELOAD' => '/opt/gitlab/embedded/lib/libjemalloc.so',
-      'MALLOC_CONF' => 'dirty_decay_ms:0,muzzy_decay_ms:0',
-      'RUBY_GC_HEAP_INIT_SLOTS' => 80000,
-      'RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO' => 0.055,
-      'RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO' => 0.111,
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
-      'SSL_CERT_FILE' => '/opt/gitlab/embedded/ssl/cert.pem'
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when gitlab-exporter is enabled' do
-    let(:config_template) { chef_run.template('/opt/gitlab/sv/gitlab-exporter/log/config') }
-
-    before do
-      stub_gitlab_rb(
-        gitlab_exporter: { enable: true }
-      )
-    end
-
-    it 'creates a default VERSION file and restarts service' do
-      expect(chef_run).to create_version_file('Create version file for GitLab-Exporter').with(
-        version_file_path: '/var/opt/gitlab/gitlab-exporter/RUBY_VERSION',
-        version_check_cmd: '/opt/gitlab/embedded/bin/ruby --version'
-      )
-
-      expect(chef_run.version_file('Create version file for GitLab-Exporter')).to notify('runit_service[gitlab-exporter]').to(:restart)
-    end
-
-    it_behaves_like 'enabled runit service', 'gitlab-exporter', 'root', 'root'
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-exporter/env').with_variables(default_env_vars)
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(config_template).to notify('ruby_block[reload_log_service]')
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/gitlab-exporter/run')
-        .with_content { |content|
-          expect(content).to match(/exec chpst -P/)
-          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/gitlab-exporter/)
-        }
-
-      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-exporter/gitlab-exporter.yml')
-        .with_content { |content|
-          # Not disabling this Cop fails the test with:
-          # Psych::BadAlias: Unknown alias: db_common
-          settings = YAML.load(content) # rubocop:disable Security/YAMLLoad
-          expect(settings.dig('server', 'name')).to eq('webrick')
-          expect(settings.dig('probes', 'database')).not_to be_nil
-          expect(settings.dig('probes', 'ruby')).not_to be_nil
-          expect(settings.dig('probes', 'metrics', 'rows_count')).not_to be_nil
-
-          expect(content).to match(/host=\/var\/opt\/gitlab\/postgresql/)
-          expect(content).to match(/redis_enable_client: true/)
-        }
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/gitlab-exporter/log/run')
-        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/gitlab-exporter/)
-    end
-
-    it 'creates default set of directories' do
-      expect(chef_run).to create_directory('/var/log/gitlab/gitlab-exporter').with(
-        owner: 'git',
-        group: nil,
-        mode: '0700'
-      )
-    end
-  end
-
-  context 'with custom user and group' do
-    before do
-      stub_gitlab_rb(
-        gitlab_exporter: {
-          enable: true
-        },
-        user: {
-          username: 'foo',
-          group: 'bar'
-        }
-      )
-    end
-
-    it_behaves_like 'enabled runit service', 'gitlab-exporter', 'root', 'root'
-  end
-
-  context 'when gitlab-exporter is enabled and postgres is disabled' do
-    let(:config_template) { chef_run.template('/opt/gitlab/sv/gitlab-exporter/log/config') }
-
-    before do
-      stub_gitlab_rb(
-        gitlab_exporter: { enable: true },
-        gitlab_rails: { db_host: 'postgres.example.com', db_port: '5432', db_password: 'secret' },
-        postgresql: { enabled: false }
-      )
-    end
-
-    it 'populates a config with a remote host' do
-      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-exporter/gitlab-exporter.yml')
-        .with_content { |content|
-          expect(content).to match(/host=postgres\.example\.com/)
-          expect(content).to match(/port=5432/)
-          expect(content).to match(/password=secret/)
-        }
-    end
-  end
-
-  context 'with custom Redis settings' do
-    before do
-      stub_gitlab_rb(
-        gitlab_exporter: { enable: true },
-        gitlab_rails: { redis_enable_client: false }
-      )
-    end
-
-    it 'disables Redis CLIENT' do
-      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-exporter/gitlab-exporter.yml')
-        .with_content { |content|
-          expect(content).to match(/redis_enable_client: false/)
-        }
-    end
-  end
-
-  context 'when log dir is changed' do
-    before do
-      stub_gitlab_rb(
-        gitlab_exporter: {
-          log_directory: 'foo',
-          enable: true
-        }
-      )
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/gitlab-exporter/log/run')
-        .with_content(/exec svlogd -tt foo/)
-    end
-  end
-
-  include_examples "consul service discovery", "gitlab_exporter", "gitlab-exporter"
-end
diff --git a/spec/chef/monitoring/recipes/grafana_spec.rb b/spec/chef/monitoring/recipes/grafana_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL21vbml0b3JpbmcvcmVjaXBlcy9ncmFmYW5hX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/monitoring/recipes/grafana_spec.rb
+++ /dev/null
@@ -1,356 +0,0 @@
-require 'chef_helper'
-
-default_dashboards_yml_output = <<-DASHBOARDYML
----
-apiVersion: 1
-providers:
-- name: GitLab Omnibus
-  orgId: 1
-  folder: GitLab Omnibus
-  type: file
-  disableDeletion: true
-  updateIntervalSeconds: 600
-  options:
-    path: "/opt/gitlab/embedded/service/grafana-dashboards"
-DASHBOARDYML
-
-default_datasources_yml_output = <<-DATASOURCEYML
----
-apiVersion: 1
-datasources:
-- name: GitLab Omnibus
-  type: prometheus
-  access: proxy
-  url: http://localhost:9090
-DATASOURCEYML
-
-RSpec.describe 'monitoring::grafana' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  let(:default_vars) do
-    {
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
-    allow_any_instance_of(PgHelper).to receive(:database_exists?).and_return(true)
-  end
-
-  context 'when grafana is enabled' do
-    let(:config_template) { chef_run.template('/opt/gitlab/sv/grafana/log/config') }
-
-    before do
-      stub_gitlab_rb(
-        grafana: { enable: true },
-        prometheus: { enable: true }
-      )
-    end
-
-    it_behaves_like 'enabled runit service', 'grafana', 'root', 'root'
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/grafana/env').with_variables(default_vars)
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(config_template).to notify('ruby_block[reload_log_service]')
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/grafana/run')
-        .with_content { |content|
-          expect(content).to match(/exec chpst -P/)
-          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/grafana-server/)
-          expect(content).to match(/-config '\/var\/opt\/gitlab\/grafana\/grafana.ini'/)
-        }
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/grafana/log/run')
-        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/grafana/)
-    end
-
-    it 'creates default set of directories' do
-      expect(chef_run).to create_directory('/var/log/gitlab/grafana').with(
-        owner: 'gitlab-prometheus',
-        group: nil,
-        mode: '0700'
-      )
-    end
-
-    it 'creates the configuration file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
-        .with_content { |content|
-          expect(content).to match(/http_addr = localhost/)
-          expect(content).to match(/http_port = 3000/)
-          expect(content).to match(/root_url = http:\/\/localhost\/-\/grafana/)
-          expect(content).not_to match(/\[auth\.gitlab\]/)
-          expect(content).to match(/reporting_enabled = true/)
-        }
-    end
-
-    it 'creates a default dashboards file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/grafana/provisioning/dashboards/gitlab_dashboards.yml')
-        .with_content(default_dashboards_yml_output)
-    end
-
-    it 'creates a default datasources file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/grafana/provisioning/datasources/gitlab_datasources.yml')
-        .with_content(default_datasources_yml_output)
-    end
-  end
-
-  context 'when grafana is enabled and prometheus is disabled' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'http://gitlab.example.com',
-        prometheus: { enable: false },
-        grafana: { enable: true }
-      )
-    end
-
-    it 'should create the gitlab-prometheus account if prometheus is disabled' do
-      expect(chef_run).to create_account('Prometheus user and group').with_username('gitlab-prometheus')
-    end
-  end
-
-  context 'when log dir is changed' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'http://gitlab.example.com',
-        grafana: {
-          log_directory: 'foo',
-          enable: true
-        }
-      )
-    end
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/grafana/log/run')
-        .with_content(/exec svlogd -tt foo/)
-    end
-  end
-
-  it 'authorizes Grafana with gitlab' do
-    stub_gitlab_rb(external_url: 'http://gitlab.example.com')
-
-    allow(GrafanaHelper).to receive(:authorize_with_gitlab)
-
-    expect(chef_run).to run_ruby_block('authorize Grafana with GitLab')
-      .at_converge_time
-    expect(GrafanaHelper).to receive(:authorize_with_gitlab)
-      .with 'http://gitlab.example.com'
-
-    chef_run.ruby_block('authorize Grafana with GitLab').block.call
-  end
-
-  context 'with user provided settings' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'https://trailingslash.example.com/',
-        grafana: {
-          http_addr: '0.0.0.0',
-          http_port: 3333,
-          enable: true,
-          gitlab_application_id: 'appid',
-          gitlab_secret: 'secret',
-          gitlab_auth_sign_up: false,
-          allowed_groups: %w[
-            allowed
-            also-allowed
-          ],
-          smtp: {
-            enabled: true,
-            host: 'smtp_server:255',
-            user: 'testuser',
-            password: 'test',
-            cert_file: '/path/to/cert_file.crt',
-            key_file: '/path/to/key_file.key',
-            skip_verify: true,
-            from_address: 'smtp_admin@grafana.gitlab',
-            from_name: 'Grafana Gitlab SMTP',
-            ehlo_identity: 'dashboard.grafama.gitlab',
-            startTLS_policy: 'NoStartTLS'
-          },
-          env: {
-            'USER_SETTING' => 'asdf1234'
-          },
-          dashboards: [
-            {
-              name: 'GitLab Omnibus',
-              orgId: 1,
-              folder: 'GitLab Omnibus',
-              type: 'file',
-              disableDeletion: true,
-              updateIntervalSeconds: 600,
-              options: {
-                path: '/etc/grafana/dashboards',
-              },
-            },
-          ],
-        }
-      )
-    end
-
-    it 'creates a custom dashboards file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/grafana/provisioning/dashboards/gitlab_dashboards.yml')
-        .with_content { |content|
-          expect(content).to match(/options:\n    path: "\/etc\/grafana\/dashboards"\n/)
-        }
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
-        .with_content { |content|
-          expect(content).to match(/http_addr = 0.0.0.0/)
-          expect(content).to match(/http_port = 3333/)
-          expect(content).to match(/root_url = https:\/\/trailingslash.example.com\/-\/grafana/)
-          expect(content).to match(/\[auth\.gitlab\]\nenabled = true\nallow_sign_up = false/)
-          expect(content).to match(/client_id = appid/)
-          expect(content).to match(/client_secret = secret/)
-          expect(content).to match(/auth_url = https:\/\/trailingslash.example.com\/oauth\/authorize/)
-          expect(content).to match(/token_url = https:\/\/trailingslash.example.com\/oauth\/token/)
-          expect(content).to match(/api_url = https:\/\/trailingslash.example.com\/api\/v4/)
-          expect(content).to match(/allowed_groups = allowed also-allowed/)
-          expect(content).to match(/\[metrics\]\n#.+\nenabled = false/)
-          expect(content).not_to match(/basic_auth_username/)
-          expect(content).not_to match(/basic_auth_password/)
-          expect(content).to match(/scopes = read_api/)
-          # smtp configuration tests
-          expect(content).to match(/\[smtp\]\nenabled = true/)
-          expect(content).to match(/host = smtp_server:255/)
-          expect(content).to match(/user = testuser/)
-          expect(content).to match(/password = test/)
-          expect(content).to match(/cert_file = \/path\/to\/cert_file.crt/)
-          expect(content).to match(/key_file = \/path\/to\/key_file.key/)
-          expect(content).to match(/skip_verify = true/)
-          expect(content).to match(/from_address = smtp_admin@grafana.gitlab/)
-          expect(content).to match(/from_name = Grafana Gitlab SMTP/)
-          expect(content).to match(/ehlo_identity = dashboard.grafama.gitlab/)
-          expect(content).to match(/startTLS_policy = NoStartTLS/)
-        }
-    end
-
-    it 'generates grafana metrics basic auth password when enabled' do
-      stub_gitlab_rb(
-        grafana: {
-          metrics_enabled: true
-        }
-      )
-
-      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
-        .with_content { |content|
-          expect(content).to match(/\[metrics\]\n#.+\nenabled = true/)
-          expect(content).not_to match(/basic_auth_username/)
-          expect(content).to match(/basic_auth_password = """\S+"""/)
-        }
-    end
-
-    it 'adds metrics basic_auth_username when set' do
-      stub_gitlab_rb(
-        grafana: {
-          metrics_enabled: true,
-          metrics_basic_auth_username: 'user'
-        }
-      )
-
-      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
-        .with_content { |content|
-          expect(content).to match(/\[metrics\]\n#.+\nenabled = true/)
-          expect(content).to match(/basic_auth_username = """user"""/)
-          expect(content).to match(/basic_auth_password = """\S+"""/)
-        }
-    end
-
-    it 'can use special chars in metrics basic auth username and password' do
-      stub_gitlab_rb(
-        grafana: {
-          metrics_enabled: true,
-          metrics_basic_auth_username: '#user;@',
-          metrics_basic_auth_password: 'password_#;@'
-        }
-      )
-
-      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
-        .with_content { |content|
-          expect(content).to match(/\[metrics\]\n#.+\nenabled = true/)
-          expect(content).to match(/basic_auth_username = """#user;@"""/)
-          expect(content).to match(/basic_auth_password = """password_#;@"""/)
-        }
-    end
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/grafana/env').with_variables(
-        default_vars.merge(
-          {
-            'USER_SETTING' => 'asdf1234'
-          }
-        )
-      )
-    end
-
-    it 'does not deduplicate attributes outside its own scope' do
-      queue_groups = ['geo,post_receive,cronjob', 'geo,post_receive,cronjob', 'geo,post_receive,cronjob']
-
-      stub_gitlab_rb(
-        grafana: {
-          metrics_enabled: true,
-        },
-        sidekiq: {
-          enable: true,
-          queue_groups: queue_groups
-        }
-      )
-
-      block = chef_run.ruby_block('populate Grafana configuration options')
-      block.block.call
-
-      expect(chef_run.node['gitlab']['sidekiq']['queue_groups']).to eq(queue_groups)
-    end
-
-    it 'disables reporting when usage_ping_enabled is disabled' do
-      stub_gitlab_rb(
-        gitlab_rails: {
-          usage_ping_enabled: false
-        }
-      )
-
-      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
-        .with_content { |content|
-          expect(content).to match(/reporting_enabled = false/)
-        }
-    end
-
-    it 'disables reporting when usage_ping_enabled is enabled but is explicitly disabled' do
-      stub_gitlab_rb(
-        gitlab_rails: {
-          usage_ping_enabled: true
-        },
-        grafana: {
-          reporting_enabled: false
-        }
-      )
-
-      expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini')
-        .with_content { |content|
-          expect(content).to match(/reporting_enabled = false/)
-        }
-    end
-
-    context 'without allowed_groups specified' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://trailingslash.example.com/',
-          grafana: {
-            http_addr: '0.0.0.0',
-            http_port: 3333,
-            enable: true,
-            gitlab_application_id: 'appid',
-            gitlab_secret: 'secret'
-          }
-        )
-      end
-
-      it 'sets auth scope to read_user' do
-        expect(chef_run).to render_file('/var/opt/gitlab/grafana/grafana.ini').with_content(/scopes = read_user/)
-      end
-    end
-  end
-end
diff --git a/spec/chef/monitoring/recipes/node-exporter_spec.rb b/spec/chef/monitoring/recipes/node-exporter_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL21vbml0b3JpbmcvcmVjaXBlcy9ub2RlLWV4cG9ydGVyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/monitoring/recipes/node-exporter_spec.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'monitoring::node-exporter' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  let(:default_vars) do
-    {
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when node-exporter is enabled' do
-    let(:config_template) { chef_run.template('/opt/gitlab/sv/node-exporter/log/config') }
-
-    before do
-      stub_gitlab_rb(
-        node_exporter: { enable: true }
-      )
-    end
-
-    it_behaves_like 'enabled runit service', 'node-exporter', 'root', 'root'
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/node-exporter/env').with_variables(default_vars)
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(config_template).to notify('ruby_block[reload_log_service]')
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/node-exporter/run')
-        .with_content { |content|
-          expect(content).to match(/exec chpst -P/)
-          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/node_exporter/)
-          expect(content).to match(/--collector\.mountstats /)
-          expect(content).to match(/--collector\.runit /)
-          expect(content).to match(/--collector\.runit.servicedir=\/opt\/gitlab\/sv /)
-          expect(content).to match(/\/textfile_collector/)
-        }
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/node-exporter/log/run')
-        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/node-exporter/)
-    end
-
-    it 'creates default set of directories' do
-      expect(chef_run).to create_directory('/var/log/gitlab/node-exporter').with(
-        owner: 'gitlab-prometheus',
-        group: nil,
-        mode: '0700'
-      )
-      expect(chef_run).to create_directory('/var/opt/gitlab/node-exporter/textfile_collector').with(
-        owner: 'gitlab-prometheus',
-        group: nil,
-        mode: '0755'
-      )
-    end
-
-    it 'sets a default listen address' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/node-exporter/run')
-        .with_content(/web.listen-address=localhost:9100/)
-    end
-  end
-
-  context 'when node-exporter is enabled and prometheus is disabled' do
-    before do
-      stub_gitlab_rb(
-        prometheus: { enable: false },
-        node_exporter: { enable: true }
-      )
-    end
-
-    it 'should create the gitlab-prometheus account if prometheus is disabled' do
-      expect(chef_run).to create_account('Prometheus user and group').with_username('gitlab-prometheus')
-    end
-  end
-
-  context 'when node-exporter is enabled and prometheus is enabled' do
-    before do
-      stub_gitlab_rb(
-        prometheus: { enable: true },
-        node_exporter: { enable: true }
-      )
-    end
-
-    it 'creates a node.rules file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/prometheus/rules/node.rules')
-        .with_content(/instance:node_cpus:count/)
-    end
-  end
-
-  context 'when log dir is changed' do
-    before do
-      stub_gitlab_rb(
-        node_exporter: {
-          log_directory: 'foo',
-          enable: true
-        }
-      )
-    end
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/node-exporter/log/run')
-        .with_content(/exec svlogd -tt foo/)
-    end
-  end
-
-  context 'with user provided settings' do
-    before do
-      stub_gitlab_rb(
-        node_exporter: {
-          flags: {
-            'collector.textfile.directory' => '/tmp',
-            'collector.arp' => false,
-            'collector.mountstats' => false
-          },
-          listen_address: 'localhost:9899',
-          enable: true,
-          env: {
-            'USER_SETTING' => 'asdf1234'
-          }
-        }
-      )
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/node-exporter/run')
-        .with_content { |content|
-          expect(content).to match(/web\.listen-address=localhost:9899/)
-          expect(content).to match(/collector\.textfile\.directory=\/tmp/)
-          expect(content).to match(/--no-collector\.arp/)
-          expect(content).to match(/--no-collector\.mountstats/)
-          expect(content).to match(/--collector\.runit/)
-        }
-    end
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/node-exporter/env').with_variables(
-        default_vars.merge(
-          {
-            'USER_SETTING' => 'asdf1234'
-          }
-        )
-      )
-    end
-  end
-
-  include_examples "consul service discovery", "node_exporter", "node-exporter"
-end
diff --git a/spec/chef/monitoring/recipes/pgbouncer-exporter_spec.rb b/spec/chef/monitoring/recipes/pgbouncer-exporter_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL21vbml0b3JpbmcvcmVjaXBlcy9wZ2JvdW5jZXItZXhwb3J0ZXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/monitoring/recipes/pgbouncer-exporter_spec.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Copyright:: Copyright (c) 2018 GitLab Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-require 'chef_helper'
-
-RSpec.describe 'monitoring::pgbouncer-exporter' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
-  let(:config_yaml) { '/var/opt/gitlab/pgbouncer-exporter/pgbouncer-exporter.yaml' }
-  let(:default_vars) do
-    {
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe 'when enabled' do
-    before do
-      stub_gitlab_rb(
-        pgbouncer_exporter: {
-          enable: true
-        },
-        pgbouncer: {
-          enable: true,
-          databases: {
-            gitlabhq_production: {
-              host: '1.2.3.4'
-            }
-          }
-        },
-        postgresql: {
-          pgbouncer_user: 'fakeuser',
-          pgbouncer_user_password: 'fakeuserpassword'
-        }
-      )
-    end
-
-    it 'includes the pgbouncer-exporter recipe' do
-      expect(chef_run).to include_recipe('monitoring::pgbouncer-exporter')
-    end
-
-    it 'includes the postgresql user recipe' do
-      expect(chef_run).to include_recipe('postgresql::user')
-    end
-
-    it_behaves_like 'enabled runit service', 'pgbouncer-exporter', 'root', 'root'
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/pgbouncer-exporter/env').with_variables(default_vars)
-    end
-
-    it 'creates the appropriate directories' do
-      expect(chef_run).to create_directory('/var/log/gitlab/pgbouncer-exporter')
-    end
-  end
-end
diff --git a/spec/chef/monitoring/recipes/postgres_exporter_spec.rb b/spec/chef/monitoring/recipes/postgres_exporter_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL21vbml0b3JpbmcvcmVjaXBlcy9wb3N0Z3Jlc19leHBvcnRlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/monitoring/recipes/postgres_exporter_spec.rb
+++ /dev/null
@@ -1,194 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'monitoring::postgres-exporter' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  let(:node) { chef_run.node }
-  let(:default_vars) do
-    {
-      'DATA_SOURCE_NAME' => 'host=/var/opt/gitlab/postgresql user=gitlab-psql database=gitlabhq_production',
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when postgres is disabled locally' do
-    before do
-      stub_gitlab_rb(
-        postgresql: { enable: false }
-      )
-    end
-
-    it 'defaults the postgres-exporter to being disabled' do
-      expect(node['monitoring']['postgres-exporter']['enable']).to eq false
-    end
-
-    it 'allows postgres-exporter to be explicitly enabled' do
-      stub_gitlab_rb(postgres_exporter: { enable: true })
-
-      expect(node['monitoring']['postgres-exporter']['enable']).to eq true
-    end
-
-    it 'uses gitlab-rails db_host for the database host if postgres-exporter is explicitly enabled' do
-      stub_gitlab_rb(
-        postgres_exporter: { enable: true },
-        gitlab_rails: {
-          db_host: '10.0.0.1',
-          db_port: '4242',
-          db_username: 'foo',
-          db_password: 'bar',
-          db_database: 'baz'
-        }
-      )
-
-      expect(node['monitoring']['postgres-exporter']['env']['DATA_SOURCE_NAME'])
-        .to eq "host=10.0.0.1 port=4242 user=foo password=bar database=baz"
-    end
-  end
-
-  context 'when postgres is enabled locally' do
-    before do
-      stub_gitlab_rb(
-        postgresql: { enable: true }
-      )
-    end
-
-    it 'uses the local postgres dir for the database host' do
-      stub_gitlab_rb(
-        postgres_exporter: { enable: true },
-        gitlab_rails: { db_host: '10.0.0.1' },
-        postgresql: { dir: '/dir/to/postgresql' }
-      )
-
-      expect(node['monitoring']['postgres-exporter']['env']['DATA_SOURCE_NAME'])
-        .to eq "host=/dir/to/postgresql user=gitlab-psql database=gitlabhq_production"
-    end
-  end
-
-  context 'when postgres-exporter is enabled' do
-    let(:config_template) { chef_run.template('/opt/gitlab/sv/postgres-exporter/log/config') }
-
-    before do
-      stub_gitlab_rb(
-        postgres_exporter: { enable: true }
-      )
-    end
-
-    it_behaves_like 'enabled runit service', 'postgres-exporter', 'root', 'root'
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/postgres-exporter/env').with_variables(default_vars)
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(config_template).to notify('ruby_block[reload_log_service]')
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/run')
-        .with_content { |content|
-          expect(content).to match(/exec chpst /)
-          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/postgres_exporter/)
-        }
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/log/run')
-        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/postgres-exporter/)
-    end
-
-    it 'creates the queries.yaml file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/postgres-exporter/queries.yaml')
-        .with_content { |content|
-          expect(content).to match(/pg_replication:/)
-          expect(content).not_to match(/pg_stat_user_table:/)
-        }
-    end
-
-    it 'creates default set of directories' do
-      expect(chef_run).to create_directory('/var/log/gitlab/postgres-exporter').with(
-        owner: 'gitlab-psql',
-        group: nil,
-        mode: '0700'
-      )
-    end
-
-    it 'sets default flags' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/run')
-        .with_content { |content|
-          expect(content).to match(/web.listen-address=localhost:9187/)
-          expect(content).to match(/extend.query-path=\/var\/opt\/gitlab\/postgres-exporter\/queries.yaml/)
-        }
-    end
-  end
-
-  context 'when enabled and run as an isolated recipe' do
-    let(:chef_run) { converge_config('monitoring::postgres-exporter') }
-    before do
-      stub_gitlab_rb(postgres_exporter: { enable: true })
-    end
-
-    it 'includes the postgresql_user recipe' do
-      expect(chef_run).to include_recipe('postgresql::user')
-    end
-  end
-
-  context 'when log dir is changed' do
-    before do
-      stub_gitlab_rb(
-        postgres_exporter: {
-          log_directory: 'foo',
-          enable: true
-        }
-      )
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/log/run')
-        .with_content(/exec svlogd -tt foo/)
-    end
-  end
-
-  context 'with user provided settings' do
-    before do
-      stub_gitlab_rb(
-        postgres_exporter: {
-          flags: {
-            'some.flag' => 'foo'
-          },
-          listen_address: 'localhost:9700',
-          enable: true,
-          per_table_stats: true,
-          sslmode: 'require',
-          env: {
-            'USER_SETTING' => 'asdf1234'
-          }
-        }
-      )
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/run')
-        .with_content(/web.listen-address=localhost:9700/)
-      expect(chef_run).to render_file('/opt/gitlab/sv/postgres-exporter/run')
-        .with_content(/some.flag=foo/)
-    end
-
-    it 'creates the queries.yaml file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/postgres-exporter/queries.yaml')
-        .with_content(/pg_stat_user_tables:/)
-    end
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/postgres-exporter/env').with_variables(
-        default_vars.merge(
-          {
-            'DATA_SOURCE_NAME' => 'host=/var/opt/gitlab/postgresql user=gitlab-psql '\
-                                  'database=gitlabhq_production sslmode=require',
-            'USER_SETTING' => 'asdf1234'
-          }
-        )
-      )
-    end
-  end
-
-  include_examples "consul service discovery", "postgres_exporter", "postgres-exporter"
-end
diff --git a/spec/chef/monitoring/recipes/prometheus_spec.rb b/spec/chef/monitoring/recipes/prometheus_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL21vbml0b3JpbmcvcmVjaXBlcy9wcm9tZXRoZXVzX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/monitoring/recipes/prometheus_spec.rb
+++ /dev/null
@@ -1,312 +0,0 @@
-require 'chef_helper'
-
-prometheus_yml_output = <<-PROMYML
-  ---
-  global:
-    scrape_interval: 15s
-    scrape_timeout: 15s
-    external_labels: {}
-  remote_read: []
-  remote_write: []
-  rule_files:
-  - "/var/opt/gitlab/prometheus/rules/*.rules"
-  scrape_configs:
-  - job_name: prometheus
-    static_configs:
-    - targets:
-      - localhost:9090
-  - job_name: nginx
-    static_configs:
-    - targets:
-      - localhost:8060
-  - job_name: redis
-    static_configs:
-    - targets:
-      - localhost:9121
-  - job_name: postgres
-    static_configs:
-    - targets:
-      - localhost:9187
-  - job_name: node
-    static_configs:
-    - targets:
-      - localhost:9100
-  - job_name: gitlab-workhorse
-    static_configs:
-    - targets:
-      - localhost:9229
-  - job_name: gitlab-rails
-    metrics_path: "/-/metrics"
-    static_configs:
-    - targets:
-      - 127.0.0.1:8080
-    relabel_configs:
-    - source_labels:
-      - __address__
-      regex: 127.0.0.1:(.*)
-      replacement: localhost:$1
-      target_label: instance
-  - job_name: gitlab-sidekiq
-    static_configs:
-    - targets:
-      - 127.0.0.1:8082
-    relabel_configs:
-    - source_labels:
-      - __address__
-      regex: 127.0.0.1:(.*)
-      replacement: localhost:$1
-      target_label: instance
-  - job_name: registry
-    static_configs:
-    - targets:
-      - localhost:5001
-  - job_name: gitlab_exporter_database
-    metrics_path: "/database"
-    static_configs:
-    - targets:
-      - localhost:9168
-  - job_name: gitlab_exporter_sidekiq
-    metrics_path: "/sidekiq"
-    static_configs:
-    - targets:
-      - localhost:9168
-  - job_name: gitlab_exporter_ruby
-    metrics_path: "/ruby"
-    static_configs:
-    - targets:
-      - localhost:9168
-  - job_name: gitaly
-    static_configs:
-    - targets:
-      - localhost:9236
-  - job_name: kubernetes-cadvisor
-    scheme: https
-    tls_config:
-      ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
-      insecure_skip_verify: true
-    bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
-    kubernetes_sd_configs:
-    - role: node
-      api_server: https://kubernetes.default.svc:443
-      tls_config:
-        ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
-      bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
-    relabel_configs:
-    - action: labelmap
-      regex: __meta_kubernetes_node_label_(.+)
-    - target_label: __address__
-      replacement: kubernetes.default.svc:443
-    - source_labels:
-      - __meta_kubernetes_node_name
-      regex: "(.+)"
-      target_label: __metrics_path__
-      replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor"
-    metric_relabel_configs:
-    - source_labels:
-      - pod_name
-      target_label: environment
-      regex: "(.+)-.+-.+"
-  - job_name: kubernetes-nodes
-    scheme: https
-    tls_config:
-      ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
-      insecure_skip_verify: true
-    bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
-    kubernetes_sd_configs:
-    - role: node
-      api_server: https://kubernetes.default.svc:443
-      tls_config:
-        ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
-      bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
-    relabel_configs:
-    - action: labelmap
-      regex: __meta_kubernetes_node_label_(.+)
-    - target_label: __address__
-      replacement: kubernetes.default.svc:443
-    - source_labels:
-      - __meta_kubernetes_node_name
-      regex: "(.+)"
-      target_label: __metrics_path__
-      replacement: "/api/v1/nodes/${1}/proxy/metrics"
-    metric_relabel_configs:
-    - source_labels:
-      - pod_name
-      target_label: environment
-      regex: "(.+)-.+-.+"
-  - job_name: kubernetes-pods
-    tls_config:
-      ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
-      insecure_skip_verify: true
-    bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
-    kubernetes_sd_configs:
-    - role: pod
-      api_server: https://kubernetes.default.svc:443
-      tls_config:
-        ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
-      bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
-    relabel_configs:
-    - source_labels:
-      - __meta_kubernetes_pod_annotation_prometheus_io_scrape
-      action: keep
-      regex: 'true'
-    - source_labels:
-      - __meta_kubernetes_pod_annotation_prometheus_io_path
-      action: replace
-      target_label: __metrics_path__
-      regex: "(.+)"
-    - source_labels:
-      - __address__
-      - __meta_kubernetes_pod_annotation_prometheus_io_port
-      action: replace
-      regex: "([^:]+)(?::[0-9]+)?;([0-9]+)"
-      replacement: "$1:$2"
-      target_label: __address__
-    - action: labelmap
-      regex: __meta_kubernetes_pod_label_(.+)
-    - source_labels:
-      - __meta_kubernetes_namespace
-      action: replace
-      target_label: kubernetes_namespace
-    - source_labels:
-      - __meta_kubernetes_pod_name
-      action: replace
-      target_label: kubernetes_pod_name
-  alerting:
-    alertmanagers:
-    - static_configs:
-      - targets:
-        - localhost:9093
-PROMYML
-
-RSpec.describe 'monitoring::prometheus' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  let(:default_vars) do
-    {
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when prometheus is enabled' do
-    let(:config_template) { chef_run.template('/opt/gitlab/sv/prometheus/log/config') }
-
-    before do
-      stub_gitlab_rb(
-        alertmanager: {
-          enable: true
-        },
-        prometheus: {
-          enable: true
-        },
-        gitlab_exporter: {
-          enable: true
-        },
-        registry: {
-          enable: true,
-          debug_addr: 'localhost:5001'
-        }
-      )
-    end
-
-    it_behaves_like 'enabled runit service', 'prometheus', 'root', 'root'
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/prometheus/env').with_variables(default_vars)
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(config_template).to notify('ruby_block[reload_log_service]')
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/prometheus/run')
-        .with_content { |content|
-                            expect(content).to match(/exec chpst -P/)
-                            expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/prometheus/)
-                            expect(content).to match(/prometheus.yml/)
-                          }
-
-      expect(chef_run).to render_file('/var/opt/gitlab/prometheus/prometheus.yml')
-        .with_content(prometheus_yml_output.gsub(/^ {2}/, ''))
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/prometheus/log/run')
-        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/prometheus/)
-    end
-
-    it 'creates default set of directories' do
-      expect(chef_run).to create_directory('/var/log/gitlab/prometheus').with(
-        owner: 'gitlab-prometheus',
-        group: nil,
-        mode: '0700'
-      )
-      expect(chef_run).to create_directory('/var/opt/gitlab/prometheus').with(
-        owner: 'gitlab-prometheus',
-        group: nil,
-        mode: '0750'
-      )
-    end
-
-    it 'should create a gitlab-prometheus user and group' do
-      expect(chef_run).to create_account('Prometheus user and group').with(username: 'gitlab-prometheus', groupname: 'gitlab-prometheus')
-    end
-
-    it 'sets a default listen address' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/prometheus/run')
-        .with_content(/web.listen-address=localhost:9090/)
-    end
-  end
-
-  context 'by default' do
-    context 'with user provided settings' do
-      it 'configures puma job' do
-        expect(chef_run).to render_file('/var/opt/gitlab/prometheus/prometheus.yml')
-          .with_content(%r{- job_name: gitlab-rails\s+metrics_path: "/-/metrics"\s+static_configs:\s+- targets:\s+- 127.0.0.1:8080})
-      end
-    end
-  end
-
-  context 'rules directory' do
-    context 'default settings' do
-      it 'creates rules directory in correct location' do
-        expect(chef_run).to create_directory("/var/opt/gitlab/prometheus/rules")
-        expect(chef_run).to render_file("/var/opt/gitlab/prometheus/rules/node.rules")
-        expect(chef_run).to render_file("/var/opt/gitlab/prometheus/rules/gitlab.rules")
-      end
-    end
-
-    context 'user specified home directory' do
-      before do
-        stub_gitlab_rb(
-          prometheus: {
-            home: "/var/opt/gitlab/prometheus-bak"
-          }
-        )
-      end
-
-      it 'creates rules directory in correct location' do
-        expect(chef_run).to create_directory("/var/opt/gitlab/prometheus-bak/rules")
-        expect(chef_run).to render_file("/var/opt/gitlab/prometheus-bak/rules/node.rules")
-        expect(chef_run).to render_file("/var/opt/gitlab/prometheus-bak/rules/gitlab.rules")
-      end
-    end
-
-    context 'user specified rules directory' do
-      before do
-        stub_gitlab_rb(
-          prometheus: {
-            rules_directory: "/var/opt/gitlab/prometheus/alert-rules"
-          }
-        )
-      end
-
-      it 'creates rules directory in correct location' do
-        expect(chef_run).to create_directory("/var/opt/gitlab/prometheus/alert-rules")
-        expect(chef_run).to render_file("/var/opt/gitlab/prometheus/alert-rules/node.rules")
-        expect(chef_run).to render_file("/var/opt/gitlab/prometheus/alert-rules/gitlab.rules")
-      end
-    end
-  end
-
-  include_examples "consul service discovery", "prometheus", "prometheus"
-end
diff --git a/spec/chef/monitoring/recipes/redis_exporter_spec.rb b/spec/chef/monitoring/recipes/redis_exporter_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL21vbml0b3JpbmcvcmVjaXBlcy9yZWRpc19leHBvcnRlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/monitoring/recipes/redis_exporter_spec.rb
+++ /dev/null
@@ -1,129 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'monitoring::redis-exporter' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  let(:node) { chef_run.node }
-  let(:default_vars) do
-    {
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when redis is disabled locally' do
-    before do
-      stub_gitlab_rb(
-        redis: { enable: false }
-      )
-    end
-
-    it 'defaults the redis-exporter to being disabled' do
-      expect(node['monitoring']['redis-exporter']['enable']).to eq false
-    end
-
-    it 'allows redis-exporter to be explicitly enabled' do
-      stub_gitlab_rb(redis_exporter: { enable: true })
-
-      expect(node['monitoring']['redis-exporter']['enable']).to eq true
-    end
-  end
-
-  context 'when redis-exporter is enabled' do
-    let(:config_template) { chef_run.template('/opt/gitlab/sv/redis-exporter/log/config') }
-
-    before do
-      stub_gitlab_rb(
-        redis_exporter: { enable: true }
-      )
-    end
-
-    it_behaves_like 'enabled runit service', 'redis-exporter', 'root', 'root'
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/redis-exporter/env').with_variables(default_vars)
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(config_template).to notify('ruby_block[reload_log_service]')
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/run')
-        .with_content { |content|
-          expect(content).to match(/exec chpst -P/)
-          expect(content).to match(/\/opt\/gitlab\/embedded\/bin\/redis_exporter/)
-        }
-
-      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/log/run')
-        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/redis-exporter/)
-    end
-
-    it 'creates default set of directories' do
-      expect(chef_run).to create_directory('/var/log/gitlab/redis-exporter').with(
-        owner: 'gitlab-redis',
-        group: nil,
-        mode: '0700'
-      )
-    end
-
-    it 'sets default flags' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/run')
-        .with_content(/web.listen-address=localhost:9121/)
-      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/run')
-        .with_content(%r{redis.addr=unix:///var/opt/gitlab/redis/redis.socket})
-    end
-  end
-
-  context 'when log dir is changed' do
-    before do
-      stub_gitlab_rb(
-        redis_exporter: {
-          log_directory: 'foo',
-          enable: true
-        }
-      )
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/log/run')
-        .with_content(/exec svlogd -tt foo/)
-    end
-  end
-
-  context 'with user provided settings' do
-    before do
-      stub_gitlab_rb(
-        redis_exporter: {
-          flags: {
-            'redis.addr' => '/tmp/socket'
-          },
-          listen_address: 'localhost:9900',
-          enable: true,
-          env: {
-            'USER_SETTING' => 'asdf1234'
-          }
-        }
-      )
-    end
-
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/run')
-        .with_content(/web.listen-address=localhost:9900/)
-      expect(chef_run).to render_file('/opt/gitlab/sv/redis-exporter/run')
-        .with_content(%r{redis.addr=/tmp/socket})
-    end
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/redis-exporter/env').with_variables(
-        default_vars.merge(
-          {
-            'USER_SETTING' => 'asdf1234'
-          }
-        )
-      )
-    end
-  end
-
-  include_examples "consul service discovery", "redis_exporter", "redis-exporter"
-end
diff --git a/spec/chef/package/libraries/helpers/node_helper_spec.rb b/spec/chef/package/libraries/helpers/node_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvbGlicmFyaWVzL2hlbHBlcnMvbm9kZV9oZWxwZXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/package/libraries/helpers/node_helper_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-require 'chef_helper'
-
-RSpec.describe NodeHelper do
-  let(:chef_run) { converge_config }
-  subject { described_class }
-
-  describe '.consume_cluster_attributes' do
-    it 'merges new attributes into node as override level' do
-      new_attrs = ::Gitlab::ConfigMash.new(geo_primary: true)
-
-      subject.consume_cluster_attributes(chef_run.node, new_attrs)
-
-      expect(chef_run.node.attributes).to include(geo_primary: true)
-      expect(chef_run.node.override).to include(geo_primary: true)
-    end
-
-    it 'merges overwritting existing attributes into node as override level' do
-      allow(Gitlab).to receive(:[]).and_call_original
-      stub_gitlab_rb(package: { custom: true })
-
-      expect(chef_run.node.attributes['package']['custom']).to be true
-
-      new_attrs = ::Gitlab::ConfigMash.new(package: { custom: false })
-      subject.consume_cluster_attributes(chef_run.node, new_attrs)
-
-      expect(chef_run.node.attributes['package']['custom']).to be false
-      expect(chef_run.node.override['package']['custom']).to be false
-    end
-  end
-end
diff --git a/spec/chef/package/libraries/helpers/shell_out_helper_spec.rb b/spec/chef/package/libraries/helpers/shell_out_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvbGlicmFyaWVzL2hlbHBlcnMvc2hlbGxfb3V0X2hlbHBlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/package/libraries/helpers/shell_out_helper_spec.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-require 'spec_helper'
-
-RSpec.describe ShellOutHelper do
-  let(:dummy_class) { Class.new { extend ShellOutHelper } }
-
-  before do
-    allow(Mixlib::ShellOut).to receive(:new).and_call_original
-  end
-
-  describe '.do_shell_out' do
-    context 'without optional arguments' do
-      it 'creates Mixlib::ShellOut object without optional arguments' do
-        expect(Mixlib::ShellOut).to receive(:new).with('ls', { user: nil, cwd: nil, environment: {} })
-
-        dummy_class.do_shell_out('ls')
-      end
-    end
-
-    context 'with optional arguments specified' do
-      it 'creates Mixlib::ShellOut object with specified optional arguments' do
-        expect(Mixlib::ShellOut).to receive(:new).with('ls', { user: 'foobar', cwd: '/tmp', environment: { PATH: '/tmp/bin' } })
-
-        dummy_class.do_shell_out('ls', 'foobar', '/tmp', env: { PATH: '/tmp/bin' })
-      end
-    end
-
-    context 'when command can not be executed' do
-      let(:shellout_object) { double('shellout', run_command: true) }
-
-      it 'logs relevant information' do
-        allow(Mixlib::ShellOut).to receive(:new).and_return(shellout_object)
-        allow(shellout_object).to receive(:run_command).and_raise(Errno::EACCES)
-
-        expect(Chef::Log).to receive(:info).with("Cannot execute ls.")
-
-        dummy_class.do_shell_out('ls')
-      end
-    end
-
-    context 'when command not found' do
-      let(:shellout_object) { double('shellout', run_command: true) }
-
-      it 'logs relevant information' do
-        allow(Mixlib::ShellOut).to receive(:new).and_return(shellout_object)
-        allow(shellout_object).to receive(:run_command).and_raise(Errno::ENOENT)
-
-        expect(Chef::Log).to receive(:info).with("ls does not exist.")
-
-        dummy_class.do_shell_out('ls')
-      end
-    end
-  end
-
-  describe '.do_shell_out_with_embedded_path' do
-    context 'without optional arguments' do
-      it 'calls do_shell_out with embedded bin path prependend to PATH' do
-        allow(ENV).to receive(:[]).with('PATH').and_return('/usr/local/bin')
-
-        expect(dummy_class).to receive(:do_shell_out).with('ls', nil, nil, env: { PATH: '/opt/gitlab/embedded/bin:/usr/local/bin' })
-
-        dummy_class.do_shell_out_with_embedded_path('ls')
-      end
-    end
-
-    context 'without PATH specified in optional env argument' do
-      it 'calls do_shell_out with embedded bin path prependend to PATH' do
-        allow(ENV).to receive(:[]).with('PATH').and_return('/usr/local/bin')
-
-        expect(dummy_class).to receive(:do_shell_out).with('ls', nil, nil, env: { PATH: '/opt/gitlab/embedded/bin:/tmp/bin:/usr/local/bin' })
-
-        dummy_class.do_shell_out_with_embedded_path('ls', env: { 'PATH' => '/tmp/bin' })
-      end
-    end
-  end
-end
diff --git a/spec/chef/package/libraries/omnibus_helper_spec.rb b/spec/chef/package/libraries/omnibus_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvbGlicmFyaWVzL29tbmlidXNfaGVscGVyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/package/libraries/omnibus_helper_spec.rb
+++ /dev/null
@@ -1,557 +0,0 @@
-# frozen_string_literal: true
-
-require 'chef_helper'
-require 'time'
-
-RSpec.describe OmnibusHelper do
-  cached(:chef_run) { converge_config }
-  let(:node) { chef_run.node }
-  let(:file) { double(:file, write: true) }
-
-  subject { described_class.new(chef_run.node) }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-
-    allow(File).to receive(:open).and_call_original
-    allow(File).to receive(:open).with('/etc/gitlab/initial_root_password', 'w', 0600).and_yield(file).once
-  end
-
-  describe '#user_exists?' do
-    it 'returns true when user exists' do
-      allow_any_instance_of(ShellOutHelper).to receive(:success?).with("id -u root").and_return(true)
-      expect(subject.user_exists?('root')).to be_truthy
-    end
-
-    it 'returns false when user does not exist' do
-      allow_any_instance_of(ShellOutHelper).to receive(:success?).with("id -u nonexistentuser").and_return(false)
-      expect(subject.user_exists?('nonexistentuser')).to be_falsey
-    end
-  end
-
-  describe '#group_exists?' do
-    it 'returns true when group exists' do
-      allow_any_instance_of(ShellOutHelper).to receive(:success?).with("getent group root").and_return(true)
-      expect(subject.group_exists?('root')).to be_truthy
-    end
-
-    it 'returns false when group does not exist' do
-      allow_any_instance_of(ShellOutHelper).to receive(:success?).with("getent group nonexistentgroup").and_return(false)
-      expect(subject.group_exists?('nonexistentgroup')).to be_falsey
-    end
-  end
-
-  describe '#not_listening?' do
-    let(:chef_run) { converge_config }
-    context 'when Redis is disabled' do
-      before do
-        stub_gitlab_rb(
-          redis: { enable: false }
-        )
-      end
-
-      it 'returns true when service is disabled' do
-        expect(subject.not_listening?('redis')).to be_truthy
-      end
-    end
-
-    context 'when Redis is enabled' do
-      before do
-        stub_gitlab_rb(
-          redis: { enable: true }
-        )
-      end
-
-      it 'returns true when service is down' do
-        stub_service_failure_status('redis', true)
-
-        expect(subject.not_listening?('redis')).to be_truthy
-      end
-
-      it 'returns false when service is up' do
-        stub_service_failure_status('redis', false)
-
-        expect(subject.not_listening?('redis')).to be_falsey
-      end
-    end
-  end
-
-  describe '#service_enabled?' do
-    context 'services are enabled' do
-      before do
-        chef_run.node.normal['gitlab']['old_service']['enable'] = true
-        chef_run.node.normal['new_service']['enable'] = true
-        chef_run.node.normal['monitoring']['another_service']['enable'] = true
-      end
-
-      it 'should return true' do
-        expect(subject.service_enabled?('old_service')).to be_truthy
-        expect(subject.service_enabled?('new_service')).to be_truthy
-        expect(subject.service_enabled?('another_service')).to be_truthy
-      end
-    end
-
-    context 'services are disabled' do
-      before do
-        chef_run.node.normal['gitlab']['old_service']['enable'] = false
-        chef_run.node.normal['new_service']['enable'] = false
-        chef_run.node.normal['monitoring']['another_service']['enable'] = false
-      end
-
-      it 'should return false' do
-        expect(subject.service_enabled?('old_service')).to be_falsey
-        expect(subject.service_enabled?('new_service')).to be_falsey
-        expect(subject.service_enabled?('another_service')).to be_falsey
-      end
-    end
-  end
-
-  describe '#is_managed_and_offline?' do
-    context 'services are disabled' do
-      before do
-        chef_run.node.normal['gitlab']['old_service']['enable'] = false
-        chef_run.node.normal['new_service']['enable'] = false
-      end
-
-      it 'returns false' do
-        expect(subject.is_managed_and_offline?('old_service')).to be_falsey
-        expect(subject.is_managed_and_offline?('new_service')).to be_falsey
-      end
-    end
-
-    context 'services are enabled' do
-      before do
-        chef_run.node.normal['gitlab']['old_service']['enable'] = true
-        chef_run.node.normal['new_service']['enable'] = true
-      end
-
-      it 'returns true when services are offline' do
-        stub_service_failure_status('old_service', true)
-        stub_service_failure_status('new_service', true)
-
-        expect(subject.is_managed_and_offline?('old_service')).to be_truthy
-        expect(subject.is_managed_and_offline?('new_service')).to be_truthy
-      end
-
-      it 'returns false when services are online ' do
-        stub_service_failure_status('old_service', false)
-        stub_service_failure_status('new_service', false)
-
-        expect(subject.is_managed_and_offline?('old_service')).to be_falsey
-        expect(subject.is_managed_and_offline?('new_service')).to be_falsey
-      end
-    end
-  end
-
-  describe '#is_deprecated_os?' do
-    before do
-      allow(OmnibusHelper).to receive(:deprecated_os_list).and_return({ "raspbian-8.0" => "GitLab 11.8" })
-    end
-
-    it 'detects deprecated OS correctly' do
-      allow_any_instance_of(Ohai::System).to receive(:data).and_return({ "platform" => "raspbian", "platform_version" => "8.0" })
-
-      OmnibusHelper.is_deprecated_os?
-
-      expect_logged_deprecation(/Your OS, raspbian-8.0, will be deprecated soon/)
-    end
-
-    it 'does not detects valid OS as deprecated' do
-      allow_any_instance_of(Ohai::System).to receive(:data).and_return({ "platform" => "ubuntu", "platform_version" => "16.04.3" })
-      expect(LoggingHelper).not_to receive(:deprecation)
-      OmnibusHelper.is_deprecated_os?
-    end
-  end
-
-  describe '#is_deprecated_praefect_config?' do
-    before do
-      chef_run.node.normal['praefect'] = config
-    end
-
-    context 'deprecated config' do
-      let(:config) do
-        {
-          storage_nodes: [
-            { storage: 'praefect1', address: 'tcp://node1.internal' },
-            { storage: 'praefect2', address: 'tcp://node2.internal' }
-          ]
-        }
-      end
-
-      it 'detects deprecated config correctly' do
-        subject.is_deprecated_praefect_config?
-
-        expect_logged_deprecation(/Specifying Praefect storage nodes as an array is deprecated/)
-      end
-    end
-
-    context 'valid config' do
-      let(:config) do
-        {
-          storage_nodes: {
-            'praefect1' => { address: 'tcp://node1.internal' },
-            'praefect2' => { address: 'tcp://node2.internal' }
-          }
-        }
-      end
-
-      it 'does not detect a valid config as deprecated' do
-        expect(LoggingHelper).not_to receive(:deprecation)
-
-        subject.is_deprecated_praefect_config?
-      end
-    end
-  end
-
-  describe '#write_root_password' do
-    before do
-      stub_gitlab_rb(
-        gitlab_rails: {
-          initial_root_password: 'foobar',
-          store_initial_root_password: true
-        }
-      )
-    end
-
-    it 'stores root password to /etc/gitlab/initial_root_password' do
-      content = <<~EOS
-        # WARNING: This value is valid only in the following conditions
-        #          1. If provided manually (either via `GITLAB_ROOT_PASSWORD` environment variable or via `gitlab_rails['initial_root_password']` setting in `gitlab.rb`, it was provided before database was seeded for the first time (usually, the first reconfigure run).
-        #          2. Password hasn't been changed manually, either via UI or via command line.
-        #
-        #          If the password shown here doesn't work, you must reset the admin password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.
-
-        Password: foobar
-
-        # NOTE: This file will be automatically deleted in the first reconfigure run after 24 hours.
-      EOS
-
-      expect(file).to receive(:write).with(content)
-
-      described_class.new(converge_config.node).write_root_password
-    end
-  end
-
-  describe '.cleanup_root_password_file' do
-    context 'when /etc/gitlab/initial_root_password does not exist' do
-      before do
-        allow(File).to receive(:exist?).and_call_original
-        allow(File).to receive(:exist?).with('/etc/gitlab/initial_root_password').and_return(false)
-      end
-
-      it 'does not attempt to remove the file' do
-        expect(FileUtils).not_to receive(:rm_f).with('/etc/gitlab/initial_root_password')
-
-        described_class.cleanup_root_password_file
-      end
-    end
-
-    context 'when /etc/gitlab/initial_root_password exists' do
-      before do
-        allow(File).to receive(:exist?).and_call_original
-        allow(File).to receive(:exist?).with('/etc/gitlab/initial_root_password').and_return(true)
-        allow(Time).to receive(:now).and_return(Time.parse('2021-06-07 08:45:51.377926667 +0530'))
-      end
-
-      context 'when file is older than 24 hours' do
-        before do
-          allow(File).to receive(:mtime).with('/etc/gitlab/initial_root_password').and_return(Time.parse('2021-06-03 06:45:51.377926667 +0530'))
-        end
-
-        it 'attempts to remove the file' do
-          expect(FileUtils).to receive(:rm_f).with('/etc/gitlab/initial_root_password')
-
-          described_class.cleanup_root_password_file
-
-          expect_logged_note('Found old initial root password file at /etc/gitlab/initial_root_password and deleted it.')
-        end
-      end
-
-      context 'when file is younger than 24 hours' do
-        before do
-          allow(File).to receive(:mtime).with('/etc/gitlab/initial_root_password').and_return(Time.parse('2021-06-08 06:45:51.377926667 +0530'))
-        end
-
-        it 'does not attempt to remove the file' do
-          expect(FileUtils).not_to receive(:rm_f).with('/etc/gitlab/initial_root_password')
-
-          described_class.cleanup_root_password_file
-
-          expect_logged_note('Found old initial root password file at /etc/gitlab/initial_root_password and deleted it.')
-        end
-      end
-    end
-  end
-
-  describe '#print_root_account_details' do
-    context 'when not on first reconfigure after installation' do
-      before do
-        chef_run.node.normal['gitlab']['bootstrap']['enable'] = false
-      end
-
-      it 'does not add a note or write password to file' do
-        expect(LoggingHelper).not_to receive(:note)
-        expect(subject).not_to receive(:write_root_password)
-
-        subject.print_root_account_details
-      end
-    end
-
-    context 'when on first reconfigure after installation' do
-      before do
-        allow_any_instance_of(OmnibusHelper).to receive(:writ_root_password).and_return(true)
-      end
-
-      context 'when display_initial_root_password is true' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              initial_root_password: 'foobar',
-              display_initial_root_password: true,
-              store_initial_root_password: false
-            }
-          )
-        end
-
-        it 'displays root password at the end of reconfigure' do
-          msg = <<~EOS
-            Default admin account has been configured with following details:
-            Username: root
-            Password: foobar
-
-            NOTE: Because these credentials might be present in your log files in plain text, it is highly recommended to reset the password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.
-          EOS
-
-          described_class.new(converge_config.node).print_root_account_details
-
-          expect_logged_note(msg)
-        end
-      end
-
-      context 'when display_initial_root_password is false' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              initial_root_password: 'foobar',
-              display_initial_root_password: false,
-              store_initial_root_password: false
-            }
-          )
-        end
-
-        it 'does not display root credentials at the end of reconfigure' do
-          msg = <<~EOS
-            Default admin account has been configured with following details:
-            Username: root
-            Password: You didn't opt-in to print initial root password to STDOUT.
-
-            NOTE: Because these credentials might be present in your log files in plain text, it is highly recommended to reset the password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.
-          EOS
-
-          described_class.new(converge_config.node).print_root_account_details
-
-          expect_logged_note(msg)
-        end
-      end
-
-      describe '#write_root_password' do
-        context 'when store_initial_root_password is true' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                initial_root_password: 'foobar',
-                display_initial_root_password: false,
-                store_initial_root_password: true
-              }
-            )
-          end
-
-          it 'writes initial root password to /etc/gitlab/initial_root_password' do
-            subject = described_class.new(converge_config.node)
-            expect(subject).to receive(:write_root_password)
-
-            subject.print_root_account_details
-
-            expect_logged_note(%r{Password stored to /etc/gitlab/initial_root_password})
-          end
-        end
-
-        context 'with default value for store_initial_root_password' do
-          context 'when password is auto-generated' do
-            before do
-              allow(ENV).to receive(:[]).and_call_original
-              allow(ENV).to receive(:[]).with('GITLAB_ROOT_PASSWORD').and_return(nil)
-            end
-
-            it 'writes initial root password to /etc/gitlab/initial_root_password' do
-              chef_run = ChefSpec::SoloRunner.converge('gitlab::default')
-              subject = described_class.new(chef_run.node)
-
-              expect(subject).to receive(:write_root_password)
-
-              subject.print_root_account_details
-
-              expect_logged_note(%r{Password stored to /etc/gitlab/initial_root_password})
-            end
-          end
-
-          context 'when password is specified by user' do
-            context 'via gitlab.rb' do
-              before do
-                stub_gitlab_rb(
-                  gitlab_rails: {
-                    initial_root_password: 'foobar',
-                  }
-                )
-              end
-
-              it 'does not write initial root password to /etc/gitlab/initial_root_password' do
-                subject = described_class.new(converge_config.node)
-
-                expect(subject).not_to receive(:write_root_password)
-
-                subject.print_root_account_details
-              end
-            end
-
-            context 'via env variable' do
-              before do
-                allow(ENV).to receive(:[]).and_call_original
-                allow(ENV).to receive(:[]).with('GITLAB_ROOT_PASSWORD').and_return('foobar')
-              end
-
-              it 'does not write initial root password to /etc/gitlab/initial_root_password' do
-                subject = described_class.new(converge_config.node)
-
-                expect(subject).not_to receive(:write_root_password)
-
-                subject.print_root_account_details
-              end
-            end
-          end
-        end
-      end
-    end
-  end
-
-  describe '#check_locale' do
-    let(:error_message) { "Identified encoding is not UTF-8. GitLab requires UTF-8 encoding to function properly. Please check your locale settings." }
-
-    describe 'using LC_ALL variable' do
-      it 'does not raise a warning when set to a UTF-8 locale even if others are not' do
-        allow(ENV).to receive(:[]).and_call_original
-        allow(ENV).to receive(:[]).with('LC_ALL').and_return('en_US.UTF-8')
-        allow(ENV).to receive(:[]).with('LC_COLLATE').and_return('en_SG ISO-8859-1')
-        allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_SG ISO-8859-1')
-        allow(ENV).to receive(:[]).with('LANG').and_return('en_SG ISO-8859-1')
-
-        expect(LoggingHelper).not_to receive(:warning).with("Environment variable .* specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
-
-        described_class.check_locale
-      end
-
-      it 'raises warning when LC_ALL is non-UTF-8' do
-        allow(ENV).to receive(:[]).and_call_original
-        allow(ENV).to receive(:[]).with('LC_ALL').and_return('en_SG ISO-8859-1')
-
-        described_class.check_locale
-
-        expect_logged_warning("Environment variable LC_ALL specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
-      end
-    end
-
-    describe 'using LC_CTYPE variable' do
-      before do
-        allow(ENV).to receive(:[]).and_call_original
-        allow(ENV).to receive(:[]).with('LC_ALL').and_return(nil)
-      end
-
-      it 'raises warning when LC_CTYPE is non-UTF-8' do
-        allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_SG ISO-8859-1')
-
-        described_class.check_locale
-
-        expect_logged_warning("Environment variable LC_CTYPE specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
-      end
-    end
-
-    describe 'using LC_COLLATE variable' do
-      before do
-        allow(ENV).to receive(:[]).and_call_original
-        allow(ENV).to receive(:[]).with('LC_ALL').and_return(nil)
-        allow(ENV).to receive(:[]).with('LC_CTYPE').and_return(nil)
-      end
-
-      it 'raises warning when LC_COLLATE is non-UTF-8' do
-        allow(ENV).to receive(:[]).with('LC_COLLATE').and_return('en_SG ISO-8859-1')
-
-        described_class.check_locale
-
-        expect_logged_warning("Environment variable LC_COLLATE specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
-      end
-    end
-
-    describe 'using LANG variable' do
-      before do
-        allow(ENV).to receive(:[]).and_call_original
-        allow(ENV).to receive(:[]).with('LC_ALL').and_return(nil)
-      end
-
-      context 'without LC_CTYPE and LC_COLLATE' do
-        before do
-          allow(ENV).to receive(:[]).with('LC_CTYPE').and_return(nil)
-          allow(ENV).to receive(:[]).with('LC_COLLATE').and_return(nil)
-        end
-
-        it 'raises warning when LANG is non-UTF-8' do
-          allow(ENV).to receive(:[]).with('LANG').and_return('en_SG ISO-8859-1')
-
-          described_class.check_locale
-
-          expect_logged_warning("Environment variable LANG specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
-        end
-      end
-
-      context 'with only LC_CTYPE set to UTF-8' do
-        before do
-          allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_US.UTF-8')
-          allow(ENV).to receive(:[]).with('LC_COLLATE').and_return(nil)
-        end
-
-        it 'raises warning when LANG is non-UTF-8' do
-          allow(ENV).to receive(:[]).with('LANG').and_return('en_SG ISO-8859-1')
-
-          described_class.check_locale
-
-          expect_logged_warning("Environment variable LANG specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
-        end
-      end
-
-      context 'with both LC_CTYPE and LC_COLLATE set to UTF-8' do
-        before do
-          allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_US.UTF-8')
-          allow(ENV).to receive(:[]).with('LC_COLLATE').and_return('en_US.UTF-8')
-        end
-
-        it 'does not raise a warning even if LANG is not UTF-8' do
-          described_class.check_locale
-
-          expect_logged_warning("Environment variable LANG specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
-        end
-      end
-    end
-  end
-
-  describe '.resource_available?' do
-    cached(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-    subject(:omnibus_helper) { described_class.new(chef_run.node) }
-
-    it 'returns false for a resource that exists but has not been loaded in runtime' do
-      expect(omnibus_helper.resource_available?('runit_service[geo-logcursor]')).to be_falsey
-    end
-
-    it 'returns true for a resource that exists and is loaded in runtime' do
-      expect(omnibus_helper.resource_available?('runit_service[logrotated]'))
-    end
-  end
-end
diff --git a/spec/chef/package/libraries/services_helper_spec.rb b/spec/chef/package/libraries/services_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvbGlicmFyaWVzL3NlcnZpY2VzX2hlbHBlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/package/libraries/services_helper_spec.rb
+++ /dev/null
@@ -1,229 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe Services do
-  let(:chef_run) { ChefSpec::SoloRunner.new }
-
-  subject(:services) { described_class }
-
-  describe '.enable_group' do
-    before do
-      chef_run.converge('gitlab::default')
-    end
-
-    it 'enables services in provide groups list' do
-      expect do
-        services.enable_group('pages_role')
-      end.to change { services.enabled?('gitlab_pages') }.from(false).to(true)
-    end
-
-    it 'skips services that dont `exist?`' do
-      expect do
-        services.enable_group('monitoring')
-      end.not_to change { services.enabled?('pgbouncer_exporter') }
-    end
-  end
-
-  describe '.disable_group' do
-    before do
-      chef_run.converge('gitlab::default')
-    end
-
-    it 'disables services in the provide groups list when setting to false' do
-      expect do
-        services.disable_group('redis')
-      end.to change { services.enabled?('redis') }.from(true).to(false)
-    end
-
-    it 'it allows disabling services in system group when setting to false and include_system: true' do
-      expect do
-        services.disable_group('system', include_system: true)
-      end.to change { services.enabled?('logrotate') }.from(true).to(false)
-    end
-  end
-
-  describe '.enable' do
-    before do
-      chef_run.converge('gitlab::default')
-    end
-
-    it 'enables provide service' do
-      expect do
-        services.enable('gitlab_pages')
-      end.to change { services.enabled?('gitlab_pages') }.from(false).to(true)
-    end
-
-    it 'skips services that dont `exist?`' do
-      expect do
-        services.enable('pgbouncer_exporter')
-      end.not_to change { services.enabled?('pgbouncer_exporter') }
-    end
-  end
-
-  describe '.disable' do
-    before do
-      chef_run.converge('gitlab::default')
-    end
-
-    it 'disables provide service' do
-      expect do
-        services.disable('puma')
-      end.to change { services.enabled?('puma') }.from(true).to(false)
-    end
-
-    it 'allows disabling service in system group when include_system: true' do
-      expect do
-        services.disable('logrotate', include_system: true)
-      end.to change { services.enabled?('logrotate') }.from(true).to(false)
-    end
-  end
-
-  describe '.set_status' do
-    before do
-      chef_run.converge('gitlab::default')
-    end
-
-    it 'enables provide service when setting to true' do
-      expect do
-        services.set_status('gitlab_pages', true)
-      end.to change { services.enabled?('gitlab_pages') }.from(false).to(true)
-    end
-
-    it 'disables provide service when setting to false' do
-      expect do
-        services.set_status('puma', false)
-      end.to change { services.enabled?('puma') }.from(true).to(false)
-    end
-
-    it 'allows disabling service in system group when setting to false and include_system: true' do
-      expect do
-        services.set_status('logrotate', false, include_system: true)
-      end.to change { services.enabled?('logrotate') }.from(true).to(false)
-    end
-  end
-
-  describe '.set_group_status' do
-    before do
-      chef_run.converge('gitlab::default')
-    end
-
-    it 'enables services in provide groups list when setting to true' do
-      expect do
-        services.set_group_status('pages_role', true)
-      end.to change { services.enabled?('gitlab_pages') }.from(false).to(true)
-    end
-
-    it 'disables services in the provide groups list when setting to false' do
-      expect do
-        services.set_group_status('redis', false)
-      end.to change { services.enabled?('redis') }.from(true).to(false)
-    end
-
-    it 'it allows disabling services in system group when setting to false and include_system: true' do
-      expect do
-        services.set_group_status('system', false, include_system: true)
-      end.to change { services.enabled?('logrotate') }.from(true).to(false)
-    end
-  end
-
-  describe '.system_services' do
-    it 'returns a list of services that are in the SYSTEM_GROUP' do
-      service_list = {
-        'redis' => { groups: %w[redis] },
-        'logrotate' => { groups: %w[system] }
-      }
-      services.add_services('test', service_list)
-
-      expect(services.system_services).to include('logrotate')
-    end
-  end
-
-  describe '.find_by_group' do
-    before do
-      service_list = {
-        'redis' => { groups: %w[redis] },
-        'redis_exporter' => { groups: %w[redis monitoring] }
-      }
-
-      services.add_services('test', service_list)
-    end
-
-    it 'returns a list of services that matches provided groups' do
-      expect(services.find_by_group('monitoring')).to eq(['redis_exporter'])
-    end
-
-    it 'returns an empty list when no provide groups match' do
-      expect(services.find_by_group('non_existent')).to eq([])
-    end
-  end
-
-  describe '.service_list' do
-    it 'returns a list of loaded known services by existing cookbooks' do
-      chef_run.converge('gitlab::default')
-
-      expect(services.service_list).to include(Services::BaseServices.list)
-    end
-  end
-
-  describe '.add_services' do
-    it 'adds provided services and register associated cookbook' do
-      services.add_services('gitlab', Services::BaseServices.list)
-
-      expect(services.service_list).to include(Services::BaseServices.list)
-      expect(services.send(:cookbook_services).keys).to include('gitlab')
-    end
-  end
-
-  describe '.enabled?' do
-    before do
-      chef_run.converge('gitlab::default')
-    end
-
-    context 'when status is set via configuration file' do
-      it 'returns true if set as enabled' do
-        Gitlab['mailroom']['enable'] = true
-
-        expect(services.enabled?('mailroom')).to be_truthy
-      end
-
-      it 'returns false if set as disabled' do
-        Gitlab['puma']['enable'] = false
-
-        expect(services.enabled?('puma')).to be_falsey
-      end
-    end
-
-    context 'when status is not set via configuration file' do
-      it 'returns true when its enabled via cookbooks' do
-        expect(services.enabled?('puma')).to be_truthy
-      end
-
-      it 'returns false when disabled via cookbooks' do
-        expect(services.enabled?('mailroom')).to be_falsey
-      end
-    end
-  end
-
-  describe '.exist?' do
-    it 'returns false for non existing service' do
-      expect(services.exist?('inexistent_service')).to be_falsey
-    end
-
-    context 'with only CE services registered' do
-      it 'returns true for CE services and false for EE services' do
-        chef_run.converge('gitlab::default')
-
-        expect(services.exist?('logrotate')).to be_truthy
-        expect(services.exist?('pgbouncer_exporter')).to be_falsey
-      end
-    end
-
-    context 'with CE and EE services registered' do
-      it 'returns true for EE services registered via add_service' do
-        chef_run.converge('gitlab::default', 'gitlab-ee::default')
-
-        expect(services.exist?('logrotate')).to be_truthy
-        expect(services.exist?('pgbouncer_exporter')).to be_truthy
-      end
-    end
-  end
-end
diff --git a/spec/chef/package/recipes/runit_spec.rb b/spec/chef/package/recipes/runit_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvcmVjaXBlcy9ydW5pdF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/package/recipes/runit_spec.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'package::runit' do
-  before do
-    allow(Chef::Log).to receive(:info).and_call_original
-    allow(Chef::Log).to receive(:warn).and_call_original
-    allow(File).to receive(:directory?).and_call_original
-    allow(File).to receive(:directory?).with('/run/systemd/system').and_return(false)
-    allow(File).to receive(:exist?).and_call_original
-    allow(File).to receive(:exist?).with('/.dockerenv').and_return(false)
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'default values' do
-    let(:chef_run) { ChefSpec::SoloRunner.converge('package::runit') }
-
-    before do
-      allow_any_instance_of(Chef::Recipe).to receive(:include_recipe)
-    end
-
-    it 'detects systemd correctly and informs user' do
-      allow(File).to receive(:directory?).with('/run/systemd/system').and_return(true)
-
-      expect_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('package::runit_systemd')
-
-      chef_run
-    end
-
-    it 'detects docker container correctly' do
-      allow(File).to receive(:exist?).with('/.dockerenv').and_return(true)
-
-      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_systemd')
-      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_upstart')
-      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_sysvinit')
-
-      chef_run
-    end
-
-    it 'detects upstart correctly and informs user' do
-      allow(Open3).to receive(:capture3).with('/sbin/init --version | grep upstart').and_return(['', '', double(success?: true)])
-
-      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_systemd')
-      expect_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('package::runit_upstart')
-      expect(Chef::Log).to receive(:warn).with("Selected upstart because /sbin/init --version is showing upstart.")
-
-      chef_run
-    end
-
-    it 'fall backs to sysvinit correctly and informs user' do
-      allow(Open3).to receive(:capture3).with('/sbin/init --version | grep upstart').and_return(['', '', double(success?: false)])
-
-      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_systemd')
-      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_upstart')
-      expect_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('package::runit_sysvinit')
-
-      chef_run
-    end
-  end
-
-  context 'explicitly disabled' do
-    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-    before do
-      stub_gitlab_rb(
-        package: {
-          detect_init: false
-        }
-      )
-    end
-
-    it 'does not run any recipe' do
-      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_upstart')
-      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_systemd')
-      expect_any_instance_of(Chef::Recipe).not_to receive(:include_recipe).with('package::runit_sysvinit')
-
-      chef_run
-    end
-
-    it 'informs the user about skipping init detection' do
-      expect(Chef::Log).to receive(:info).with('Skipped selecting an init system because it was explicitly disabled')
-
-      chef_run
-    end
-  end
-end
diff --git a/spec/chef/package/recipes/runit_systemd_spec.rb b/spec/chef/package/recipes/runit_systemd_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvcmVjaXBlcy9ydW5pdF9zeXN0ZW1kX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/package/recipes/runit_systemd_spec.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'package::runit_systemd' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::config', 'package::runit_systemd') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe 'setting TasksMax value' do
-    context 'when systemd version < 227' do
-      before do
-        allow(SystemdHelper).to receive(:systemd_version).and_return(200)
-      end
-
-      it 'does not include TasksMax setting in unit file' do
-        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service')
-        expect(chef_run).not_to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('TasksMax=4915')
-      end
-    end
-
-    context 'when systemd version >= 227' do
-      before do
-        allow(SystemdHelper).to receive(:systemd_version).and_return(235)
-      end
-
-      it 'include TasksMax setting in unit file' do
-        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('TasksMax=4915')
-      end
-    end
-
-    context 'with user provided value for TasksMax' do
-      before do
-        stub_gitlab_rb(
-          package: { systemd_tasks_max: 10000 }
-        )
-        allow(SystemdHelper).to receive(:systemd_version).and_return(235)
-      end
-
-      it 'sets correct value for TasksMax in unit file' do
-        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('TasksMax=10000')
-      end
-    end
-  end
-
-  describe 'setting WantedBy and After settings for unit file' do
-    context 'by default' do
-      it 'uses "multi-user.target" for WantedBy' do
-        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('WantedBy=multi-user.target')
-      end
-
-      it 'uses "multi-user.target" for WantedBy' do
-        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('After=multi-user.target')
-      end
-    end
-
-    context 'when WantedBy is specified' do
-      before do
-        stub_gitlab_rb(
-          package: { systemd_wanted_by: 'basic.target' }
-        )
-      end
-
-      it 'uses specified value for WantedBy' do
-        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('WantedBy=basic.target')
-      end
-
-      it 'uses value specified for WantedBy for After also' do
-        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('After=basic.target')
-      end
-    end
-
-    context 'when both WantedBy and After are specified' do
-      before do
-        stub_gitlab_rb(
-          package: {
-            systemd_wanted_by: 'foo',
-            systemd_after: 'bar'
-          }
-        )
-      end
-
-      it 'uses specified value for WantedBy' do
-        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('WantedBy=foo')
-      end
-
-      it 'uses specified value for After, not value given for WantedBy' do
-        expect(chef_run).to render_file('/usr/lib/systemd/system/gitlab-runsvdir.service').with_content('After=bar')
-      end
-    end
-  end
-end
diff --git a/spec/chef/package/resources/account_spec.rb b/spec/chef/package/resources/account_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvcmVzb3VyY2VzL2FjY291bnRfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/package/resources/account_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'account' do
-  let(:runner) do
-    ChefSpec::SoloRunner.new(step_into: %w(account))
-  end
-
-  context 'create' do
-    let(:chef_run) { runner.converge('test_package::account_create') }
-
-    it 'creates user' do
-      expect(chef_run).to create_user('foobar').with_username('foo')
-      expect(chef_run).to create_group('foobar').with_group_name('bar')
-    end
-  end
-
-  context 'remove' do
-    let(:chef_run) { runner.converge('test_package::account_remove') }
-
-    it 'remove user' do
-      expect(chef_run).to remove_user('foo')
-      expect(chef_run).to remove_group('bar')
-    end
-  end
-end
diff --git a/spec/chef/package/resources/env_dir_spec.rb b/spec/chef/package/resources/env_dir_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvcmVzb3VyY2VzL2Vudl9kaXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/package/resources/env_dir_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'env_dir' do
-  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(env_dir)) }
-  let(:chef_run) { runner.converge("test_package::env_dir_create") }
-
-  after do
-    FileUtils.rm_rf('/tmp/env')
-  end
-
-  it 'creates env directory' do
-    expect(chef_run).to create_directory('/tmp/env')
-  end
-
-  it 'creates env variable' do
-    expect(chef_run).to create_file('/tmp/env/FOO').with_content('Lorem')
-    expect(chef_run).to create_file('/tmp/env/BAR').with_content('Ipsum')
-  end
-
-  it 'deletes extraneous files' do
-    FileUtils.mkdir_p('/tmp/env')
-    FileUtils.touch('/tmp/env/erase-me')
-
-    expect(chef_run).to delete_file('/tmp/env/erase-me')
-  end
-end
diff --git a/spec/chef/package/resources/gitlab_sysctl_spec.rb b/spec/chef/package/resources/gitlab_sysctl_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvcmVzb3VyY2VzL2dpdGxhYl9zeXNjdGxfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/package/resources/gitlab_sysctl_spec.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'sysctl' do
-  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(gitlab_sysctl)) }
-  let(:chef_run) { runner.converge("package::sysctl", "test_package::gitlab_sysctl_create") }
-  let(:conf) { '90-omnibus-gitlab-foo.conf' }
-
-  context 'with default values' do
-    it 'creates sysctl.d directory for the service' do
-      expect(chef_run).to create_directory('/etc/sysctl.d').with(mode: '0755', recursive: true)
-    end
-
-    it 'creates conf file for the service and loads it' do
-      expect(chef_run).to create_file("create /opt/gitlab/embedded/etc/#{conf} foo").with_content("foo = 15000\n")
-
-      resource = chef_run.file("create /opt/gitlab/embedded/etc/#{conf} foo")
-      expect(resource).to notify("execute[load sysctl conf foo]").to(:run).delayed
-    end
-
-    it 'links conf file to /etc/sysctl.d location' do
-      expect(chef_run).to create_link("/etc/sysctl.d/#{conf}")
-    end
-
-    it 'loaded the settings' do
-      resource = chef_run.execute('load sysctl conf foo')
-      expect(resource.command).to eq("sysctl -e -p /opt/gitlab/embedded/etc/#{conf}")
-      expect(resource).to do_nothing
-    end
-  end
-
-  context 'when modify_kernel_parameters is false' do
-    let(:chef_run) { runner.converge("gitlab::config", "package::sysctl", "test_package::gitlab_sysctl_create") }
-
-    before do
-      allow(Gitlab).to receive(:[]).and_call_original
-
-      stub_gitlab_rb(
-        package: {
-          modify_kernel_parameters: false
-        }
-      )
-    end
-
-    it 'do not create sysctl.d directory' do
-      expect(chef_run).not_to create_directory('/etc/sysctl.d')
-    end
-
-    it 'does not create conf file' do
-      expect(chef_run).not_to create_file("create /opt/gitlab/embedded/etc/#{conf} foo")
-    end
-
-    it 'do not link conf file to /etc/sysctl.d location' do
-      expect(chef_run).not_to create_link("/etc/sysctl.d/#{conf}")
-    end
-  end
-end
diff --git a/spec/chef/package/resources/storage_directory_spec.rb b/spec/chef/package/resources/storage_directory_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvcmVzb3VyY2VzL3N0b3JhZ2VfZGlyZWN0b3J5X3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/package/resources/storage_directory_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'storage_directory' do
-  let(:runner) do
-    ChefSpec::SoloRunner.new(step_into: %w(storage_directory)) do |node|
-      node.normal['gitlab']['manage-storage-directories']['enable'] = true
-    end
-  end
-  let(:chef_run) { runner.converge("test_package::storage_directory_create") }
-
-  it 'executes correct ruby block' do
-    allow_any_instance_of(StorageDirectoryHelper).to receive(:ensure_directory_exists).and_return(true)
-    allow_any_instance_of(StorageDirectoryHelper).to receive(:ensure_permissions_set).and_return(true)
-    allow_any_instance_of(StorageDirectoryHelper).to receive(:validate!).and_return(true)
-
-    expect(chef_run).to run_ruby_block('directory resource: /tmp/bar')
-    expect_any_instance_of(StorageDirectoryHelper).to receive(:ensure_directory_exists)
-
-    ruby_block = chef_run.ruby_block('directory resource: /tmp/bar')
-    ruby_block.block.call
-  end
-end
diff --git a/spec/chef/package/resources/templatesymlink_spec.rb b/spec/chef/package/resources/templatesymlink_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvcmVzb3VyY2VzL3RlbXBsYXRlc3ltbGlua19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/package/resources/templatesymlink_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'templatesymlink' do
-  let(:runner) do
-    ChefSpec::SoloRunner.new(step_into: %w(templatesymlink))
-  end
-
-  context 'create' do
-    let(:chef_run) { runner.converge('test_package::templatesymlink_create') }
-
-    it 'creates symlinks' do
-      expect(chef_run).to create_link('/opt/gitlab/embedded/service/gitlab-rails/config/database.yml')
-    end
-
-    it 'populates conf file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/database.yml').with_content('value: 500')
-    end
-  end
-  context 'delete' do
-    let(:chef_run) { runner.converge('test_package::templatesymlink_delete') }
-
-    it 'deletes symlinks' do
-      expect(chef_run).to delete_link('/opt/gitlab/embedded/service/gitlab-rails/config/database.yml')
-    end
-  end
-end
diff --git a/spec/chef/package/resources/version_file_spec.rb b/spec/chef/package/resources/version_file_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhY2thZ2UvcmVzb3VyY2VzL3ZlcnNpb25fZmlsZV9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/package/resources/version_file_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'chef_helper'
-
-version_file_path = '/tmp/VERSION_TEST'
-version_check_cmd = 'echo 1.0.0-test'
-
-# Collect version as the custom resource would
-version = VersionHelper.version(version_check_cmd)
-
-RSpec.describe 'version_file' do
-  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(version_file)) }
-  let(:chef_run) { runner.converge("test_package::version_file_create") }
-
-  after do
-    FileUtils.rm_rf(version_file_path)
-  end
-
-  it 'creates version file' do
-    expect(chef_run).to create_file(version_file_path).with_content(version)
-  end
-
-  it 'restarts service' do
-    expect(chef_run.version_file('Test version file creation')).to notify('runit_service[foo]').to(:hup)
-  end
-end
diff --git a/spec/chef/patroni/libraries/patroni_helper_spec.rb b/spec/chef/patroni/libraries/patroni_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhdHJvbmkvbGlicmFyaWVzL3BhdHJvbmlfaGVscGVyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/patroni/libraries/patroni_helper_spec.rb
+++ /dev/null
@@ -1,144 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe PatroniHelper do
-  let(:chef_run) do
-    ChefSpec::SoloRunner.new(step_into: %w(patroni)).converge('gitlab-ee::default')
-  end
-
-  subject(:helper) { PatroniHelper.new(chef_run.node) }
-  let(:pg_helper) { PgHelper.new(chef_run.node) }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe '#ctl_command' do
-    it 'returns a full path to the ctl_command' do
-      expect(helper.ctl_command).to eq('/opt/gitlab/embedded/bin/patronictl')
-    end
-  end
-
-  describe '#bootstrapped?' do
-    before do
-      allow(File).to receive(:exist?).and_call_original
-    end
-
-    it 'returns true when patroni.dynamic.json exists in postgresql data directory' do
-      allow(File).to receive(:exist?).with('/var/opt/gitlab/postgresql/data/patroni.dynamic.json').and_return(true)
-
-      expect(helper.bootstrapped?).to eq(true)
-    end
-
-    it 'returns false when patroni.dynamic.json does not exist in postgresql data directory' do
-      allow(File).to receive(:exist?).with('/var/opt/gitlab/postgresql/data/patroni.dynamic.json').and_return(false)
-
-      expect(helper.bootstrapped?).to eq(false)
-    end
-  end
-
-  describe '#dynamic_settings' do
-    it 'returns a hash with required keys' do
-      expected_root_keys = PatroniHelper::DCS_ATTRIBUTES + %w[postgresql slots]
-
-      expect(helper.dynamic_settings(pg_helper).keys).to match_array(expected_root_keys)
-    end
-
-    context 'with standby cluster enabled' do
-      it 'includes standby cluster attributes' do
-        stub_gitlab_rb(
-          patroni: {
-            enable: true,
-            standby_cluster: {
-              enable: true
-            }
-          }
-        )
-
-        expected_root_keys = PatroniHelper::DCS_ATTRIBUTES + %w[postgresql slots standby_cluster]
-
-        expect(helper.dynamic_settings(pg_helper).keys).to match_array(expected_root_keys)
-      end
-    end
-  end
-
-  describe '#node_status' do
-    context 'when Patroni service is down' do
-      before do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('patroni').and_return(false)
-      end
-
-      it 'returns not running' do
-        expect(helper.node_status).to eq 'not running'
-      end
-    end
-
-    context 'when Patroni service is up' do
-      before do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('patroni').and_return(true)
-
-        result = spy('shellout')
-        allow(helper).to receive(:do_shell_out).and_return(result)
-        allow(result).to receive(:stdout).and_return(' running')
-      end
-
-      it 'returns running current node state' do
-        expect(helper.node_status).to eq 'running'
-      end
-    end
-  end
-
-  describe '#public_attributes' do
-    context 'when patroni is enabled' do
-      it 'returns a hash with required keys' do
-        stub_gitlab_rb(
-          patroni: {
-            enable: true
-          }
-        )
-
-        expected_patroni_keys = %w(config_dir data_dir log_dir api_address)
-
-        expect(helper.public_attributes.keys).to match_array('patroni')
-        expect(helper.public_attributes['patroni'].keys).to match_array(expected_patroni_keys)
-      end
-    end
-
-    context 'when patroni is enabled with tls api' do
-      it 'returns a hash with required keys' do
-        stub_gitlab_rb(
-          patroni: {
-            enable: true,
-            tls_certificate_file: '/path/to/crt.pem',
-            tls_key_file: '/path/to/key.pem',
-            tls_key_password: 'fakepassword',
-            tls_ca_file: '/path/to/ca.pem',
-            tls_ciphers: 'CIPHERS LIST',
-            tls_client_mode: 'optional',
-            tls_client_certificate_file: '/path/to/client.pem',
-            tls_client_key_file: '/path/to/client.key'
-          }
-        )
-
-        expected_tls_cfg = {
-          'tls_verify' => true,
-          'ca_file' => '/path/to/ca.pem',
-          'verify_client' => true,
-          'client_cert' => '/path/to/client.pem',
-          'client_key' => '/path/to/client.key'
-        }
-
-        expect(helper.public_attributes.keys).to match_array('patroni')
-        expect(helper.public_attributes['patroni']).to include(expected_tls_cfg)
-        expect(helper.public_attributes['patroni']['api_address']).to start_with('https://')
-      end
-    end
-
-    context 'when patroni is disabled' do
-      it 'returns an empty hash' do
-        expect(helper.public_attributes).to be_empty
-      end
-    end
-  end
-end
diff --git a/spec/chef/patroni/recipes/patroni_spec.rb b/spec/chef/patroni/recipes/patroni_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BhdHJvbmkvcmVjaXBlcy9wYXRyb25pX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/patroni/recipes/patroni_spec.rb
+++ /dev/null
@@ -1,734 +0,0 @@
-require 'chef_helper'
-require 'yaml'
-
-RSpec.describe 'patroni cookbook' do
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  let(:chef_run) do
-    ChefSpec::SoloRunner.new(step_into: %w(database_objects)).converge('gitlab-ee::default')
-  end
-
-  it 'should be disabled by default' do
-    expect(chef_run).to include_recipe('patroni::disable')
-  end
-
-  context 'when postgres_role is enabled' do
-    before do
-      stub_gitlab_rb(roles: %w(postgres_role))
-    end
-
-    it 'should be disabled' do
-      expect(chef_run).to include_recipe('patroni::disable')
-    end
-  end
-
-  context 'when patroni_role is configured' do
-    before do
-      stub_gitlab_rb(roles: %w(patroni_role))
-    end
-
-    it 'should be enabled' do
-      expect(chef_run).to include_recipe('patroni::enable')
-    end
-  end
-
-  context 'when patroni_role and postgres_role are configured' do
-    before do
-      stub_gitlab_rb(roles: %w(postgres_role patroni_role))
-    end
-
-    it 'should be enabled' do
-      expect(chef_run).to include_recipe('patroni::enable')
-    end
-  end
-
-  context 'when enabled with default config' do
-    before do
-      stub_gitlab_rb(
-        roles: %w(patroni_role),
-        postgresql: {
-          pgbouncer_user_password: ''
-        }
-      )
-      allow_any_instance_of(OmnibusHelper).to receive(:service_dir_enabled?).and_return(true)
-      allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
-      allow_any_instance_of(PgHelper).to receive(:bootstrapped?).and_return(false)
-      allow_any_instance_of(PgHelper).to receive(:is_replica?).and_return(false)
-    end
-
-    let(:default_patroni_config_pg13) do
-      {
-        name: 'fauxhai.local',
-        scope: 'postgresql-ha',
-        log: {
-          level: 'INFO'
-        },
-        consul: {
-          url: 'http://127.0.0.1:8500',
-          service_check_interval: '10s',
-          register_service: true,
-          checks: [],
-        },
-        postgresql: {
-          bin_dir: '/opt/gitlab/embedded/bin',
-          data_dir: '/var/opt/gitlab/postgresql/data',
-          config_dir: '/var/opt/gitlab/postgresql/data',
-          listen: :'5432',
-          connect_address: "#{Patroni.private_ipv4}:5432",
-          use_unix_socket: true,
-          parameters: {
-            unix_socket_directories: '/var/opt/gitlab/postgresql'
-          },
-          authentication: {
-            superuser: {
-              username: 'gitlab-psql'
-            },
-            replication: {
-              username: 'gitlab_replicator'
-            },
-          },
-          remove_data_directory_on_diverged_timelines: false,
-          remove_data_directory_on_rewind_failure: false,
-          basebackup: [
-            'no-password'
-          ],
-        },
-        bootstrap: {
-          dcs: {
-            loop_wait: 10,
-            ttl: 30,
-            retry_timeout: 10,
-            maximum_lag_on_failover: 1_048_576,
-            max_timelines_history: 0,
-            master_start_timeout: 300,
-            postgresql: {
-              use_pg_rewind: true,
-              use_slots: true,
-              parameters: {
-                wal_level: 'replica',
-                hot_standby: 'on',
-                wal_keep_size: 160,
-                max_replication_slots: 5,
-                max_connections: 200,
-                max_locks_per_transaction: 128,
-                max_worker_processes: 8,
-                max_wal_senders: 5,
-                checkpoint_timeout: 30,
-                max_prepared_transactions: 0,
-                track_commit_timestamp: 'off',
-                wal_log_hints: 'off'
-              },
-            },
-            slots: {},
-          },
-          method: 'gitlab_ctl',
-          gitlab_ctl: {
-            command: '/opt/gitlab/bin/gitlab-ctl patroni bootstrap --srcdir=/var/opt/gitlab/patroni/data'
-          }
-        },
-        restapi: {
-          listen: :'8008',
-          connect_address: "#{Patroni.private_ipv4}:8008",
-          allowlist_include_members: false
-        },
-      }
-    end
-
-    let(:default_patroni_config) do
-      {
-        name: 'fauxhai.local',
-        scope: 'postgresql-ha',
-        log: {
-          level: 'INFO'
-        },
-        consul: {
-          url: 'http://127.0.0.1:8500',
-          service_check_interval: '10s',
-          register_service: true,
-          checks: [],
-        },
-        postgresql: {
-          bin_dir: '/opt/gitlab/embedded/bin',
-          data_dir: '/var/opt/gitlab/postgresql/data',
-          config_dir: '/var/opt/gitlab/postgresql/data',
-          listen: :'5432',
-          connect_address: "#{Patroni.private_ipv4}:5432",
-          use_unix_socket: true,
-          parameters: {
-            unix_socket_directories: '/var/opt/gitlab/postgresql'
-          },
-          authentication: {
-            superuser: {
-              username: 'gitlab-psql'
-            },
-            replication: {
-              username: 'gitlab_replicator'
-            },
-          },
-          remove_data_directory_on_diverged_timelines: false,
-          remove_data_directory_on_rewind_failure: false,
-          basebackup: [
-            'no-password'
-          ],
-        },
-        bootstrap: {
-          dcs: {
-            loop_wait: 10,
-            ttl: 30,
-            retry_timeout: 10,
-            maximum_lag_on_failover: 1_048_576,
-            max_timelines_history: 0,
-            master_start_timeout: 300,
-            postgresql: {
-              use_pg_rewind: true,
-              use_slots: true,
-              parameters: {
-                wal_level: 'replica',
-                hot_standby: 'on',
-                wal_keep_segments: 10,
-                max_replication_slots: 5,
-                max_connections: 200,
-                max_locks_per_transaction: 128,
-                max_worker_processes: 8,
-                max_wal_senders: 5,
-                checkpoint_timeout: 30,
-                max_prepared_transactions: 0,
-                track_commit_timestamp: 'off',
-                wal_log_hints: 'off'
-              },
-            },
-            slots: {},
-          },
-          method: 'gitlab_ctl',
-          gitlab_ctl: {
-            command: '/opt/gitlab/bin/gitlab-ctl patroni bootstrap --srcdir=/var/opt/gitlab/patroni/data'
-          }
-        },
-        restapi: {
-          listen: :'8008',
-          connect_address: "#{Patroni.private_ipv4}:8008",
-          allowlist_include_members: false
-        },
-      }
-    end
-
-    it 'should enable patroni service and disable postgresql runit service' do
-      expect(chef_run).to enable_runit_service('patroni')
-      expect(chef_run).to disable_runit_service('postgresql')
-    end
-
-    it 'should notify patroni service to hup' do
-      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).and_call_original
-      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).with('patroni').and_return(true)
-
-      expect(chef_run.template('/var/opt/gitlab/patroni/patroni.yaml')).to notify('runit_service[patroni]').to(:hup)
-    end
-
-    it 'should skip standalone postgresql configuration' do
-      expect(chef_run).to create_postgresql_config('gitlab')
-      expect(chef_run.postgresql_config('gitlab')).not_to notify('execute[start postgresql]').to(:run)
-      expect(chef_run).not_to run_execute(/(start|reload) postgresql/)
-    end
-
-    it 'should create database objects (roles, databses, extension)' do
-      expect(chef_run).not_to run_execute('/opt/gitlab/embedded/bin/initdb -D /var/opt/gitlab/postgresql/data -E UTF8')
-      expect(chef_run).to create_postgresql_user('gitlab')
-      expect(chef_run).to create_postgresql_user('gitlab_replicator')
-      expect(chef_run).to create_pgbouncer_user('rails')
-      expect(chef_run).to create_postgresql_database('gitlabhq_production')
-      expect(chef_run).to enable_postgresql_extension('pg_trgm')
-      expect(chef_run).to enable_postgresql_extension('btree_gist')
-    end
-
-    it 'should create patroni configuration file for PostgreSQL 12' do
-      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
-        expect(YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)).to eq(default_patroni_config)
-      }
-    end
-
-    it 'should create patroni configuration file for PostgreSQL 13' do
-      allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('13.0'))
-      allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('13.0'))
-
-      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
-        expect(YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)).to eq(default_patroni_config_pg13)
-      }
-    end
-  end
-
-  context 'when enabled with specific config' do
-    before do
-      stub_gitlab_rb(
-        roles: %w(postgres_role),
-        postgresql: {
-          username: 'test_psql_user',
-          sql_user: 'test_sql_user',
-          sql_user_password: 'dbda601b8d4dc3d1697ef84dbbb8e61b',
-          sql_replication_user: 'test_sql_replication_user',
-          sql_replication_password: '48e84afb4b268128ac14f7c66fc7af42',
-          pgbouncer_user: 'test_pgbouncer_user',
-          pgbouncer_user_password: '2bc94731612abb74aea7805a41dfcb09',
-          connect_port: 15432,
-        },
-        patroni: {
-          enable: true,
-          scope: 'test-scope',
-          name: 'test-node-name',
-          log_level: 'DEBUG',
-          loop_wait: 20,
-          ttl: 60,
-          master_start_timeout: 600,
-          use_slots: false,
-          use_pg_rewind: true,
-          connect_address: '1.2.3.4',
-          connect_port: 18008,
-          username: 'gitlab',
-          password: 'restapipassword',
-          replication_password: 'fakepassword',
-          allowlist: ['1.2.3.4/32', '127.0.0.1/32'],
-          allowlist_include_members: false,
-          remove_data_directory_on_diverged_timelines: true,
-          remove_data_directory_on_rewind_failure: true,
-          replication_slots: {
-            'geo_secondary' => { 'type' => 'physical' }
-          },
-          consul: {
-            service_check_interval: '20s'
-          },
-          postgresql: {
-            wal_keep_segments: 16,
-            max_wal_senders: 4,
-            max_replication_slots: 4
-          },
-          tags: {
-            nofailover: true
-          },
-          callbacks: {
-            on_role_change: "/patroni/scripts/post-failover-maintenance.sh"
-          },
-          recovery_conf: {
-            restore_command: "/opt/wal-g/bin/wal-g wal-fetch %f %p"
-          }
-        }
-      )
-    end
-
-    it 'should be reflected in patroni configuration file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
-        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
-
-        expect(cfg).to include(
-          name: 'test-node-name',
-          scope: 'test-scope',
-          log: {
-            level: 'DEBUG'
-          },
-          tags: {
-            nofailover: true
-          }
-        )
-        expect(cfg[:consul][:service_check_interval]).to eq('20s')
-        expect(cfg[:postgresql][:connect_address]).to eq('1.2.3.4:15432')
-        expect(cfg[:postgresql][:authentication]).to eq(
-          superuser: {
-            username: 'test_psql_user'
-          },
-          replication: {
-            username: 'test_sql_replication_user',
-            password: 'fakepassword'
-          }
-        )
-        expect(cfg[:postgresql][:callbacks]).to eq(
-          on_role_change: "/patroni/scripts/post-failover-maintenance.sh"
-        )
-        expect(cfg[:postgresql][:recovery_conf]).to eq(
-          restore_command: "/opt/wal-g/bin/wal-g wal-fetch %f %p"
-        )
-        expect(cfg[:restapi]).to include(
-          connect_address: '1.2.3.4:18008',
-          authentication: {
-            username: 'gitlab',
-            password: 'restapipassword'
-          },
-          allowlist: ['1.2.3.4/32', '127.0.0.1/32'],
-          allowlist_include_members: false
-        )
-        expect(cfg[:bootstrap][:dcs]).to include(
-          loop_wait: 20,
-          ttl: 60,
-          master_start_timeout: 600,
-          slots: {
-            geo_secondary: { type: 'physical' }
-          }
-        )
-        expect(cfg[:bootstrap][:dcs][:postgresql]).to include(
-          use_slots: false,
-          use_pg_rewind: true
-        )
-        expect(cfg[:bootstrap][:dcs][:postgresql][:parameters]).to include(
-          wal_keep_segments: 16,
-          max_wal_senders: 4,
-          max_replication_slots: 4
-        )
-        expect(cfg[:postgresql][:remove_data_directory_on_rewind_failure]).to be true
-        expect(cfg[:postgresql][:remove_data_directory_on_diverged_timelines]).to be true
-      }
-    end
-
-    it 'should reflect into dcs config file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
-        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
-
-        expect(cfg).to include(
-          loop_wait: 20,
-          ttl: 60,
-          master_start_timeout: 600,
-          slots: {
-            geo_secondary: { type: 'physical' }
-          }
-        )
-        expect(cfg[:postgresql]).to include(
-          use_slots: false,
-          use_pg_rewind: true
-        )
-        expect(cfg[:postgresql][:parameters]).to include(
-          wal_keep_segments: 16,
-          max_wal_senders: 4,
-          max_replication_slots: 4
-        )
-      }
-    end
-  end
-
-  context 'when standby cluster is enabled' do
-    before do
-      stub_gitlab_rb(
-        roles: %w(postgres_role),
-        patroni: {
-          enable: true,
-          use_pg_rewind: true,
-          replication_password: 'fakepassword',
-          standby_cluster: {
-            enable: true,
-            host: '1.2.3.4',
-            port: 5432,
-            primary_slot_name: 'geo_secondary'
-          }
-        },
-        postgresql: {
-          sql_user_password: 'a4125c87ce2572ce271cd77e0de9a0ad',
-          sql_replication_password: 'e64b415e9b9a34ac7ac6e53ae16ccacb',
-          md5_auth_cidr_addresses: '1.2.3.4/32'
-        }
-      )
-    end
-
-    it 'should be reflected in patroni configuration file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
-        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
-
-        expect(cfg[:postgresql][:authentication]).to include(
-          replication: {
-            username: 'gitlab_replicator',
-            password: 'fakepassword'
-          }
-        )
-        expect(cfg[:bootstrap][:dcs]).to include(
-          standby_cluster: {
-            host: '1.2.3.4',
-            port: 5432,
-            primary_slot_name: 'geo_secondary'
-          }
-        )
-        expect(cfg[:bootstrap][:dcs][:postgresql]).to include(
-          use_pg_rewind: true
-        )
-      }
-    end
-
-    it 'should reflect into dcs config file' do
-      expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
-        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
-
-        expect(cfg).to include(
-          standby_cluster: {
-            host: '1.2.3.4',
-            port: 5432,
-            primary_slot_name: 'geo_secondary'
-          }
-        )
-        expect(cfg[:postgresql]).to include(
-          use_pg_rewind: true
-        )
-      }
-    end
-  end
-
-  context 'when building a cluster' do
-    before do
-      stub_gitlab_rb(
-        roles: %w(postgres_role),
-        patroni: {
-          enable: true
-        }
-      )
-    end
-
-    context 'from scratch' do
-      before do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_dir_enabled?).and_return(false)
-      end
-
-      it 'should enable patroni service and disable postgresql runit service' do
-        expect(chef_run).to enable_runit_service('patroni')
-        expect(chef_run).to disable_runit_service('postgresql')
-      end
-    end
-
-    context 'converting a standalone instance to a cluster member' do
-      before do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_dir_enabled?).and_return(true)
-        allow_any_instance_of(PatroniHelper).to receive(:node_status).and_return('running')
-      end
-
-      it 'should signal to node to restart postgresql and disable its runit service' do
-        expect(chef_run).to enable_runit_service('patroni')
-        expect(chef_run).to disable_runit_service('postgresql')
-        expect(chef_run).to run_execute('signal to restart postgresql')
-      end
-    end
-
-    context 'on a replica' do
-      before do
-        allow_any_instance_of(PgHelper).to receive(:replica?).and_return(true)
-      end
-
-      it 'should not create database objects' do
-        expect(chef_run).not_to create_postgresql_user('gitlab')
-        expect(chef_run).not_to create_postgresql_user('gitlab_replicator')
-        expect(chef_run).not_to create_pgbouncer_user('patroni')
-        expect(chef_run).not_to run_execute('create gitlabhq_production database')
-        expect(chef_run).not_to enable_postgresql_extension('pg_trgm')
-        expect(chef_run).not_to enable_postgresql_extension('btree_gist')
-      end
-    end
-  end
-
-  context 'postgresql dynamic configuration' do
-    before do
-      allow(Gitlab).to receive(:[]).and_call_original
-    end
-
-    context 'with no explicit override' do
-      before do
-        stub_gitlab_rb(
-          roles: %w(postgres_role),
-          patroni: {
-            enable: true
-          }
-        )
-      end
-
-      it 'should use default values from postgresql cookbook and handle corner cases' do
-        expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
-          cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
-
-          expect(cfg[:postgresql][:use_pg_rewind]).to be(true)
-          expect(cfg[:postgresql][:parameters]).to include(
-            max_connections: 200,
-            max_locks_per_transaction: 128,
-            max_worker_processes: 8,
-            max_prepared_transactions: 0,
-            track_commit_timestamp: 'off',
-            wal_log_hints: 'off',
-            max_wal_senders: 5,
-            max_replication_slots: 5,
-            wal_keep_segments: 10,
-            checkpoint_timeout: 30
-          )
-        }
-      end
-    end
-
-    context 'with no explicit override and non-default postgresql settings' do
-      before do
-        stub_gitlab_rb(
-          roles: %w(postgres_role),
-          patroni: {
-            enable: true,
-          },
-          postgresql: {
-            max_connections: 123,
-            max_locks_per_transaction: 321,
-            max_worker_processes: 12,
-            wal_log_hints: 'foo',
-            max_wal_senders: 11,
-            max_replication_slots: 13,
-          }
-        )
-      end
-
-      it 'should use default values from postgresql cookbook' do
-        expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
-          cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
-
-          expect(cfg[:postgresql][:use_pg_rewind]).to be(true)
-          expect(cfg[:postgresql][:parameters]).to include(
-            max_connections: 123,
-            max_locks_per_transaction: 321,
-            max_worker_processes: 12,
-            max_prepared_transactions: 0,
-            track_commit_timestamp: 'off',
-            wal_log_hints: 'foo',
-            max_wal_senders: 11,
-            max_replication_slots: 13,
-            wal_keep_segments: 10,
-            checkpoint_timeout: 30
-          )
-        }
-      end
-    end
-
-    context 'with explicit override' do
-      before do
-        stub_gitlab_rb(
-          roles: %w(postgres_role),
-          patroni: {
-            enable: true,
-            postgresql: {
-              max_connections: 100,
-              max_locks_per_transaction: 64,
-              max_worker_processes: 4,
-              wal_log_hints: 'on',
-              max_wal_senders: 0,
-              max_replication_slots: 0,
-              checkpoint_timeout: '5min'
-            }
-          }
-        )
-      end
-
-      it 'should use default values from postgresql cookbook' do
-        expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
-          cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
-
-          expect(cfg[:postgresql][:use_pg_rewind]).to be(true)
-          expect(cfg[:postgresql][:parameters]).to include(
-            max_connections: 100,
-            max_locks_per_transaction: 64,
-            max_worker_processes: 4,
-            max_wal_senders: 0,
-            max_replication_slots: 0,
-            wal_log_hints: 'on'
-          )
-        }
-      end
-    end
-
-    context 'when pg_rewind is enabled' do
-      before do
-        stub_gitlab_rb(
-          roles: %w(postgres_role),
-          patroni: {
-            enable: true,
-            use_pg_rewind: true,
-            remove_data_directory_on_diverged_timelines: true,
-            remove_data_directory_on_rewind_failure: true
-          }
-        )
-      end
-
-      it 'should use default values from postgresql cookbook' do
-        expect(chef_run).to render_file('/var/opt/gitlab/patroni/dcs.yaml').with_content { |content|
-          cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
-
-          expect(cfg[:postgresql][:use_pg_rewind]).to be(true)
-          expect(cfg[:postgresql][:parameters]).to include(
-            max_connections: 200,
-            max_locks_per_transaction: 128,
-            max_worker_processes: 8,
-            wal_log_hints: 'on'
-          )
-        }
-      end
-    end
-  end
-
-  context 'when patroni is enabled but consul is not' do
-    let(:chef_run) do
-      converge_config('gitlab-ee::default', is_ee: true)
-    end
-
-    before do
-      stub_gitlab_rb(
-        patroni: {
-          enable: true
-        }
-      )
-    end
-
-    it 'expects a warning to be printed' do
-      chef_run
-
-      expect_logged_warning(/Patroni is enabled but Consul seems to be disabled/)
-    end
-  end
-
-  context 'when tls is enabled' do
-    it 'should only set the path to the tls certificate and key' do
-      stub_gitlab_rb(
-        roles: %w(postgres_role),
-        patroni: {
-          enable: true,
-          tls_certificate_file: '/path/to/crt.pem',
-          tls_key_file: '/path/to/key.pem'
-        }
-      )
-
-      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
-        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
-
-        expect(cfg[:restapi][:certfile]).to eq('/path/to/crt.pem')
-        expect(cfg[:restapi][:keyfile]).to eq('/path/to/key.pem')
-        expect(cfg[:restapi][:keyfile_password]).to be(nil)
-        expect(cfg[:restapi][:cafile]).to be(nil)
-        expect(cfg[:restapi][:ciphers]).to be(nil)
-        expect(cfg[:restapi][:verify_client]).to be(nil)
-        expect(cfg[:ctl]).to be(nil)
-      }
-    end
-
-    it 'should set all available tls configuration including tls certificate and key paths' do
-      stub_gitlab_rb(
-        roles: %w(postgres_role),
-        patroni: {
-          enable: true,
-          tls_certificate_file: '/path/to/crt.pem',
-          tls_key_file: '/path/to/key.pem',
-          tls_key_password: 'fakepassword',
-          tls_ca_file: '/path/to/ca.pem',
-          tls_ciphers: 'CIPHERS LIST',
-          tls_client_mode: 'optional',
-          tls_client_certificate_file: '/path/to/client.pem',
-          tls_client_key_file: '/path/to/client.key'
-        }
-      )
-
-      expect(chef_run).to render_file('/var/opt/gitlab/patroni/patroni.yaml').with_content { |content|
-        cfg = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
-
-        expect(cfg[:restapi][:certfile]).to eq('/path/to/crt.pem')
-        expect(cfg[:restapi][:keyfile]).to eq('/path/to/key.pem')
-        expect(cfg[:restapi][:keyfile_password]).to eq('fakepassword')
-        expect(cfg[:restapi][:cafile]).to eq('/path/to/ca.pem')
-        expect(cfg[:restapi][:ciphers]).to eq('CIPHERS LIST')
-        expect(cfg[:restapi][:verify_client]).to eq('optional')
-        expect(cfg[:ctl][:insecure]).to eq(false)
-        expect(cfg[:ctl][:certfile]).to eq('/path/to/client.pem')
-        expect(cfg[:ctl][:keyfile]).to eq('/path/to/client.key')
-      }
-    end
-  end
-end
diff --git a/spec/chef/pgbouncer/libraries/pgbouncer_helper_spec.rb b/spec/chef/pgbouncer/libraries/pgbouncer_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BnYm91bmNlci9saWJyYXJpZXMvcGdib3VuY2VyX2hlbHBlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/pgbouncer/libraries/pgbouncer_helper_spec.rb
+++ /dev/null
@@ -1,165 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe PgbouncerHelper do
-  let(:chef_run) { converge_config(is_ee: true) }
-  subject { described_class.new(chef_run.node) }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe '#running?' do
-    cached(:chef_run) { converge_config(is_ee: true) }
-
-    it 'returns true when pgbouncer is running' do
-      stub_service_success_status('pgbouncer', true)
-
-      expect(subject.running?).to be_truthy
-    end
-
-    it 'returns false when pgbouncer is not running' do
-      stub_service_success_status('pgbouncer', false)
-
-      expect(subject.running?).to be_falsey
-    end
-  end
-
-  describe '#pg_auth_type_prefix' do
-    using RSpec::Parameterized::TableSyntax
-    where(:type, :prefix) do
-      'md5' | 'md5'
-      'scram-sha-256' | 'SCRAM-SHA-256$'
-      'MD5' | 'md5'
-      'SCRAM-SHA-256' | 'SCRAM-SHA-256$'
-      'plain' | nil
-      'ScRaM-ShA-256' | 'SCRAM-SHA-256$'
-    end
-
-    with_them do
-      it 'responds to default values' do
-        expect(subject.pg_auth_type_prefix(type)).to eq(prefix)
-      end
-    end
-  end
-
-  describe '#pgbouncer_admin_config' do
-    context 'by default' do
-      it 'by default' do
-        expect(subject.pgbouncer_admin_config).to eq('user=pgbouncer dbname=pgbouncer sslmode=disable port=6432 host=/var/opt/gitlab/pgbouncer')
-      end
-    end
-
-    context 'with custom sttings' do
-      before do
-        stub_gitlab_rb(
-          postgresql: {
-            pgbouncer_user: 'tester',
-          },
-          pgbouncer: {
-            listen_port: 1234,
-            data_directory: '/tmp'
-          }
-        )
-      end
-
-      it 'uses custom settings' do
-        expect(subject.pgbouncer_admin_config).to eq('user=tester dbname=pgbouncer sslmode=disable port=1234 host=/tmp')
-      end
-    end
-  end
-
-  describe '#pg_auth_users' do
-    context 'by default' do
-      it 'by default' do
-        expect(subject.pg_auth_users).to be_empty
-      end
-    end
-
-    context 'with users' do
-      before do
-        stub_gitlab_rb(
-          pgbouncer: {
-            users: {
-              fakeuser: {
-                password: 'fakepassword'
-              }
-            }
-          }
-        )
-      end
-
-      it 'returns a hash' do
-        expect(subject.pg_auth_users).to eq 'fakeuser' => { 'password' => 'fakepassword' }
-      end
-    end
-
-    context 'with databases' do
-      before do
-        stub_gitlab_rb(
-          pgbouncer: {
-            databases: {
-              fakedb: {
-                user: 'fakeuser',
-                password: 'fakepassword'
-              }
-            }
-          }
-        )
-      end
-
-      it 'returns a hash' do
-        expect(subject.pg_auth_users).to eq 'fakeuser' => { 'password' => 'fakepassword' }
-      end
-    end
-
-    context 'with both' do
-      before do
-        stub_gitlab_rb(
-          pgbouncer: {
-            databases: {
-              fakedb: {
-                user: 'fakedbuser',
-                password: 'fakepassword'
-              }
-            },
-            users: {
-              fakeusersuser: {
-                password: 'fakepassword'
-              }
-            }
-          }
-        )
-      end
-
-      it 'should merge both' do
-        expected = {
-          'fakeusersuser' => { 'password' => 'fakepassword' }, 'fakedbuser' => { 'password' => 'fakepassword' }
-        }
-        expect(subject.pg_auth_users).to eq expected
-      end
-    end
-
-    context 'with multiple databases' do
-      before do
-        stub_gitlab_rb(
-          pgbouncer: {
-            databases: {
-              fakedb_one: {
-                user: 'fakeuser',
-                password: 'fakepassword'
-              },
-              fakedb_two: {
-                user: 'fakeuser',
-                password: 'fakepassword'
-              }
-            }
-          }
-        )
-      end
-
-      it 'should only have one entry' do
-        expect(subject.pg_auth_users).to eq 'fakeuser' => { 'password' => 'fakepassword' }
-      end
-    end
-  end
-end
diff --git a/spec/chef/pgbouncer/recipes/pgbouncer_spec.rb b/spec/chef/pgbouncer/recipes/pgbouncer_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BnYm91bmNlci9yZWNpcGVzL3BnYm91bmNlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/pgbouncer/recipes/pgbouncer_spec.rb
+++ /dev/null
@@ -1,395 +0,0 @@
-#
-# Copyright:: Copyright (c) 2017 GitLab Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-require 'chef_helper'
-
-RSpec.describe 'pgbouncer' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
-  let(:pgbouncer_ini) { '/var/opt/gitlab/pgbouncer/pgbouncer.ini' }
-  let(:databases_json) { '/var/opt/gitlab/pgbouncer/databases.json' }
-  let(:default_vars) do
-    {
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe 'when enabled' do
-    before do
-      stub_gitlab_rb(
-        pgbouncer: {
-          enable: true,
-          databases: {
-            gitlabhq_production: {
-              host: '1.2.3.4'
-            }
-          }
-        },
-        postgresql: {
-          pgbouncer_user: 'fakeuser',
-          pgbouncer_user_password: 'fakeuserpassword'
-        }
-      )
-    end
-
-    it 'includes the pgbouncer recipe' do
-      expect(chef_run).to include_recipe('pgbouncer::enable')
-    end
-
-    it 'includes the postgresql user recipe' do
-      expect(chef_run).to include_recipe('postgresql::user')
-    end
-
-    it_behaves_like 'enabled runit service', 'pgbouncer', 'root', 'root'
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/pgbouncer/env').with_variables(default_vars)
-    end
-
-    it 'creates the appropriate directories' do
-      expect(chef_run).to create_directory('/var/log/gitlab/pgbouncer')
-      expect(chef_run).to create_directory('/var/opt/gitlab/pgbouncer')
-    end
-
-    it 'installs pgbouncer.ini with default values' do
-      # Default values are pulled from:
-      # https://github.com/pgbouncer/pgbouncer/blob/6ef66f0139b9c8a5c0747f2a6157d008b87bf0c5/etc/pgbouncer.ini
-      expect(chef_run).to render_file(pgbouncer_ini).with_content { |content|
-        expect(content).to match(/^listen_addr = 0\.0\.0\.0$/)
-        expect(content).to match(/^listen_port = 6432$/)
-        expect(content).to match(/^pool_mode = transaction$/)
-        expect(content).to match(/^server_reset_query = DISCARD ALL$/)
-        expect(content).to match(/^application_name_add_host = 1$/)
-        expect(content).to match(/^max_client_conn = 2048$/)
-        expect(content).to match(/^default_pool_size = 100$/)
-        expect(content).to match(/^min_pool_size = 0$/)
-        expect(content).to match(/^reserve_pool_size = 5$/)
-        expect(content).to match(/^reserve_pool_timeout = 5.0$/)
-        expect(content).to match(/^server_round_robin = 0$/)
-        expect(content).to match(/^auth_type = md5$/)
-        expect(content).to match(/^log_connections = 0/)
-        expect(content).to match(/^server_idle_timeout = 30.0$/)
-        expect(content).to match(/^dns_max_ttl = 15.0$/)
-        expect(content).to match(/^dns_zone_check_period = 0$/)
-        expect(content).to match(/^dns_nxdomain_ttl = 15.0$/)
-        expect(content).to match(%r{^auth_file = /var/opt/gitlab/pgbouncer/pg_auth$})
-        expect(content).to match(/^admin_users = gitlab-psql, postgres, pgbouncer$/)
-        expect(content).to match(/^stats_users = gitlab-psql, postgres, pgbouncer$/)
-        expect(content).to match(/^ignore_startup_parameters = extra_float_digits$/)
-        expect(content).to match(%r{^unix_socket_dir = /var/opt/gitlab/pgbouncer$})
-        expect(content).to match(%r{^%include /var/opt/gitlab/pgbouncer/databases.ini})
-        expect(content).to match(/^unix_socket_mode = 0777$/)
-        expect(content).to match(/^client_tls_sslmode = disable$/)
-        expect(content).to match(/^client_tls_protocols = all$/)
-        expect(content).to match(/^client_tls_dheparams = auto$/)
-        expect(content).to match(/^client_tls_ecdhcurve = auto$/)
-        expect(content).to match(/^server_tls_sslmode = disable$/)
-        expect(content).to match(/^server_tls_protocols = all$/)
-        expect(content).to match(/^server_tls_ciphers = fast$/)
-        expect(content).to match(/^server_reset_query_always = 0$/)
-        expect(content).to match(/^server_check_query = select 1$/)
-        expect(content).to match(/^server_check_delay = 30$/)
-        expect(content).to match(/^syslog = 0$/)
-        expect(content).to match(/^syslog_facility = daemon$/)
-        expect(content).to match(/^syslog_ident = pgbouncer$/)
-        expect(content).to match(/^log_disconnections = 1$/)
-        expect(content).to match(/^log_pooler_errors = 1$/)
-        expect(content).to match(/^stats_period = 60$/)
-        expect(content).to match(/^verbose = 0$/)
-        expect(content).to match(/^server_lifetime = 3600$/)
-        expect(content).to match(/^server_connect_timeout = 15$/)
-        expect(content).to match(/^server_login_retry = 15$/)
-        expect(content).to match(/^query_timeout = 0$/)
-        expect(content).to match(/^query_wait_timeout = 120$/)
-        expect(content).to match(/^client_idle_timeout = 0$/)
-        expect(content).to match(/^client_login_timeout = 60$/)
-        expect(content).to match(/^autodb_idle_timeout = 3600$/)
-        expect(content).to match(/^suspend_timeout = 10$/)
-        expect(content).to match(/^idle_transaction_timeout = 0$/)
-        expect(content).to match(/^pkt_buf = 4096$/)
-        expect(content).to match(/^listen_backlog = 128$/)
-        expect(content).to match(/^sbuf_loopcnt = 5$/)
-        expect(content).to match(/^max_packet_size = 2147483647$/)
-        expect(content).to match(/^tcp_defer_accept = 0$/)
-        expect(content).to match(/^tcp_socket_buffer = 0$/)
-        expect(content).to match(/^tcp_keepalive = 1$/)
-        expect(content).to match(/^tcp_keepcnt = 0$/)
-        expect(content).to match(/^tcp_keepidle = 0$/)
-        expect(content).to match(/^tcp_keepintvl = 0$/)
-        expect(content).to match(/^disable_pqexec = 0$/)
-        expect(content).not_to match(/^logfile =/)
-        expect(content).not_to match(/^pidfile =/)
-        expect(content).not_to match(%r{^unix_socket_group =})
-        expect(content).not_to match(%r{^client_tls_ca_file =})
-        expect(content).not_to match(%r{^client_tls_cert_file =})
-        expect(content).not_to match(%r{^server_tls_ca_file =})
-        expect(content).not_to match(%r{^server_tls_key_file =})
-        expect(content).not_to match(%r{^server_tls_cert_file =})
-        expect(content).not_to match(%r{^max_db_connections =})
-        expect(content).not_to match(%r{^max_user_connections =})
-      }
-    end
-
-    context 'pgbouncer.ini template changes' do
-      let(:template) { chef_run.template(pgbouncer_ini) }
-
-      it 'stores the socket directory in a different location when set' do
-        stub_gitlab_rb(
-          pgbouncer: {
-            enable: true,
-            unix_socket_dir: '/fake/dir',
-            unix_socket_group: 'fakegroup',
-            client_tls_ca_file: '/fakecafile',
-            client_tls_cert_file: '/fakecertfile',
-            server_tls_ca_file: '/fakeservercafile',
-            server_tls_key_file: '/fakeserverkeyfile',
-            server_tls_cert_file: '/fakeservercertfile',
-            max_db_connections: 99999,
-            max_user_connections: 88888
-          }
-        )
-        expect(chef_run).to render_file(pgbouncer_ini).with_content { |content|
-          expect(content).to match(%r{^unix_socket_dir = /fake/dir$})
-          expect(content).to match(%r{^unix_socket_group = fakegroup$})
-          expect(content).to match(%r{^client_tls_ca_file = /fakecafile$})
-          expect(content).to match(%r{^client_tls_cert_file = /fakecertfile$})
-          expect(content).to match(%r{^server_tls_ca_file = /fakeservercafile$})
-          expect(content).to match(%r{^server_tls_key_file = /fakeserverkeyfile$})
-          expect(content).to match(%r{^server_tls_cert_file = /fakeservercertfile$})
-          expect(content).to match(%r{^max_db_connections = 99999$})
-          expect(content).to match(%r{^max_user_connections = 88888$})
-        }
-      end
-
-      it 'reloads pgbouncer and starts pgbouncer if it is not running' do
-        allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).and_call_original
-        allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).with('pgbouncer').and_return(true)
-        expect(template).to notify('execute[reload pgbouncer]').to(:run).immediately
-      end
-    end
-
-    context 'databases.json' do
-      it 'creates databases.json' do
-        expect(chef_run).to create_file(databases_json)
-          .with_content("{\"gitlabhq_production\":{\"host\":\"1.2.3.4\"}}")
-          .with(user: 'root', group: 'gitlab-psql')
-      end
-
-      it 'notifies pgb-notify to generate databases.ini' do
-        json_resource = chef_run.file(databases_json)
-        expect(json_resource).to notify('execute[generate databases.ini]').to(:run).immediately
-      end
-
-      it 'does not run pgb-notify when databases.ini exists' do
-        allow(File).to receive(:exist?).and_call_original
-        allow(File).to receive(:exist?).with('/var/opt/gitlab/pgbouncer/databases.ini').and_return(true)
-        expect(chef_run).not_to run_execute('generate databases.ini')
-      end
-
-      it 'stores in a different location when attribute is set' do
-        stub_gitlab_rb(
-          pgbouncer: {
-            enable: true,
-            databases_json: '/fakepath/fakedatabases.json'
-          }
-        )
-        expect(chef_run).to create_file('databases.json')
-          .with(path: '/fakepath/fakedatabases.json')
-      end
-
-      it 'changes the user when the attribute is changed' do
-        stub_gitlab_rb(
-          pgbouncer: {
-            enable: true,
-            databases_ini_user: 'fakeuser'
-          }
-        )
-        expect(chef_run).to create_file('databases.json')
-          .with(user: 'fakeuser', group: 'gitlab-psql')
-      end
-    end
-  end
-
-  context 'authentication' do
-    let(:pg_auth) { '/var/opt/gitlab/pgbouncer/pg_auth' }
-
-    it 'sets up auth_hba when attributes are set' do
-      stub_gitlab_rb(
-        {
-          pgbouncer: {
-            enable: true,
-            auth_hba_file: '/fake/hba_file',
-            auth_query: 'SELECT * FROM FAKETABLE'
-          }
-        }
-      )
-      expect(chef_run).to render_file(pgbouncer_ini).with_content { |content|
-        expect(content).to match(%r{^auth_hba_file = /fake/hba_file$})
-        expect(content).to match(/^auth_query = SELECT \* FROM FAKETABLE$/)
-      }
-    end
-
-    it 'does not create the user file by default' do
-      expect(chef_run).not_to render_file(pg_auth)
-    end
-
-    it 'creates the user file when the attributes are set' do
-      stub_gitlab_rb(
-        {
-          pgbouncer: {
-            enable: true,
-            databases: {
-              gitlabhq_production: {
-                password: 'fakemd5password',
-                user: 'fakeuser',
-                host: '127.0.0.1',
-                port: 5432
-              }
-            }
-          }
-        }
-      )
-      expect(chef_run).to render_file(pg_auth)
-        .with_content(%r{^"fakeuser" "md5fakemd5password"$})
-    end
-
-    it 'creates arbitrary user' do
-      stub_gitlab_rb(
-        {
-          pgbouncer: {
-            enable: true,
-            users: {
-              'fakeuser': {
-                'password': 'fakehash'
-              }
-            }
-          }
-        }
-      )
-      expect(chef_run).to render_file(pg_auth)
-        .with_content(%r{^"fakeuser" "md5fakehash"})
-    end
-
-    it 'supports SCRAM secrets' do
-      stub_gitlab_rb(
-        pgbouncer: {
-          enable: true,
-          auth_type: 'scram-sha-256',
-          users: {
-            'fakeuser': {
-              'password': 'REALLYFAKEHASH'
-            }
-          }
-        }
-      )
-      expect(chef_run).to render_file(pg_auth)
-        .with_content(%r{^"fakeuser" "SCRAM-SHA-256\$REALLYFAKEHASH"})
-    end
-
-    it 'supports a default auth type' do
-      stub_gitlab_rb(
-        pgbouncer: {
-          enable: true,
-          auth_type: 'scram-sha-256',
-          users: {
-            'firstfakeuser': {
-              'password': 'AREALLYFAKEHASH'
-            },
-            'secondfakeuser': {
-              'password': 'ANOTHERREALLYFAKEHASH'
-            }
-          },
-          databases: {
-            fakedb: {
-              user: 'databasefakeuser',
-              password: 'DATABASEHASH'
-            }
-          }
-        }
-      )
-      expect(chef_run).to render_file(pg_auth).with_content { |content|
-        expect(content).to match(%r{^"firstfakeuser" "SCRAM-SHA-256\$AREALLYFAKEHASH"})
-        expect(content).to match(%r{^"secondfakeuser" "SCRAM-SHA-256\$ANOTHERREALLYFAKEHASH"})
-        expect(content).to match(%r{^"databasefakeuser" "SCRAM-SHA-256\$DATABASEHASH"})
-      }
-    end
-
-    it 'supports per user auth types' do
-      stub_gitlab_rb(
-        pgbouncer: {
-          enable: true,
-          users: {
-            'firstfakeuser': {
-              'password': 'AREALLYFAKEHASH'
-            },
-            'secondfakeuser': {
-              'password': 'ANOTHERREALLYFAKEHASH',
-              'auth_type': 'scram-sha-256'
-            }
-          },
-          databases: {
-            fakedb: {
-              user: 'databasefakeuser',
-              auth_type: 'plain',
-              password: 'DATABASEHASH'
-            }
-          }
-        }
-      )
-      expect(chef_run).to render_file(pg_auth).with_content { |content|
-        expect(content).to match(%r{^"firstfakeuser" "md5AREALLYFAKEHASH"})
-        expect(content).to match(%r{^"secondfakeuser" "SCRAM-SHA-256\$ANOTHERREALLYFAKEHASH"})
-        expect(content).to match(%r{^"databasefakeuser" "DATABASEHASH"})
-      }
-    end
-
-    context 'when disabled by default' do
-      it_behaves_like 'disabled runit service', 'pgbouncer'
-
-      it 'includes the pgbouncer_disable recipe' do
-        expect(chef_run).to include_recipe('pgbouncer::disable')
-      end
-    end
-  end
-end
-
-RSpec.describe 'gitlab-ee::default' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    stub_gitlab_rb(
-      {
-        pgbouncer: {
-          db_user_password: 'fakeuserpassword'
-        },
-        postgresql: {
-          pgbouncer_user: 'fakeuser',
-          pgbouncer_user_password: 'fakeuserpassword'
-        }
-      }
-    )
-  end
-
-  it 'should create the pgbouncer user on the database' do
-    expect(chef_run).to include_recipe('pgbouncer::user')
-    expect(chef_run).to create_pgbouncer_user('rails').with(
-      password: 'fakeuserpassword'
-    )
-  end
-end
diff --git a/spec/chef/pgbouncer/recipes/pgbouncer_user_spec.rb b/spec/chef/pgbouncer/recipes/pgbouncer_user_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3BnYm91bmNlci9yZWNpcGVzL3BnYm91bmNlcl91c2VyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/pgbouncer/recipes/pgbouncer_user_spec.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-#
-# Copyright:: Copyright (c) 2017 GitLab Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef_helper'
-
-RSpec.describe 'gitlab-ee::pgbouncer_user' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'create the rails pgbouncer user' do
-    before do
-      allow(Gitlab).to receive(:[]).and_call_original
-      stub_gitlab_rb(
-        {
-          postgresql: {
-            enable: true,
-            pgbouncer_user: 'pgbouncer-rails',
-            pgbouncer_user_password: 'fakepassword'
-          }
-        }
-      )
-    end
-
-    it 'should call pgbouncer_user with the correct values for rails' do
-      expect(chef_run).to create_pgbouncer_user('rails').with(
-        database: 'gitlabhq_production',
-        password: 'fakepassword',
-        user: 'pgbouncer-rails',
-        add_auth_function: true
-      )
-      expect(chef_run).not_to create_pgbouncer_user('geo')
-    end
-  end
-
-  context 'auth_query is not the default value' do
-    before do
-      stub_gitlab_rb(
-        {
-          pgbouncer: {
-            auth_query: 'SELECT * FROM FAKETABLE'
-          },
-          postgresql: {
-            enable: true,
-            pgbouncer_user: 'pgbouncer-rails',
-            pgbouncer_user_password: 'fakepassword'
-          }
-        }
-      )
-    end
-
-    it 'should not create the pg_shadow_lookup function' do
-      expect(chef_run).to create_pgbouncer_user('rails').with(
-        add_auth_function: false
-      )
-    end
-  end
-
-  context 'no pgbouncer user' do
-    before do
-      stub_gitlab_rb(
-        {
-          postgresql: {
-            enable: true,
-          },
-          geo_postgresql: {
-            enable: true,
-          }
-        }
-      )
-    end
-
-    it 'should not create the pgbouncer user' do
-      expect(chef_run).not_to create_pgbouncer_user('rails')
-      expect(chef_run).not_to create_pgbouncer_user('geo')
-    end
-  end
-
-  context 'create the geo pgbouncer user' do
-    before do
-      stub_gitlab_rb(
-        {
-          geo_postgresql: {
-            enable: true,
-            pgbouncer_user: 'pgbouncer-geo',
-            pgbouncer_user_password: 'fakepassword'
-          }
-        }
-      )
-      allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
-      allow_any_instance_of(PgHelper).to receive(:user_exists?).with('pgbouncer-geo').and_return(false)
-    end
-
-    it 'should call pgbouncer_user with the correct values for geo' do
-      expect(chef_run).to create_pgbouncer_user('geo').with(
-        database: 'gitlabhq_geo_production',
-        password: 'fakepassword',
-        user: 'pgbouncer-geo',
-        add_auth_function: true
-      )
-      expect(chef_run).not_to create_pgbouncer_user('rails')
-    end
-  end
-end
diff --git a/spec/chef/postgresql/recipes/sysctl_spec.rb b/spec/chef/postgresql/recipes/sysctl_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3Bvc3RncmVzcWwvcmVjaXBlcy9zeXNjdGxfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/postgresql/recipes/sysctl_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'postgresql::user' do
-  cached(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::config', 'postgresql::sysctl') }
-
-  it 'creates sysctl files' do
-    expect(chef_run).to create_gitlab_sysctl('kernel.shmmax').with_value(17179869184)
-    expect(chef_run).to create_gitlab_sysctl('kernel.shmall').with_value(4194304)
-  end
-end
diff --git a/spec/chef/postgresql/recipes/user_spec.rb b/spec/chef/postgresql/recipes/user_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3Bvc3RncmVzcWwvcmVjaXBlcy91c2VyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/postgresql/recipes/user_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'postgresql::user' do
-  cached(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::config', 'postgresql::user') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  it 'includes postgresql::directory_locations recipe' do
-    expect(chef_run).to include_recipe('postgresql::directory_locations')
-  end
-
-  context 'with default attributes' do
-    it 'creates postgresql user and groups' do
-      account_params = {
-        username: 'gitlab-psql',
-        groupname: 'gitlab-psql',
-        home: '/var/opt/gitlab/postgresql',
-        shell: '/bin/sh'
-      }
-
-      expect(chef_run).to create_account('Postgresql user and group').with(account_params)
-    end
-
-    it 'creates postgresql base directory' do
-      directory_params = {
-        owner: 'gitlab-psql',
-        mode: '0755',
-        recursive: true
-      }
-      expect(chef_run).to create_directory('/var/opt/gitlab/postgresql').with(directory_params)
-    end
-
-    it 'creates a .profile with bundled PostgreSQL PATH' do
-      file_params = {
-        owner: 'gitlab-psql',
-        content: %r{PATH=/opt/gitlab/embedded/bin:/opt/gitlab/bin:\$PATH}
-      }
-
-      expect(chef_run).to create_file('/var/opt/gitlab/postgresql/.profile').with(file_params)
-    end
-  end
-
-  context 'when using custom values for directories' do
-    cached(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::config', 'postgresql::user') }
-
-    before do
-      stub_gitlab_rb(
-        postgresql: {
-          home: '/mypghomedir'
-        })
-    end
-
-    it 'creates postgresql user and groups' do
-      account_params = {
-        username: 'gitlab-psql',
-        groupname: 'gitlab-psql',
-        home: '/mypghomedir',
-        shell: '/bin/sh'
-      }
-
-      expect(chef_run).to create_account('Postgresql user and group').with(account_params)
-    end
-
-    it 'creates postgresql base directory' do
-      directory_params = {
-        owner: 'gitlab-psql',
-        mode: '0755',
-        recursive: true
-      }
-      expect(chef_run).to create_directory('/mypghomedir').with(directory_params)
-    end
-
-    it 'creates necessary directories' do
-      expect(chef_run).to create_directory('/mypghomedir').with(owner: 'gitlab-psql', mode: '0755', recursive: true)
-    end
-  end
-end
diff --git a/spec/chef/postgresql/resources/config_spec.rb b/spec/chef/postgresql/resources/config_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3Bvc3RncmVzcWwvcmVzb3VyY2VzL2NvbmZpZ19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/postgresql/resources/config_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'postgresql_config' do
-  let(:runner) do
-    ChefSpec::SoloRunner.new(step_into: %w(postgresql_config)) do |node|
-      node.normal['postgresql']['dir'] = '/fakedir'
-      node.normal['patroni']['dir'] = '/patronifakedir'
-    end
-  end
-
-  let(:chef_run) { runner.converge('gitlab::config', 'test_postgresql::postgresql_config') }
-
-  before do
-    allow_any_instance_of(PgHelper).to receive(:postgresql_user).and_return('fakeuser')
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'create' do
-    context 'default settings' do
-      it 'creates the files' do
-        expect(chef_run).to render_file('/fakedir/data/postgresql.conf')
-        expect(chef_run).to render_file('/fakedir/data/runtime.conf')
-        expect(chef_run).to render_file('/fakedir/data/pg_hba.conf')
-        expect(chef_run).to render_file('/fakedir/data/pg_ident.conf')
-      end
-    end
-
-    context 'when patroni is enabled' do
-      before do
-        stub_gitlab_rb(
-          patroni: {
-            enable: true
-          }
-        )
-      end
-
-      it 'creates the files' do
-        expect(chef_run).to render_file('/patronifakedir/data/postgresql.base.conf')
-        expect(chef_run).to render_file('/patronifakedir/data/runtime.conf')
-        expect(chef_run).to render_file('/patronifakedir/data/pg_hba.conf')
-        expect(chef_run).to render_file('/patronifakedir/data/pg_ident.conf')
-      end
-    end
-  end
-end
diff --git a/spec/chef/postgresql/resources/database_spec.rb b/spec/chef/postgresql/resources/database_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3Bvc3RncmVzcWwvcmVzb3VyY2VzL2RhdGFiYXNlX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/postgresql/resources/database_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'postgresql_database' do
-  let(:runner) do
-    ChefSpec::SoloRunner.new(step_into: %w(postgresql_database)) do |node|
-      # unix_socket_directory is normally conditionally set in postgresql::enable
-      # which is not executed as part of this spec
-      node.normal['postgresql']['unix_socket_directory'] = '/var/opt/gitlab/postgresql'
-    end
-  end
-
-  context 'create' do
-    let(:chef_run) { runner.converge('test_postgresql::postgresql_database_create') }
-
-    context 'server is running' do
-      before do
-        allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
-      end
-
-      context 'database does not already exist' do
-        before do
-          allow_any_instance_of(PgHelper).to receive(:database_exists?).and_return(false)
-        end
-
-        it 'creates a database' do
-          expect(chef_run).to run_execute('create database example').with(
-            command: %(/opt/gitlab/embedded/bin/createdb --port 5432 -h /var/opt/gitlab/postgresql -O gitlab example),
-            user: 'gitlab-psql'
-          )
-        end
-
-        context 'with non-default options' do
-          let(:chef_run) { runner.converge('test_postgresql::postgresql_database_create_with_options') }
-
-          it 'creates with the correct options set' do
-            expect(chef_run).to run_execute('create database fakedb').with(
-              command: %(/opt/gitlab/embedded/bin/createdb --port 9999 -h /fake/dir -O fakeuser fakedb)
-            )
-          end
-        end
-      end
-
-      context 'database already exists' do
-        before do
-          allow_any_instance_of(PgHelper).to receive(:database_exists?).with('example').and_return(true)
-        end
-
-        it 'does nothing' do
-          expect(chef_run).not_to run_execute('create database example')
-        end
-      end
-    end
-
-    context 'server is not running' do
-      before do
-        allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(false)
-      end
-
-      it 'does nothing' do
-        expect(chef_run).not_to run_execute('create database example')
-      end
-    end
-  end
-end
diff --git a/spec/chef/postgresql/resources/extension_spec.rb b/spec/chef/postgresql/resources/extension_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3Bvc3RncmVzcWwvcmVzb3VyY2VzL2V4dGVuc2lvbl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/postgresql/resources/extension_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'postgresql_query' do
-  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(postgresql_extension)) }
-
-  context 'run' do
-    let(:chef_run) { runner.converge('test_postgresql::postgresql_extension_enable') }
-
-    context 'when extension can be enabled' do
-      before do
-        allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('foobar', 'lorem').and_return(true)
-      end
-
-      it 'runs the query to enable extension' do
-        expect(chef_run).to run_postgresql_query('enable foobar extension').with(
-          query: %(CREATE EXTENSION IF NOT EXISTS foobar),
-          db_name: 'lorem'
-        )
-      end
-    end
-
-    context 'when extension can not be enabled' do
-      before do
-        allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('foobar', 'lorem').and_return(false)
-      end
-
-      it 'does not run the query to enable extension' do
-        expect(chef_run).not_to run_postgresql_query('enable foobar extension')
-      end
-    end
-  end
-end
diff --git a/spec/chef/postgresql/resources/query_spec.rb b/spec/chef/postgresql/resources/query_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3Bvc3RncmVzcWwvcmVzb3VyY2VzL3F1ZXJ5X3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/postgresql/resources/query_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'postgresql_query' do
-  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(postgresql_query)) }
-
-  before do
-    allow_any_instance_of(PgHelper).to receive(:is_ready?).and_return(true)
-    allow_any_instance_of(GeoPgHelper).to receive(:is_ready?).and_return(true)
-  end
-
-  context 'run' do
-    let(:chef_run) { runner.converge('test_postgresql::postgresql_query_run') }
-
-    context 'when service is up' do
-      before do
-        allow_any_instance_of(PgHelper).to receive(:is_offline_or_readonly?).and_return(false)
-        allow_any_instance_of(GeoPgHelper).to receive(:is_offline_or_readonly?).and_return(false)
-      end
-
-      it 'runs the query with correct service' do
-        expect(chef_run).to run_execute('create schema (postgresql)').with(
-          command: %(/opt/gitlab/bin/gitlab-psql -d omnibus_test -c "CREATE SCHEMA example AUTHORIZATION foobar;")
-        )
-
-        expect(chef_run).to run_execute('create schema (geo-postgresql)').with(
-          command: %(/opt/gitlab/bin/gitlab-geo-psql -d omnibus_test -c "CREATE SCHEMA example AUTHORIZATION foobar;")
-        )
-      end
-    end
-
-    context 'when service is offline' do
-      before do
-        allow_any_instance_of(PgHelper).to receive(:is_offline_or_readonly?).and_return(true)
-        allow_any_instance_of(GeoPgHelper).to receive(:is_offline_or_readonly?).and_return(true)
-      end
-
-      it 'does not run the query' do
-        expect(chef_run).not_to run_execute('create example schema (postgresql)')
-        expect(chef_run).not_to run_execute('create example schema (geo-postgresql)')
-      end
-    end
-  end
-end
diff --git a/spec/chef/postgresql/resources/schema_spec.rb b/spec/chef/postgresql/resources/schema_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3Bvc3RncmVzcWwvcmVzb3VyY2VzL3NjaGVtYV9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/postgresql/resources/schema_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'postgresql_schema' do
-  let(:runner) { ChefSpec::SoloRunner.new(step_into: %w(postgresql_schema)) }
-
-  context 'create' do
-    let(:chef_run) { runner.converge('test_postgresql::postgresql_schema_create') }
-
-    context 'server is running' do
-      before do
-        allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
-        allow_any_instance_of(PgHelper).to receive(:is_standby?).and_return(false)
-      end
-
-      it 'creates schema' do
-        expect(chef_run).to run_postgresql_query('create example schema on omnibus_gitlab_test').with(
-          db_name: 'omnibus_gitlab_test'
-        )
-      end
-
-      context 'schema already exists' do
-        before do
-          allow_any_instance_of(PgHelper).to receive(:schema_exists?).and_return(true)
-        end
-
-        it 'does not try to create schema again' do
-          expect(chef_run).not_to run_postgresql_query('create example schema on omnibus_gitlab_test')
-        end
-
-        context 'when schema owner is different' do
-          before do
-            allow_any_instance_of(PgHelper).to receive(:schema_owner?).and_return(false)
-          end
-
-          it 'tries to update schema' do
-            expect(chef_run).to run_postgresql_query('modify example schema owner on omnibus_gitlab_test')
-          end
-        end
-
-        context 'when schema owner is the same' do
-          before do
-            allow_any_instance_of(PgHelper).to receive(:schema_owner?).and_return(true)
-          end
-
-          it 'tries to update schema' do
-            expect(chef_run).not_to run_postgresql_query('modify example schema owner on omnibus_gitlab_test')
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/postgresql/resources/user_spec.rb b/spec/chef/postgresql/resources/user_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3Bvc3RncmVzcWwvcmVzb3VyY2VzL3VzZXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/postgresql/resources/user_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'postgresql_user' do
-  before do
-    allow_any_instance_of(PgHelper).to receive(:is_offline_or_readonly?).and_return(false)
-    allow_any_instance_of(PgHelper).to receive(:user_exists?).and_return(false, true)
-    allow_any_instance_of(PgHelper).to receive(:user_password_match?).and_return(false)
-    allow_any_instance_of(PgHelper).to receive(:user_options_set?).and_return(false)
-    allow_any_instance_of(PgHelper).to receive(:is_ready?).and_return(true)
-    allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
-  end
-
-  let(:runner) { ChefSpec::SoloRunner.new(step_into: ['postgresql_user']) }
-
-  context 'create' do
-    let(:chef_run) { runner.converge('test_postgresql::postgresql_user_create') }
-
-    it 'creates a user' do
-      expect(chef_run).to run_execute('create example postgresql user')
-    end
-  end
-
-  context 'password' do
-    context 'not specified' do
-      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_password_unspecified') }
-
-      it 'does not set the password of the no_password user' do
-        expect(chef_run).not_to run_execute('set password for no_password postgresql user')
-      end
-    end
-
-    context 'nil' do
-      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_password_nil') }
-
-      it 'does set the password of the nil_password user' do
-        expect(chef_run).to run_execute('set password for nil_password postgresql user')
-          .with(command: /PASSWORD NULL/)
-      end
-    end
-
-    context 'md5' do
-      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_password_md5') }
-
-      it 'does set the password of the md5_password user' do
-        expect(chef_run).to run_execute('set password for md5_password postgresql user')
-          .with(command: /PASSWORD 'e99b79fbdf9b997e6918df2385e60f5c'/)
-      end
-    end
-
-    context 'empty' do
-      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_password_empty') }
-
-      it 'does set the password of the empty_password user' do
-        expect(chef_run).to run_execute('set password for empty_password postgresql user')
-          .with(command: /PASSWORD ''/)
-      end
-    end
-  end
-
-  context 'options' do
-    context 'unspecified' do
-      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_options_unspecified') }
-
-      it 'does not set options' do
-        expect(chef_run).not_to run_execute('set options for example postgresql user')
-      end
-    end
-
-    context 'SUPERUSER' do
-      let(:chef_run) { runner.converge('test_postgresql::postgresql_user_options_superuser') }
-
-      it 'does set SUPERUSER' do
-        expect(chef_run).to run_execute('set options for example postgresql user')
-          .with(command: /\bSUPERUSER\b/)
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/add_trusted_certs_spec.rb b/spec/chef/recipes/add_trusted_certs_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvYWRkX3RydXN0ZWRfY2VydHNfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/add_trusted_certs_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'add_trusted_certs recipe' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-  let(:cert_helper) { CertificateHelper.new('/etc/gitlab/trusted-certs', '/opt/gitlab/embedded/ssl/certs', '/var/opt/gitlab') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  it 'creates the certificate directories' do
-    expect(chef_run).to create_directory('/opt/gitlab/embedded/ssl/certs').with(mode: '0755')
-    expect(chef_run).to create_directory('/etc/gitlab/trusted-certs').with(mode: '0755')
-  end
-
-  it 'creates a readme file for the managed certificate directory' do
-    expect(chef_run).to create_file('/opt/gitlab/embedded/ssl/certs/README').with(
-      mode: '0644',
-      content: /This directory is managed by omnibus\-gitlab/
-    )
-  end
-
-  context 'when new trusted certificates have been added' do
-    before do
-      allow(cert_helper).to receive(:new_certificate_added?).and_return(true)
-      allow(CertificateHelper).to receive(:new).and_return(cert_helper)
-    end
-
-    it 'attempts to link trusted certificates into our certificate chain' do
-      expect(chef_run).to run_ruby_block('Move existing certs and link to /opt/gitlab/embedded/ssl/certs')
-    end
-  end
-
-  context 'when there have been no changes to the trusted certificates directory' do
-    before do
-      allow(cert_helper).to receive(:new_certificate_added?).and_return(false)
-      allow(CertificateHelper).to receive(:new).and_return(cert_helper)
-    end
-
-    it 'does not attempt to link trusted certificates into our certificate chain' do
-      expect(chef_run).not_to run_ruby_block('Move existing certs and link to /opt/gitlab/embedded/ssl/certs')
-    end
-  end
-end
diff --git a/spec/chef/recipes/config_spec.rb b/spec/chef/recipes/config_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvY29uZmlnX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/config_spec.rb
+++ /dev/null
@@ -1,226 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::config' do
-  cached(:chef_run) { converge_config }
-  let(:node) { chef_run.node }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  shared_examples 'regular services are disabled' do
-    it 'disables regular services' do
-      expect(node['gitlab']['puma']['enable']).to eq false
-      expect(node['gitlab']['sidekiq']['enable']).to eq false
-      expect(node['gitlab']['gitlab-workhorse']['enable']).to eq false
-      expect(node['gitaly']['enable']).to eq false
-      expect(node['gitlab']['nginx']['enable']).to eq false
-      expect(node['postgresql']['enable']).to eq false
-      expect(node['gitlab']['mailroom']['enable']).to eq false
-      expect(node['monitoring']['gitlab-exporter']['enable']).to eq false
-      expect(node['monitoring']['postgres-exporter']['enable']).to eq false
-      expect(node['monitoring']['prometheus']['enable']).to eq false
-    end
-  end
-
-  it 'ignores unsupported top level variables' do
-    allow(IO).to receive(:read).and_call_original
-    allow(IO).to receive(:read).with('config-test.rb').and_return('abc="top-level"')
-    Gitlab.from_file('config-test.rb')
-
-    expect(node['gitlab']['abc']).to be_nil
-  end
-
-  it 'errors on unsupported top level methods' do
-    allow(IO).to receive(:read).and_call_original
-    allow(IO).to receive(:read).with('config-test.rb').and_return('abc "top-level"')
-    expect do
-      Gitlab.from_file('config-test.rb')
-    end.to raise_error(Mixlib::Config::UnknownConfigOptionError)
-  end
-
-  it 'errors on unsupported nested variables' do
-    allow(IO).to receive(:read).and_call_original
-    allow(IO).to receive(:read).with('config-test.rb').and_return('abc["def"]["hij"] = "top-level"')
-    expect do
-      Gitlab.from_file('config-test.rb')
-    end.to raise_error(Mixlib::Config::UnknownConfigOptionError)
-  end
-
-  context 'when gitlab-rails is disabled' do
-    cached(:chef_run) { converge_config }
-    before do
-      stub_gitlab_rb(
-        gitlab_rails: {
-          enable: false
-        }
-      )
-    end
-
-    it 'disables Gitlab components' do
-      expect(node['gitlab']['puma']['enable']).to eq false
-      expect(node['gitlab']['sidekiq']['enable']).to eq false
-      expect(node['gitlab']['gitlab-workhorse']['enable']).to eq false
-      expect(node['monitoring']['gitlab-exporter']['enable']).to eq false
-    end
-
-    it 'still leaves other default service enabled' do
-      expect(node['gitlab']['nginx']['enable']).to eq true
-      expect(node['postgresql']['enable']).to eq true
-      expect(node['redis']['enable']).to eq true
-      expect(node['monitoring']['prometheus']['enable']).to eq true
-      expect(node['monitoring']['alertmanager']['enable']).to eq true
-      expect(node['monitoring']['node-exporter']['enable']).to eq true
-      expect(node['monitoring']['redis-exporter']['enable']).to eq true
-      expect(node['logrotate']['enable']).to eq true
-      expect(node['monitoring']['postgres-exporter']['enable']).to eq true
-    end
-  end
-
-  context 'with roles' do
-    context 'when redis_sentinel_role is enabled' do
-      cached(:chef_run) { converge_config(is_ee: true) }
-      before do
-        stub_gitlab_rb(
-          redis_sentinel_role: {
-            enable: true
-          },
-          redis: {
-            master_password: 'PASSWORD',
-            master_ip: '10.0.0.0'
-          }
-        )
-      end
-
-      it_behaves_like 'regular services are disabled'
-
-      it 'only sentinel is enabled' do
-        expect(node['gitlab']['sentinel']['enable']).to eq true
-        expect(node['redis']['enable']).to eq false
-        expect(node['monitoring']['redis-exporter']['enable']).to eq false
-        expect(node['monitoring']['node-exporter']['enable']).to eq true
-        expect(node['logrotate']['enable']).to eq true
-      end
-
-      context 'when redis_sentinel_role is enabled with redis_master_role' do
-        cached(:chef_run) { converge_config(is_ee: true) }
-        before do
-          stub_gitlab_rb(
-            redis_sentinel_role: {
-              enable: true
-            },
-            redis_master_role: {
-              enable: true
-            }
-          )
-        end
-
-        it_behaves_like 'regular services are disabled'
-
-        it 'redis and sentinel are enabled' do
-          expect(node['gitlab']['sentinel']['enable']).to eq true
-          expect(node['redis']['enable']).to eq true
-          expect(node['monitoring']['redis-exporter']['enable']).to eq true
-          expect(node['monitoring']['node-exporter']['enable']).to eq true
-          expect(node['logrotate']['enable']).to eq true
-        end
-      end
-
-      context 'when redis_sentinel_role is enabled with redis_replica_role' do
-        cached(:chef_run) { converge_config(is_ee: true) }
-        before do
-          stub_gitlab_rb(
-            redis_sentinel_role: {
-              enable: true
-            },
-            redis_replica_role: {
-              enable: true
-            },
-            redis: {
-              master_ip: '10.0.0.0',
-              master_port: 6379,
-              master_password: 'PASSWORD'
-            }
-          )
-        end
-
-        it_behaves_like 'regular services are disabled'
-
-        it 'only redis is enabled' do
-          expect(node['gitlab']['sentinel']['enable']).to eq true
-          expect(node['redis']['enable']).to eq true
-          expect(node['monitoring']['redis-exporter']['enable']).to eq true
-          expect(node['monitoring']['node-exporter']['enable']).to eq true
-          expect(node['logrotate']['enable']).to eq true
-        end
-      end
-    end
-
-    context 'when redis_master_role is enabled' do
-      cached(:chef_run) { converge_config(is_ee: true) }
-      before do
-        stub_gitlab_rb(
-          redis_master_role: {
-            enable: true
-          }
-        )
-      end
-
-      it_behaves_like 'regular services are disabled'
-
-      it 'only redis is enabled' do
-        expect(node['redis']['enable']).to eq true
-        expect(node['gitlab']['sentinel']['enable']).to eq false
-        expect(node['monitoring']['redis-exporter']['enable']).to eq true
-        expect(node['monitoring']['node-exporter']['enable']).to eq true
-        expect(node['logrotate']['enable']).to eq true
-      end
-    end
-
-    context 'when redis_replica_role is enabled' do
-      cached(:chef_run) { converge_config(is_ee: true) }
-      before do
-        stub_gitlab_rb(
-          redis_replica_role: {
-            enable: true
-          },
-          redis: {
-            master_ip: '10.0.0.0',
-            master_port: 6379,
-            master_password: 'PASSWORD'
-          }
-        )
-      end
-
-      it_behaves_like 'regular services are disabled'
-
-      it 'only redis is enabled' do
-        expect(node['redis']['enable']).to eq true
-        expect(node['gitlab']['sentinel']['enable']).to eq false
-      end
-    end
-
-    context 'when redis_master_role and redis_replica_role are enabled' do
-      cached(:chef_run) { converge_config(is_ee: true) }
-      before do
-        stub_gitlab_rb(
-          redis_master_role: {
-            enable: true
-          },
-          redis_replica_role: {
-            enable: true
-          },
-          redis: {
-            master_ip: '10.0.0.0',
-            master_port: 6379,
-            master_password: 'PASSWORD'
-          }
-        )
-      end
-
-      it 'fails with an error' do
-        expect { chef_run }.to raise_error RuntimeError
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/crond_enable_spec.rb b/spec/chef/recipes/crond_enable_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvY3JvbmRfZW5hYmxlX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/crond_enable_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'crond::enable' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('crond::enable') }
-
-  it "should create a log directory" do
-    expect(chef_run).to create_directory("/var/log/gitlab/crond").with(
-      owner: "root"
-    )
-  end
-
-  it "should create a cron.d directory" do
-    expect(chef_run).to create_directory("/var/opt/gitlab/crond").with(
-      recursive: true,
-      owner: "root"
-    )
-  end
-
-  it "should pass correct options to runit_service" do
-    expect(chef_run).to render_file("/opt/gitlab/sv/crond/run").with_content(/--include=\/var\/opt\/gitlab\/crond/)
-  end
-
-  it_behaves_like "enabled runit service", "crond", "root", "root"
-end
diff --git a/spec/chef/recipes/database_migrations_spec.rb b/spec/chef/recipes/database_migrations_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZGF0YWJhc2VfbWlncmF0aW9uc19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/database_migrations_spec.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-require 'chef_helper'
-
-# NOTE: These specs do not verify whether the code actually ran
-# Nor whether the resource inside of the recipe was notified correctly.
-# At this moment they only verify whether the expected commands are passed
-# to the resource block.
-
-RSpec.describe 'gitlab::database-migrations' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when migration should run' do
-    before do
-      stub_default_not_listening?(false)
-    end
-
-    let(:bash_block) { chef_run.bash('migrate gitlab-rails database') }
-    let(:migration_block) { chef_run.rails_migration('gitlab-rails') }
-
-    it 'runs the migrations with expected attributes' do
-      expect(chef_run).to run_rails_migration('gitlab-rails') do |resource|
-        expect(resource.rake_task).to eq('gitlab:db:configure')
-        expect(resource.logfile_prefix).to eq('gitlab-rails-db-migrate')
-        expect(resource.helper).to be_a(RailsMigrationHelper)
-      end
-    end
-
-    it 'skips outdated external databases warning by default' do
-      expect(migration_block).to notify('ruby_block[check remote PG version]').to(:run)
-      expect(chef_run.ruby_block('check remote PG version').should_skip?(:run)).to be_truthy
-    end
-
-    context 'using external db' do
-      before { stub_gitlab_rb(postgresql: { enable: false }) }
-
-      it 'warns about outdated databases' do
-        allow(GitlabRailsEnvHelper).to receive(:db_version).and_return(11)
-
-        expect(migration_block).to notify('ruby_block[check remote PG version]').to(:run)
-        expect(chef_run.ruby_block('check remote PG version').should_skip?(:run)).to be_falsey
-
-        chef_run.ruby_block('check remote PG version').block.call
-
-        expect_logged_warning(/Support for PostgreSQL 11 has been removed/)
-      end
-    end
-
-    context 'initial root password' do
-      context 'set via gitlab.rb' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              initial_root_password: 'foobar'
-            }
-          )
-        end
-
-        it 'runs DB migration with GITLAB_ROOT_PASSWORD variable set to provided value' do
-          expect(chef_run).to run_rails_migration('gitlab-rails').with(
-            environment: {
-              'GITLAB_ROOT_PASSWORD' => 'foobar',
-            }
-          )
-        end
-      end
-
-      context 'set via env variable' do
-        before do
-          allow(ENV).to receive(:[]).and_call_original
-          allow(ENV).to receive(:[]).with('GITLAB_ROOT_PASSWORD').and_return('asdf1234@')
-        end
-
-        it 'runs DB migration with GITLAB_ROOT_PASSWORD variable set to provided value' do
-          expect(chef_run).to run_rails_migration('gitlab-rails').with(
-            environment: {
-              'GITLAB_ROOT_PASSWORD' => 'asdf1234@',
-            }
-          )
-        end
-      end
-
-      context 'set via both env variable and gitlab.rb' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              initial_root_password: 'foobar'
-            }
-          )
-
-          allow(ENV).to receive(:[]).and_call_original
-          allow(ENV).to receive(:[]).with('GITLAB_ROOT_PASSWORD').and_return('asdf1234@')
-        end
-
-        it 'runs DB migration with GITLAB_ROOT_PASSWORD variable set to value provided via env variable' do
-          expect(chef_run).to run_rails_migration('gitlab-rails').with(
-            environment: {
-              'GITLAB_ROOT_PASSWORD' => 'asdf1234@',
-            }
-          )
-        end
-      end
-
-      context 'not set' do
-        before do
-          allow(SecretsHelper).to receive(:generate_base64).and_return('LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk=')
-        end
-
-        it 'generates a random root password and runs DB migration with GITLAB_ROOT_PASSWORD set to it' do
-          expect(chef_run).to run_rails_migration('gitlab-rails').with(
-            environment: {
-              'GITLAB_ROOT_PASSWORD' => 'LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk='
-            }
-          )
-        end
-      end
-    end
-
-    it 'runs with the initial_root_password and initial_shared_runners_registration_token in the environment' do
-      stub_gitlab_rb(
-        gitlab_rails: { initial_root_password: '123456789', initial_shared_runners_registration_token: '987654321' }
-      )
-
-      expect(chef_run).to run_rails_migration('gitlab-rails').with(
-        environment: {
-          'GITLAB_ROOT_PASSWORD' => '123456789',
-          'GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN' => '987654321'
-        }
-      )
-    end
-
-    context 'initial license file' do
-      before do
-        allow(SecretsHelper).to receive(:generate_base64).and_return('LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk=')
-      end
-
-      it 'detects license file from /etc/gitlab' do
-        allow(Dir).to receive(:glob).and_call_original
-        allow(Dir).to receive(:glob).with('/etc/gitlab/*.gitlab-license').and_return(%w[/etc/gitlab/company.gitlab-license /etc/gitlab/company2.gitlab-license])
-
-        expect(chef_run).to run_rails_migration('gitlab-rails').with(
-          environment: {
-            'GITLAB_ROOT_PASSWORD' => 'LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk=',
-            'GITLAB_LICENSE_FILE' => '/etc/gitlab/company.gitlab-license'
-          }
-        )
-      end
-
-      it 'license file specified in gitlab.rb gets priority' do
-        allow(Dir).to receive(:glob).and_call_original
-        allow(Dir).to receive(:glob).with('/etc/gitlab/*.gitlab-license').and_return(%w[/etc/gitlab/company.gitlab-license /etc/gitlab/company2.gitlab-license])
-
-        stub_gitlab_rb(
-          gitlab_rails: { initial_license_file: '/mnt/random.gitlab-license' }
-        )
-
-        expect(chef_run).to run_rails_migration('gitlab-rails').with(
-          environment: {
-            'GITLAB_ROOT_PASSWORD' => 'LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk=',
-            'GITLAB_LICENSE_FILE' => '/mnt/random.gitlab-license'
-          }
-        )
-      end
-
-      it 'Does not fail if no license file found in /etc/gitlab' do
-        allow(Dir).to receive(:glob).and_call_original
-        allow(Dir).to receive(:glob).with('/etc/gitlab/*.gitlab-license').and_return([])
-
-        expect(chef_run).to run_rails_migration('gitlab-rails').with(
-          environment: {
-            'GITLAB_ROOT_PASSWORD' => 'LFQLd2ayKNpthh+Ehxqy7ROxsmpzACy55EcOYoMfRlk=',
-          }
-        )
-      end
-    end
-
-    it 'should notify rails cache clear resource' do
-      expect(migration_block).to notify(
-        'execute[clear the gitlab-rails cache]')
-    end
-  end
-
-  context 'with auto_migrate off' do
-    before { stub_gitlab_rb(gitlab_rails: { auto_migrate: false }) }
-
-    it 'skips running the migrations' do
-      expect(chef_run).not_to run_rails_migration('gitlab-rails')
-    end
-  end
-end
diff --git a/spec/chef/recipes/database_reindexing_disable_spec.rb b/spec/chef/recipes/database_reindexing_disable_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZGF0YWJhc2VfcmVpbmRleGluZ19kaXNhYmxlX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/database_reindexing_disable_spec.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::database-reindexing' do
-  let(:chef_run) { converge_config('gitlab::database_reindexing_disable') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  it 'removes the database-reindexing cronjob' do
-    expect(chef_run).to delete_crond_job('database-reindexing')
-  end
-end
diff --git a/spec/chef/recipes/database_reindexing_enable_spec.rb b/spec/chef/recipes/database_reindexing_enable_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZGF0YWJhc2VfcmVpbmRleGluZ19lbmFibGVfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/database_reindexing_enable_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::database-reindexing' do
-  let(:chef_run) { converge_config('gitlab::database_reindexing_enable') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'with defaults' do
-    before do
-      stub_gitlab_rb(gitlab_rails: { database_reindexing: { enable: true } })
-    end
-
-    it 'adds a crond_job with default schedule' do
-      expect(chef_run).to create_crond_job('database-reindexing').with(
-        user: "root",
-        hour: '*',
-        minute: 0,
-        month: '*',
-        day_of_month: '*',
-        day_of_week: '0,6',
-        command: "/opt/gitlab/bin/gitlab-rake gitlab:db:reindex"
-      )
-    end
-  end
-
-  context 'with specific schedule' do
-    let(:config) do
-      {
-        enable: true,
-        hour: 10,
-        minute: 5,
-        month: 3,
-        day_of_month: 2,
-        day_of_week: 1
-      }
-    end
-
-    it 'adds a crond_job with the configured schedule' do
-      stub_gitlab_rb(gitlab_rails: { database_reindexing: config })
-
-      expect(chef_run).to create_crond_job('database-reindexing').with(
-        user: "root",
-        hour: 10,
-        minute: 5,
-        month: 3,
-        day_of_week: 1,
-        command: "/opt/gitlab/bin/gitlab-rake gitlab:db:reindex"
-      )
-    end
-  end
-end
diff --git a/spec/chef/recipes/default_spec.rb b/spec/chef/recipes/default_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZGVmYXVsdF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/default_spec.rb
+++ /dev/null
@@ -1,193 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::default' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  it 'creates the user config directory' do
-    expect(chef_run).to create_directory('/etc/gitlab').with(
-      user: 'root',
-      group: 'root',
-      mode: '0775'
-    )
-  end
-
-  it 'creates the var opt data config directory' do
-    expect(chef_run).to create_directory('Create /var/opt/gitlab').with(
-      path: '/var/opt/gitlab',
-      user: 'root',
-      group: 'root',
-      mode: '0755'
-    )
-
-    gitconfig_hash = {
-      "receive" => ["fsckObjects = true", "advertisePushOptions = true"],
-      "pack" => ["threads = 1"],
-      "repack" => ["writeBitmaps = true"],
-      "transfer" => ["hideRefs=^refs/tmp/", "hideRefs=^refs/keep-around/", "hideRefs=^refs/remotes/"],
-      "core" => [
-        'alternateRefsCommand="exit 0 #"',
-        "fsyncObjectFiles = true"
-      ],
-      "fetch" => ["writeCommitGraph = true"]
-    }
-
-    expect(chef_run).to create_template('/opt/gitlab/embedded/etc/gitconfig').with(
-      variables: { gitconfig: gitconfig_hash }
-    )
-  end
-
-  it 'creates the system gitconfig directory and file' do
-    stub_gitlab_rb(omnibus_gitconfig: { system: { receive: ["fsckObjects = true", "advertisePushOptions = true"], pack: ["threads = 2"] } })
-
-    expect(chef_run).to create_directory('/opt/gitlab/embedded/etc').with(
-      user: 'root',
-      group: 'root',
-      mode: '0755'
-    )
-
-    gitconfig_hash = {
-      "receive" => ["fsckObjects = true", "advertisePushOptions = true"],
-      "pack" => ["threads = 2"],
-      "repack" => ["writeBitmaps = true"],
-      "transfer" => ["hideRefs=^refs/tmp/", "hideRefs=^refs/keep-around/", "hideRefs=^refs/remotes/"],
-      "core" => [
-        'alternateRefsCommand="exit 0 #"',
-        "fsyncObjectFiles = true"
-      ],
-      "fetch" => ["writeCommitGraph = true"]
-    }
-
-    expect(chef_run).to create_template('/opt/gitlab/embedded/etc/gitconfig').with(
-      source: 'gitconfig-system.erb',
-      variables: { gitconfig: gitconfig_hash },
-      mode: 0755
-    )
-  end
-
-  context 'with logrotate' do
-    it 'runs logrotate directory and configuration recipe by default' do
-      expect(chef_run).to include_recipe('logrotate::folders_and_configs')
-    end
-
-    it 'runs logrotate directory and configuration recipe when logrotate is disabled' do
-      stub_gitlab_rb(logrotate: { enable: false })
-
-      expect(chef_run).to include_recipe('logrotate::folders_and_configs')
-    end
-  end
-
-  context 'when manage_etc directory management is disabled' do
-    before { stub_gitlab_rb(manage_storage_directories: { enable: true, manage_etc: false }) }
-
-    it 'does not create the user config directory' do
-      expect(chef_run).not_to create_directory('/etc/gitlab')
-    end
-  end
-
-  context 'prometheus is enabled by default' do
-    it 'includes the prometheus recipe' do
-      expect(chef_run).to include_recipe('monitoring::prometheus')
-      expect(chef_run).not_to include_recipe('monitoring::prometheus_disable')
-    end
-  end
-
-  context 'with prometheus disabled' do
-    before { stub_gitlab_rb(prometheus: { enable: false }) }
-
-    it 'includes the prometheus_disable recipe' do
-      expect(chef_run).to include_recipe('monitoring::prometheus_disable')
-      expect(chef_run).not_to include_recipe('monitoring::prometheus')
-    end
-  end
-
-  context 'with database reindexing and LetsEncrypt auto-renew disabled' do
-    it 'disables crond' do
-      expect(chef_run).to include_recipe('crond::disable')
-      expect(chef_run).not_to include_recipe('crond::enable')
-    end
-  end
-
-  context 'with database reindexing enabled' do
-    before do
-      stub_gitlab_rb(gitlab_rails: { database_reindexing: { enable: true } })
-    end
-
-    it 'enables crond' do
-      expect(chef_run).to include_recipe('crond::enable')
-      expect(chef_run).not_to include_recipe('crond::disable')
-    end
-  end
-
-  context 'with LetsEncrypt auto-renew enabled' do
-    before do
-      # Registry will be auto-enabled if LetsEncrypt is enabled
-      stub_gitlab_rb(external_url: 'http://gitlab.example.com',
-                     registry: { enable: false },
-                     letsencrypt: { enable: true, auto_renew: true })
-    end
-
-    it 'enables crond' do
-      expect(chef_run).to include_recipe('crond::enable')
-      expect(chef_run).not_to include_recipe('crond::disable')
-    end
-  end
-
-  context 'with sidekiq exporter settings' do
-    context 'when Sidekiq exporter and Sidekiq health checks port are the same' do
-      before do
-        stub_gitlab_rb(
-          sidekiq:
-            {
-              metrics_enabled: true,
-              listen_address: 'localhost',
-              listen_port: 3807,
-              health_checks_enabled: true,
-              health_checks_listen_address: 'localhost',
-              health_checks_listen_port: 3807
-            }
-        )
-      end
-
-      it 'logs a warning' do
-        allow(LoggingHelper).to receive(:deprecation)
-        expect(LoggingHelper).to receive(:deprecation).with("Sidekiq exporter and health checks are set to the same address and port. This is deprecated and will result in an error in version 15.0. See https://docs.gitlab.com/ee/administration/sidekiq.html")
-
-        chef_run
-      end
-    end
-
-    context 'when Sidekiq exporter and Sidekiq health checks port are different' do
-      before do
-        stub_gitlab_rb(
-          sidekiq:
-            {
-              metrics_enabled: true,
-              listen_address: 'localhost',
-              listen_port: 3807,
-              health_checks_enabled: true,
-              health_checks_listen_address: 'localhost',
-              health_checks_listen_port: 3907
-            }
-        )
-      end
-
-      it 'does not log a warning' do
-        expect(LoggingHelper).not_to receive(:deprecation).with("Sidekiq exporter and health checks are set to the same address and port. This is deprecated and will result in an error in version 15.0. See https://docs.gitlab.com/ee/administration/sidekiq.html")
-
-        chef_run
-      end
-    end
-  end
-
-  context 'with sidekiq exporter settings not set (default settings)' do
-    it 'does not log a warning' do
-      expect(LoggingHelper).not_to receive(:deprecation).with("Sidekiq exporter and health checks are set to the same address and port. This is deprecated and will result in an error in version 15.0. See https://docs.gitlab.com/ee/administration/sidekiq.html")
-
-      chef_run
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitaly_spec.rb b/spec/chef/recipes/gitaly_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0YWx5X3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitaly_spec.rb
+++ /dev/null
@@ -1,890 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitaly' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  let(:config_path) { '/var/opt/gitlab/gitaly/config.toml' }
-  let(:gitaly_config) { chef_run.template(config_path) }
-  let(:internal_socket_dir) { '/var/opt/gitlab/gitaly/user_defined/internal_sockets' }
-  let(:socket_path) { '/tmp/gitaly.socket' }
-  let(:listen_addr) { 'localhost:7777' }
-  let(:tls_listen_addr) { 'localhost:8888' }
-  let(:certificate_path) { '/path/to/cert.pem' }
-  let(:key_path) { '/path/to/key.pem' }
-  let(:prometheus_listen_addr) { 'localhost:9000' }
-  let(:logging_level) { 'warn' }
-  let(:logging_format) { 'default' }
-  let(:logging_sentry_dsn) { 'https://my_key:my_secret@sentry.io/test_project' }
-  let(:logging_ruby_sentry_dsn) { 'https://my_key:my_secret@sentry.io/test_project-ruby' }
-  let(:logging_sentry_environment) { 'production' }
-  let(:prometheus_grpc_latency_buckets) do
-    '[0.001, 0.005, 0.025, 0.1, 0.5, 1.0, 10.0, 30.0, 60.0, 300.0, 1500.0]'
-  end
-  let(:auth_token) { '123secret456' }
-  let(:auth_transitioning) { true }
-  let(:ruby_max_rss) { 1000000 }
-  let(:graceful_restart_timeout) { '20m' }
-  let(:ruby_graceful_restart_timeout) { '30m' }
-  let(:ruby_restart_delay) { '10m' }
-  let(:ruby_num_workers) { 5 }
-  let(:ruby_rugged_git_config_search_path) { '/path/to/opt/gitlab/embedded/etc' }
-  let(:git_catfile_cache_size) { 50 }
-  let(:git_bin_path) { '/path/to/usr/bin/git' }
-  let(:use_bundled_git) { true }
-  let(:open_files_ulimit) { 10000 }
-  let(:default_vars) do
-    {
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
-      'TZ' => ':/etc/localtime',
-      'HOME' => '/var/opt/gitlab',
-      'PATH' => '/opt/gitlab/bin:/opt/gitlab/embedded/bin:/bin:/usr/bin',
-      'ICU_DATA' => '/opt/gitlab/embedded/share/icu/current',
-      'PYTHONPATH' => '/opt/gitlab/embedded/lib/python3.9/site-packages',
-      'WRAPPER_JSON_LOGGING' => 'true',
-      "GITALY_PID_FILE" => '/var/opt/gitlab/gitaly/gitaly.pid',
-    }
-  end
-
-  let(:gitlab_url) { 'http://localhost:3000' }
-  let(:workhorse_addr) { 'localhost:4000' }
-  let(:gitaly_custom_hooks_dir) { '/path/to/gitaly/custom/hooks' }
-  let(:gitlab_shell_custom_hooks_dir) { '/path/to/gitlab-shell/custom/hooks' }
-  let(:user) { 'user123' }
-  let(:password) { 'password321' }
-  let(:ca_file) { '/path/to/ca_file' }
-  let(:ca_path) { '/path/to/ca_path' }
-  let(:self_signed_cert) { true }
-  let(:read_timeout) { 123 }
-  let(:daily_maintenance_start_hour) { 21 }
-  let(:daily_maintenance_start_minute) { 9 }
-  let(:daily_maintenance_duration) { '45m' }
-  let(:daily_maintenance_storages) { ["default"] }
-  let(:daily_maintenance_disabled) { false }
-  let(:cgroups_count) { 10 }
-  let(:cgroups_mountpoint) { '/sys/fs/cgroup' }
-  let(:cgroups_hierarchy_root) { 'gitaly' }
-  let(:cgroups_memory_enabled) { true }
-  let(:cgroups_memory_limit) { 1048576 }
-  let(:cgroups_cpu_enabled) { true }
-  let(:cgroups_cpu_shares) { 512 }
-  let(:pack_objects_cache_enabled) { true }
-  let(:pack_objects_cache_dir) { '/pack-objects-cache' }
-  let(:pack_objects_cache_max_age) { '10m' }
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'by default' do
-    it_behaves_like "enabled runit service", "gitaly", "root", "root"
-
-    it 'creates expected directories with correct permissions' do
-      expect(chef_run).to create_directory('/var/opt/gitlab/gitaly').with(user: 'git', mode: '0700')
-      expect(chef_run).to create_directory('/var/log/gitlab/gitaly').with(user: 'git', mode: '0700')
-    end
-
-    it 'creates a default VERSION file and restarts service' do
-      expect(chef_run).to create_version_file('Create version file for Gitaly').with(
-        version_file_path: '/var/opt/gitlab/gitaly/VERSION',
-        version_check_cmd: "/opt/gitlab/embedded/bin/ruby -rdigest/sha2 -e 'puts %(sha256:) + Digest::SHA256.file(%(/opt/gitlab/embedded/bin/gitaly)).hexdigest'"
-      )
-
-      expect(chef_run.version_file('Create version file for Gitaly')).to notify('runit_service[gitaly]').to(:hup)
-    end
-
-    it 'creates a default RUBY_VERSION file and restarts service' do
-      expect(chef_run).to create_version_file('Create Ruby version file for Gitaly').with(
-        version_file_path: '/var/opt/gitlab/gitaly/RUBY_VERSION',
-        version_check_cmd: '/opt/gitlab/embedded/bin/ruby --version'
-      )
-
-      expect(chef_run.version_file('Create Ruby version file for Gitaly')).to notify('runit_service[gitaly]').to(:hup)
-    end
-
-    it 'populates gitaly config.toml with defaults' do
-      expect(chef_run).to render_file(config_path).with_content { |content|
-        expect(content).to include("socket_path = '/var/opt/gitlab/gitaly/gitaly.socket'")
-        expect(content).to include("internal_socket_dir = '/var/opt/gitlab/gitaly/internal_sockets'")
-        expect(content).to include("bin_dir = '/opt/gitlab/embedded/bin'")
-        expect(content).to include(%(rugged_git_config_search_path = "/opt/gitlab/embedded/etc"))
-      }
-
-      expect(chef_run).not_to render_file(config_path)
-        .with_content("listen_addr = '#{listen_addr}'")
-      expect(chef_run).not_to render_file(config_path)
-        .with_content("tls_listen_addr =")
-      expect(chef_run).not_to render_file(config_path)
-       .with_content("certificate_path  =")
-      expect(chef_run).not_to render_file(config_path)
-       .with_content("key_path  =")
-      expect(chef_run).not_to render_file(config_path)
-        .with_content("prometheus_listen_addr = '#{prometheus_listen_addr}'")
-      expect(chef_run).not_to render_file(config_path)
-        .with_content(%r{\[logging\]\s+level = '#{logging_level}'\s+format = '#{logging_format}'\s+sentry_dsn = '#{logging_sentry_dsn}'})
-      expect(chef_run).not_to render_file(config_path)
-        .with_content(%r{\[logging\]\s+level = '#{logging_level}'\s+format = '#{logging_format}'\s+ruby_sentry_dsn = '#{logging_ruby_sentry_dsn}'})
-      expect(chef_run).not_to render_file(config_path)
-        .with_content(%r{\[logging\]\s+level = '#{logging_level}'\s+format = '#{logging_format}'\s+sentry_environment = '#{logging_sentry_environment}'})
-      expect(chef_run).not_to render_file(config_path)
-        .with_content(%r{\[prometheus\]\s+grpc_latency_buckets = #{Regexp.escape(prometheus_grpc_latency_buckets)}})
-      expect(chef_run).not_to render_file(config_path)
-        .with_content(%r{\[auth\]\s+token = })
-      expect(chef_run).not_to render_file(config_path)
-        .with_content('transitioning =')
-      expect(chef_run).not_to render_file(config_path)
-        .with_content('max_rss =')
-      expect(chef_run).not_to render_file(config_path)
-        .with_content('graceful_restart_timeout =')
-      expect(chef_run).not_to render_file(config_path)
-        .with_content('restart_delay =')
-      expect(chef_run).not_to render_file(config_path)
-        .with_content('num_workers =')
-      expect(chef_run).not_to render_file(config_path)
-        .with_content(%r{\[logging\]\s+level})
-      expect(chef_run).not_to render_file(config_path)
-        .with_content(%r{catfile_cache_size})
-      expect(chef_run).not_to render_file(config_path)
-        .with_content('[pack_objects_cache]')
-    end
-
-    it 'populates gitaly config.toml with default git binary path' do
-      expect(chef_run).to render_file(config_path)
-        .with_content("bin_path = '/opt/gitlab/embedded/bin/git'")
-    end
-
-    it 'populates gitaly config.toml with default storages' do
-      expect(chef_run).to render_file(config_path)
-        .with_content(%r{\[\[storage\]\]\s+name = 'default'\s+path = '/var/opt/gitlab/git-data/repositories'})
-    end
-
-    it 'renders the runit run script with defaults' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/run')
-        .with_content(%r{ulimit -n 15000})
-    end
-
-    it 'does not append timestamp in logs if logging format is json' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/log/run')
-        .with_content(/exec svlogd \/var\/log\/gitlab\/gitaly/)
-    end
-
-    it 'populates gitaly config.toml with gitlab-shell values' do
-      expect(chef_run).to render_file(config_path)
-        .with_content(%r{\[gitlab-shell\]\s+dir = "/opt/gitlab/embedded/service/gitlab-shell"})
-    end
-
-    it 'populates gitaly config.toml with gitlab-workhorse socket' do
-      expect(chef_run).to render_file(config_path)
-        .with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket'\s+relative_url_root = ''})
-    end
-  end
-
-  context 'with user settings' do
-    before do
-      stub_gitlab_rb(
-        gitaly: {
-          socket_path: socket_path,
-          internal_socket_dir: internal_socket_dir,
-          listen_addr: listen_addr,
-          tls_listen_addr: tls_listen_addr,
-          certificate_path: certificate_path,
-          key_path: key_path,
-          prometheus_listen_addr: prometheus_listen_addr,
-          logging_level: logging_level,
-          logging_format: logging_format,
-          logging_sentry_dsn: logging_sentry_dsn,
-          logging_ruby_sentry_dsn: logging_ruby_sentry_dsn,
-          logging_sentry_environment: logging_sentry_environment,
-          prometheus_grpc_latency_buckets: prometheus_grpc_latency_buckets,
-          auth_token: auth_token,
-          auth_transitioning: auth_transitioning,
-          graceful_restart_timeout: graceful_restart_timeout,
-          ruby_max_rss: ruby_max_rss,
-          ruby_graceful_restart_timeout: ruby_graceful_restart_timeout,
-          ruby_restart_delay: ruby_restart_delay,
-          ruby_num_workers: ruby_num_workers,
-          git_catfile_cache_size: git_catfile_cache_size,
-          git_bin_path: git_bin_path,
-          use_bundled_git: true,
-          open_files_ulimit: open_files_ulimit,
-          ruby_rugged_git_config_search_path: ruby_rugged_git_config_search_path,
-          daily_maintenance_start_hour: daily_maintenance_start_hour,
-          daily_maintenance_start_minute: daily_maintenance_start_minute,
-          daily_maintenance_duration: daily_maintenance_duration,
-          daily_maintenance_storages: daily_maintenance_storages,
-          daily_maintenance_disabled: daily_maintenance_disabled,
-          cgroups_count: cgroups_count,
-          cgroups_mountpoint: cgroups_mountpoint,
-          cgroups_hierarchy_root: cgroups_hierarchy_root,
-          cgroups_memory_enabled: cgroups_memory_enabled,
-          cgroups_memory_limit: cgroups_memory_limit,
-          cgroups_cpu_enabled: cgroups_cpu_enabled,
-          cgroups_cpu_shares: cgroups_cpu_shares,
-          pack_objects_cache_enabled: pack_objects_cache_enabled,
-          pack_objects_cache_dir: pack_objects_cache_dir,
-          pack_objects_cache_max_age: pack_objects_cache_max_age,
-          custom_hooks_dir: gitaly_custom_hooks_dir
-        },
-        gitlab_rails: {
-          internal_api_url: gitlab_url
-        },
-        gitlab_shell: {
-          http_settings: {
-            read_timeout: read_timeout,
-            user: user,
-            password: password,
-            ca_file: ca_file,
-            ca_path: ca_path,
-            self_signed_cert: self_signed_cert
-          }
-        },
-        gitlab_workhorse: {
-          listen_network: 'tcp',
-          listen_addr: workhorse_addr,
-        },
-        user: {
-          username: 'foo',
-          group: 'bar'
-        }
-      )
-    end
-
-    it_behaves_like "enabled runit service", "gitaly", "root", "root"
-
-    it 'creates expected directories with correct permissions' do
-      expect(chef_run).to create_directory(internal_socket_dir).with(user: 'foo', mode: '0700')
-    end
-
-    it 'populates gitaly config.toml with custom values' do
-      expect(chef_run).to render_file(config_path)
-        .with_content("socket_path = '#{socket_path}'")
-      expect(chef_run).to render_file(config_path)
-        .with_content("internal_socket_dir = '#{internal_socket_dir}'")
-      expect(chef_run).to render_file(config_path)
-        .with_content("bin_dir = '/opt/gitlab/embedded/bin'")
-      expect(chef_run).to render_file(config_path)
-        .with_content("listen_addr = 'localhost:7777'")
-      expect(chef_run).to render_file(config_path)
-        .with_content("graceful_restart_timeout = '#{graceful_restart_timeout}'")
-      expect(chef_run).to render_file(config_path)
-        .with_content("bin_path = '#{git_bin_path}'")
-      expect(chef_run).to render_file(config_path)
-        .with_content("use_bundled_binaries = true")
-
-      gitaly_logging_section = Regexp.new([
-        %r{\[logging\]},
-        %r{level = '#{logging_level}'},
-        %r{format = '#{logging_format}'},
-        %r{sentry_dsn = '#{logging_sentry_dsn}'},
-        %r{ruby_sentry_dsn = '#{logging_ruby_sentry_dsn}'},
-        %r{sentry_environment = '#{logging_sentry_environment}'},
-      ].map(&:to_s).join('\s+'))
-
-      gitaly_ruby_section = Regexp.new([
-        %r{\[gitaly-ruby\]},
-        %r{dir = "/opt/gitlab/embedded/service/gitaly-ruby"},
-        %r{max_rss = #{ruby_max_rss}},
-        %r{graceful_restart_timeout = '#{Regexp.escape(ruby_graceful_restart_timeout)}'},
-        %r{restart_delay = '#{Regexp.escape(ruby_restart_delay)}'},
-        %r{num_workers = #{ruby_num_workers}},
-      ].map(&:to_s).join('\s+'))
-
-      gitlab_shell_section = Regexp.new([
-        %r{\[gitlab-shell\]},
-        %r{dir = "/opt/gitlab/embedded/service/gitlab-shell"},
-      ].map(&:to_s).join('\s+'))
-
-      gitlab_section = Regexp.new([
-        %r{\[gitlab\]},
-        %r{url = '#{Regexp.escape(gitlab_url)}'},
-        %r{\[gitlab.http-settings\]},
-        %r{read_timeout = #{read_timeout}},
-        %r{user = '#{Regexp.escape(user)}'},
-        %r{password = '#{Regexp.escape(password)}'},
-        %r{ca_file = '#{Regexp.escape(ca_file)}'},
-        %r{ca_path = '#{Regexp.escape(ca_path)}'},
-        %r{self_signed_cert = #{self_signed_cert}},
-      ].map(&:to_s).join('\s+'))
-
-      hooks_section = Regexp.new([
-        %r{\[hooks\]},
-        %r{custom_hooks_dir = '#{Regexp.escape(gitaly_custom_hooks_dir)}'},
-      ].map(&:to_s).join('\s+'))
-
-      maintenance_section = Regexp.new([
-        %r{\[daily_maintenance\]},
-        %r{start_hour = #{daily_maintenance_start_hour}},
-        %r{start_minute = #{daily_maintenance_start_minute}},
-        %r{duration = '#{daily_maintenance_duration}'},
-        %r{storages = #{Regexp.escape(daily_maintenance_storages.to_s)}},
-      ].map(&:to_s).join('\s+'))
-
-      cgroups_section = Regexp.new([
-        %r{\[cgroups\]},
-        %r{count = #{cgroups_count}},
-        %r{mountpoint = '#{cgroups_mountpoint}'},
-        %r{hierarchy_root = '#{cgroups_hierarchy_root}'},
-      ].map(&:to_s).join('\s+'))
-
-      cgroups_memory_section = Regexp.new([
-        %r{\[cgroups\.memory\]},
-        %r{enabled = #{cgroups_memory_enabled}},
-        %r{limit = #{cgroups_memory_limit}},
-      ].map(&:to_s).join('\s+'))
-
-      cgroups_cpu_section = Regexp.new([
-        %r{\[cgroups\.cpu\]},
-        %r{enabled = #{cgroups_cpu_enabled}},
-        %r{shares = #{cgroups_cpu_shares}},
-      ].map(&:to_s).join('\s+'))
-
-      pack_objects_cache_section = Regexp.new([
-        %r{\[pack_objects_cache\]\nenabled = true},
-        %r{dir = '#{pack_objects_cache_dir}'},
-        %r{max_age = '#{pack_objects_cache_max_age}'},
-      ].map(&:to_s).join('\s+'))
-
-      expect(chef_run).to render_file(config_path).with_content { |content|
-        expect(content).to include("tls_listen_addr = 'localhost:8888'")
-        expect(content).to include("certificate_path = '/path/to/cert.pem'")
-        expect(content).to include("key_path = '/path/to/key.pem'")
-        expect(content).to include(%(rugged_git_config_search_path = "#{ruby_rugged_git_config_search_path}"))
-        expect(content).to include("prometheus_listen_addr = 'localhost:9000'")
-        expect(content).to match(gitaly_logging_section)
-        expect(content).to match(%r{\[prometheus\]\s+grpc_latency_buckets = #{Regexp.escape(prometheus_grpc_latency_buckets)}})
-        expect(content).to match(%r{\[auth\]\s+token = '#{Regexp.escape(auth_token)}'\s+transitioning = true})
-        expect(content).to match(gitaly_ruby_section)
-        expect(content).to match(%r{\[git\]\s+catfile_cache_size = 50})
-        expect(content).to match(gitlab_shell_section)
-        expect(content).to match(gitlab_section)
-        expect(content).to match(hooks_section)
-        expect(content).to match(maintenance_section)
-        expect(content).to match(cgroups_section)
-        expect(content).to match(cgroups_memory_section)
-        expect(content).to match(cgroups_cpu_section)
-        expect(content).to match(pack_objects_cache_section)
-      }
-    end
-
-    it 'populates gitaly config.toml with custom deprecated values' do
-      stub_gitlab_rb(
-        gitlab_shell: {
-          custom_hooks_dir: gitlab_shell_custom_hooks_dir
-        }
-      )
-
-      hooks_section = Regexp.new([
-        %r{\[hooks\]},
-        %r{custom_hooks_dir = '#{Regexp.escape(gitlab_shell_custom_hooks_dir)}'},
-      ].map(&:to_s).join('\s+'))
-
-      expect(chef_run).to render_file(config_path).with_content { |content|
-        expect(content).to match(hooks_section)
-      }
-    end
-
-    it 'renders the runit run script with custom values' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/run')
-        .with_content(%r{ulimit -n #{open_files_ulimit}})
-    end
-
-    it 'renders the runit run script with cgroup root creation' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/run').with_content { |content|
-        expect(content).to match(%r{mkdir -m 0700 -p #{cgroups_mountpoint}/memory/#{cgroups_hierarchy_root}})
-        expect(content).to match(%r{mkdir -m 0700 -p #{cgroups_mountpoint}/cpu/#{cgroups_hierarchy_root}})
-        expect(content).to match(%r{chown foo:bar #{cgroups_mountpoint}/memory/#{cgroups_hierarchy_root}})
-        expect(content).to match(%r{chown foo:bar #{cgroups_mountpoint}/cpu/#{cgroups_hierarchy_root}})
-      }
-    end
-
-    it 'populates sv related log files' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/log/run')
-        .with_content(/exec svlogd -tt \/var\/log\/gitlab\/gitaly/)
-    end
-
-    context 'when using multiple maintenance storage entries' do
-      let(:daily_maintenance_storages) { %w(storage0 storage1 storage2) }
-
-      it 'renders daily_maintenance with multiple storage entries' do
-        expect(chef_run).to render_file(config_path).with_content { |content|
-          expect(content).to include("storages = #{daily_maintenance_storages}")
-        }
-      end
-    end
-
-    context 'when maintenance is disabled' do
-      let(:daily_maintenance_disabled) { true }
-
-      it 'renders daily_maintenance with disabled set to true' do
-        expect(chef_run).to render_file(config_path).with_content { |content|
-          expect(content).to include("[daily_maintenance]\ndisabled = true\n\n")
-        }
-      end
-    end
-
-    context 'when using gitaly storage configuration' do
-      before do
-        stub_gitlab_rb(
-          gitaly: {
-            storage: [
-              {
-                'name' => 'default',
-                'path' => '/tmp/path-1'
-              },
-              {
-                'name' => 'nfs1',
-                'path' => '/mnt/nfs1'
-              }
-            ]
-          }
-        )
-      end
-
-      it 'populates gitaly config.toml with custom storages' do
-        expect(chef_run).to render_file(config_path)
-          .with_content(%r{\[\[storage\]\]\s+name = 'default'\s+path = '/tmp/path-1'})
-        expect(chef_run).to render_file(config_path)
-          .with_content(%r{\[\[storage\]\]\s+name = 'nfs1'\s+path = '/mnt/nfs1'})
-      end
-    end
-
-    context 'when using git_data_dirs storage configuration' do
-      context 'using local gitaly' do
-        before do
-          stub_gitlab_rb(
-            {
-              git_data_dirs:
-              {
-                'default' => { 'path' => '/tmp/default/git-data' },
-                'nfs1' => { 'path' => '/mnt/nfs1' }
-              }
-            }
-          )
-        end
-
-        it 'populates gitaly config.toml with custom storages' do
-          expect(chef_run).to render_file(config_path)
-            .with_content(%r{\[\[storage\]\]\s+name = 'default'\s+path = '/tmp/default/git-data/repositories'})
-          expect(chef_run).to render_file(config_path)
-            .with_content(%r{\[\[storage\]\]\s+name = 'nfs1'\s+path = '/mnt/nfs1/repositories'})
-          expect(chef_run).not_to render_file(config_path)
-            .with_content('gitaly_address: "/var/opt/gitlab/gitaly/gitaly.socket"')
-        end
-      end
-
-      context 'using external gitaly' do
-        before do
-          stub_gitlab_rb(
-            {
-              git_data_dirs:
-              {
-                'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
-              }
-            }
-          )
-        end
-
-        it 'populates gitaly config.toml with custom storages' do
-          expect(chef_run).to render_file(config_path)
-            .with_content(%r{\[\[storage\]\]\s+name = 'default'\s+path = '/var/opt/gitlab/git-data/repositories'})
-          expect(chef_run).not_to render_file(config_path)
-            .with_content('gitaly_address: "tcp://gitaly.internal:8075"')
-        end
-      end
-    end
-  end
-
-  context 'when gitaly is disabled' do
-    before do
-      stub_gitlab_rb(gitaly: { enable: false })
-    end
-
-    it_behaves_like "disabled runit service", "gitaly"
-
-    it 'does not create the gitaly directories' do
-      expect(chef_run).not_to create_directory('/var/opt/gitlab/gitaly')
-      expect(chef_run).not_to create_directory('/var/log/gitlab/gitaly')
-      expect(chef_run).not_to create_directory('/opt/gitlab/etc/gitaly')
-      expect(chef_run).not_to create_file('/var/opt/gitlab/gitaly/config.toml')
-    end
-  end
-
-  context 'when using concurrency configuration' do
-    before do
-      stub_gitlab_rb(
-        {
-          gitaly: {
-            concurrency: [
-              {
-                'rpc' => "/gitaly.SmartHTTPService/PostReceivePack",
-                'max_per_repo' => 20
-              }, {
-                'rpc' => "/gitaly.SSHService/SSHUploadPack",
-                'max_per_repo' => 5
-              }
-            ]
-          }
-        }
-      )
-    end
-
-    it 'populates gitaly config.toml with custom concurrency configurations' do
-      expect(chef_run).to render_file(config_path)
-        .with_content(%r{\[\[concurrency\]\]\s+rpc = "/gitaly.SmartHTTPService/PostReceivePack"\s+max_per_repo = 20})
-      expect(chef_run).to render_file(config_path)
-        .with_content(%r{\[\[concurrency\]\]\s+rpc = "/gitaly.SSHService/SSHUploadPack"\s+max_per_repo = 5})
-    end
-  end
-
-  shared_examples 'empty concurrency configuration' do
-    it 'does not generate a gitaly concurrency configuration' do
-      expect(chef_run).not_to render_file(config_path)
-        .with_content(%r{\[\[concurrency\]\]})
-    end
-  end
-
-  context 'when not using concurrency configuration' do
-    context 'when concurrency configuration is not set' do
-      before do
-        stub_gitlab_rb(
-          {
-            gitaly: {
-            }
-          }
-        )
-      end
-
-      it_behaves_like 'empty concurrency configuration'
-    end
-
-    context 'when concurrency configuration is empty' do
-      before do
-        stub_gitlab_rb(
-          {
-            gitaly: {
-              concurrency: []
-            }
-          }
-        )
-      end
-
-      it_behaves_like 'empty concurrency configuration'
-    end
-
-    context 'when max_queue_size and max_queue_wait are empty' do
-      before do
-        stub_gitlab_rb(
-          {
-            gitaly: {
-              concurrency: [
-                {
-                  'rpc' => "/gitaly.SmartHTTPService/PostReceivePack",
-                  'max_per_repo' => 20,
-                }, {
-                  'rpc' => "/gitaly.SSHService/SSHUploadPack",
-                  'max_per_repo' => 5,
-                }
-              ]
-            }
-          }
-        )
-      end
-
-      it 'populates gitaly config.toml without max_queue_size and max_queue_wait' do
-        expect(chef_run).to render_file(config_path).with_content { |content|
-          expect(content).not_to include("max_queue_size")
-          expect(content).not_to include("max_queue_wait")
-        }
-      end
-    end
-
-    context 'when max_per_repo is empty' do
-      before do
-        stub_gitlab_rb(
-          {
-            gitaly: {
-              concurrency: [
-                {
-                  'rpc' => "/gitaly.SmartHTTPService/PostReceivePack",
-                  'max_queue_size' => '10s'
-                }, {
-                  'rpc' => "/gitaly.SSHService/SSHUploadPack",
-                  'max_queue_size' => '10s'
-                }
-              ]
-            }
-          }
-        )
-      end
-
-      it 'populates gitaly config.toml without max_per_repo' do
-        expect(chef_run).to render_file(config_path).with_content { |content|
-          expect(content).not_to include("max_per_repo")
-        }
-      end
-    end
-
-    context 'when max_queue_wait is set' do
-      before do
-        stub_gitlab_rb(
-          {
-            gitaly: {
-              concurrency: [
-                {
-                  'rpc' => "/gitaly.SmartHTTPService/PostReceivePack",
-                  'max_queue_wait' => "10s",
-                }
-              ]
-            }
-          }
-        )
-      end
-
-      it 'populates gitaly config.toml with quoted max_queue_wait' do
-        expect(chef_run).to render_file(config_path)
-        .with_content(%r{\[\[concurrency\]\]\s+rpc = "/gitaly.SmartHTTPService/PostReceivePack"\s+max_queue_wait = "10s"})
-      end
-    end
-  end
-
-  context 'populates default env variables' do
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitaly/env').with_variables(default_vars)
-    end
-  end
-
-  context 'computes env variables based on other values' do
-    before do
-      stub_gitlab_rb(
-        {
-          user: {
-            home: "/my/random/path"
-          }
-        }
-      )
-    end
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitaly/env').with_variables(
-        default_vars.merge(
-          {
-            'HOME' => '/my/random/path',
-          }
-        )
-      )
-    end
-  end
-
-  context 'with a non-default workhorse unix socket' do
-    context 'with only a listen address set' do
-      before do
-        stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/fake/workhorse/socket' })
-      end
-
-      it 'create config file with provided values' do
-        expect(chef_run).to render_file(config_path)
-          .with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Ffake%2Fworkhorse%2Fsocket'\s+relative_url_root = ''})
-      end
-    end
-
-    context 'with only a socket directory set' do
-      before do
-        stub_gitlab_rb(gitlab_workhorse: { sockets_directory: '/fake/workhorse/sockets' })
-      end
-
-      it 'create config file with provided values' do
-        expect(chef_run).to render_file(config_path)
-          .with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Ffake%2Fworkhorse%2Fsockets%2Fsocket'\s+relative_url_root = ''})
-      end
-    end
-
-    context 'with a listen_address and a sockets_directory set' do
-      before do
-        stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/sockets/in/the/wind', sockets_directory: '/sockets/in/the' })
-      end
-
-      it 'create config file with provided values' do
-        expect(chef_run).to render_file(config_path)
-          .with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Fsockets%2Fin%2Fthe%2Fwind'\s+relative_url_root = ''})
-      end
-    end
-  end
-
-  context 'with a tcp workhorse listener' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'http://example.com/gitlab',
-        gitlab_workhorse: {
-          listen_network: 'tcp',
-          listen_addr: 'localhost:1234'
-        }
-      )
-    end
-
-    it 'create config file with only the URL set' do
-      expect(chef_run).to render_file(config_path).with_content { |content|
-        expect(content).to match(%r{\[gitlab\]\s+url = 'http://localhost:1234/gitlab'})
-        expect(content).not_to match(/relative_url_root/)
-      }
-    end
-  end
-
-  context 'with relative path in external_url' do
-    before do
-      stub_gitlab_rb(external_url: 'http://example.com/gitlab')
-    end
-
-    it 'create config file with the relative_url_root set' do
-      expect(chef_run).to render_file(config_path)
-        .with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket'\s+relative_url_root = '/gitlab'})
-    end
-  end
-
-  context 'with cgroups mountpoint and hierarchy_root' do
-    before do
-      stub_gitlab_rb(
-        gitaly: {
-          cgroups_mountpoint: '/sys/fs/cgroup',
-          cgroups_hierarchy_root: 'gitaly'
-        }
-      )
-    end
-  end
-
-  include_examples "consul service discovery", "gitaly", "gitaly"
-end
-
-RSpec.describe 'gitaly::git_data_dirs' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when user has not specified git_data_dir' do
-    it 'defaults to correct path' do
-      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages'])
-        .to eql('default' => { 'path' => '/var/opt/gitlab/git-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' })
-    end
-  end
-
-  context 'when gitaly is set to use a listen_addr' do
-    before do
-      stub_gitlab_rb(git_data_dirs: {
-                       'default' => {
-                         'path' => '/tmp/user/git-data'
-                       }
-                     }, gitaly: {
-                       socket_path: '',
-                       listen_addr: 'localhost:8123'
-                     })
-    end
-
-    it 'correctly sets the repository storage directories' do
-      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages'])
-        .to eql('default' => { 'path' => '/tmp/user/git-data/repositories', 'gitaly_address' => 'tcp://localhost:8123' })
-    end
-  end
-
-  context 'when gitaly is set to use a tls_listen_addr' do
-    before do
-      stub_gitlab_rb(git_data_dirs: {
-                       'default' => {
-                         'path' => '/tmp/user/git-data'
-                       }
-                     }, gitaly: {
-                       socket_path: '', tls_listen_addr: 'localhost:8123'
-                     })
-    end
-
-    it 'correctly sets the repository storage directories' do
-      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages'])
-        .to eql('default' => { 'path' => '/tmp/user/git-data/repositories', 'gitaly_address' => 'tls://localhost:8123' })
-    end
-  end
-
-  context 'when both tls and socket' do
-    before do
-      stub_gitlab_rb(git_data_dirs: {
-                       'default' => {
-                         'path' => '/tmp/user/git-data'
-                       }
-                     }, gitaly: {
-                       socket_path: '/some/socket/path.socket', tls_listen_addr: 'localhost:8123'
-                     })
-    end
-
-    it 'TlS should take precedence' do
-      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages'])
-        .to eql('default' => { 'path' => '/tmp/user/git-data/repositories', 'gitaly_address' => 'tls://localhost:8123' })
-    end
-  end
-
-  context 'when git_data_dirs is set to multiple directories' do
-    before do
-      stub_gitlab_rb({
-                       git_data_dirs: {
-                         'default' => { 'path' => '/tmp/default/git-data' },
-                         'overflow' => { 'path' => '/tmp/other/git-overflow-data' }
-                       }
-                     })
-    end
-
-    it 'correctly sets the repository storage directories' do
-      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages']).to eql({
-                                                                                        'default' => { 'path' => '/tmp/default/git-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
-                                                                                        'overflow' => { 'path' => '/tmp/other/git-overflow-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' }
-                                                                                      })
-    end
-  end
-
-  context 'when git_data_dirs is set to multiple directories with different gitaly addresses' do
-    before do
-      stub_gitlab_rb({
-                       git_data_dirs: {
-                         'default' => { 'path' => '/tmp/default/git-data' },
-                         'overflow' => { 'path' => '/tmp/other/git-overflow-data', 'gitaly_address' => 'tcp://localhost:8123', 'gitaly_token' => '123secret456gitaly' }
-                       }
-                     })
-    end
-
-    it 'correctly sets the repository storage directories' do
-      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages']).to eql({
-                                                                                        'default' => { 'path' => '/tmp/default/git-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
-                                                                                        'overflow' => { 'path' => '/tmp/other/git-overflow-data/repositories', 'gitaly_address' => 'tcp://localhost:8123', 'gitaly_token' => '123secret456gitaly' }
-                                                                                      })
-    end
-  end
-
-  context 'when path not defined in git_data_dirs' do
-    before do
-      stub_gitlab_rb(
-        {
-          git_data_dirs:
-          {
-            'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
-          }
-        }
-      )
-    end
-
-    it 'correctly sets the repository storage directories' do
-      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages']).to eql({ 'default' => { 'path' => '/var/opt/gitlab/git-data/repositories', 'gitaly_address' => 'tcp://gitaly.internal:8075' } })
-    end
-  end
-
-  context 'when git_data_dirs is set with symbol keys rather than string keys' do
-    before do
-      with_symbol_keys = {
-        default: { path: '/tmp/default/git-data' },
-        overflow: { path: '/tmp/other/git-overflow-data' }
-      }
-
-      allow(Gitlab).to receive(:[]).with('git_data_dirs').and_return(with_symbol_keys)
-    end
-
-    it 'correctly sets the repository storage directories' do
-      expect(chef_run.node['gitlab']['gitlab-rails']['repositories_storages']).to eql({
-                                                                                        'default' => { 'path' => '/tmp/default/git-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
-                                                                                        'overflow' => { 'path' => '/tmp/other/git-overflow-data/repositories', 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' }
-                                                                                      })
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-healthcheck_spec.rb b/spec/chef/recipes/gitlab-healthcheck_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLWhlYWx0aGNoZWNrX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-healthcheck_spec.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-healthcheck' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'nginx is enabled' do
-    before do
-      stub_gitlab_rb(nginx: { enable: true })
-    end
-
-    it 'correctly renders the healthcheck-rc file' do
-      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
-        .with_content(%r{url='http://localhost:80/help'})
-      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc").with_content(%r{flags='--insecure'})
-    end
-
-    it 'correctly renders out the healthcheck-rc file when using https' do
-      stub_gitlab_rb(external_url: 'https://gitlab.example.com')
-      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
-        .with_content(%r{url='https://localhost:443/help'})
-    end
-
-    it 'correctly renders out the healthcheck-rc file when using custom port' do
-      stub_gitlab_rb(external_url: 'http://gitlab.example.com:8080')
-      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
-        .with_content(%r{url='http://localhost:8080/help'})
-    end
-
-    it 'correctly renders out the healthcheck-rc file when using a relative url' do
-      stub_gitlab_rb(external_url: 'http://gitlab.example.com/custom')
-      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
-        .with_content(%r{url='http://localhost:80/custom/help'})
-    end
-  end
-
-  context 'nginx is disabled' do
-    before do
-      stub_gitlab_rb(nginx: { enable: false })
-    end
-
-    it 'correctly renders the healthcheck-rc file using workhorse' do
-      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
-        .with_content(%r{url='http://localhost/help'})
-      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
-        .with_content(%r{flags='--unix-socket /var/opt/gitlab/gitlab-workhorse/sockets/socket'})
-    end
-
-    it 'correctly renders healthcheck-rc file using workhorse on a port' do
-      stub_gitlab_rb(
-        gitlab_workhorse: { listen_network: 'tcp', listen_addr: 'localhost:9191' }
-      )
-      expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
-        .with_content(%r{url='http://localhost:9191/help'})
-    end
-
-    it 'does not render the healthcheck-rc file when workhorse workhorse is disabled' do
-      stub_gitlab_rb(nginx: { enable: false }, gitlab_workhorse: { enable: false })
-      expect(chef_run).not_to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-kas_spec.rb b/spec/chef/recipes/gitlab-kas_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLWthc19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-kas_spec.rb
+++ /dev/null
@@ -1,508 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab-kas' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service env_dir templatesymlink)).converge('gitlab::default') }
-  let(:gitlab_kas_config_yml) { chef_run_load_yaml_template(chef_run, '/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when application_role is configured' do
-    before do
-      stub_gitlab_rb(roles: %w(application_role))
-    end
-
-    it 'should be enabled' do
-      expect(chef_run).to include_recipe('gitlab-kas::enable')
-    end
-  end
-
-  context 'with defaults' do
-    before do
-      stub_gitlab_rb(external_url: 'https://gitlab.example.com')
-    end
-
-    it 'creates a default VERSION file and restarts service' do
-      expect(chef_run).to create_version_file('Create version file for Gitlab KAS').with(
-        version_file_path: '/var/opt/gitlab/gitlab-kas/VERSION',
-        version_check_cmd: '/opt/gitlab/embedded/bin/gitlab-kas --version'
-      )
-
-      expect(chef_run.version_file('Create version file for Gitlab KAS')).to notify('runit_service[gitlab-kas]').to(:restart)
-    end
-
-    it 'correctly renders the KAS service run file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-kas/run").with_content(%r{--configuration-file /var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml})
-    end
-
-    it 'correctly renders the KAS log run file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-kas/log/run").with_content(%r{exec svlogd -tt /var/log/gitlab/gitlab-kas})
-    end
-
-    it 'correctly renders the KAS config file' do
-      expect(gitlab_kas_config_yml).to(
-        include(
-          agent: hash_including(
-            listen: {
-              network: 'tcp',
-              address: 'localhost:8150',
-              websocket: true,
-            },
-            kubernetes_api: {
-              listen: {
-                address: 'localhost:8154',
-              },
-              url_path_prefix: '/'
-            }
-          ),
-          observability: {
-            logging: {
-              level: 'info',
-            },
-            usage_reporting_period: '60s'
-          },
-          private_api: {
-            listen: {
-              address: "localhost:8155",
-              authentication_secret_file: "/var/opt/gitlab/gitlab-kas/private_api_authentication_secret_file",
-              network: "tcp"
-            },
-          }
-        )
-      )
-    end
-
-    it 'correctly renders the KAS authentication secret files' do
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-kas/authentication_secret_file").with_content { |content| Base64.strict_decode64(content).size == 32 }
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-kas/private_api_authentication_secret_file").with_content { |content| Base64.strict_decode64(content).size == 32 }
-    end
-
-    it 'sets OWN_PRIVATE_API_URL and SSL_CERT_DIR' do
-      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-kas/env/OWN_PRIVATE_API_URL').with_content('grpc://localhost:8155')
-      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-kas/env/SSL_CERT_DIR').with_content('/opt/gitlab/embedded/ssl/certs/')
-    end
-  end
-
-  context 'with user settings' do
-    let(:api_secret_key) { Base64.strict_encode64('1' * 32) }
-    let(:private_api_secret_key) { Base64.strict_encode64('2' * 32) }
-
-    before do
-      stub_gitlab_rb(
-        external_url: 'https://gitlab.example.com',
-        gitlab_kas: {
-          api_secret_key: api_secret_key,
-          private_api_secret_key: private_api_secret_key,
-          listen_address: 'localhost:5006',
-          listen_websocket: false,
-          metrics_usage_reporting_period: '120',
-          sentry_dsn: 'https://my_key:my_secret@sentry.io/test_project',
-          sentry_environment: 'production',
-          log_level: 'debug',
-        }
-      )
-    end
-
-    it 'correctly renders the KAS service run file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-kas/run").with_content(%r{--configuration-file /var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml})
-    end
-
-    it 'correctly renders the KAS log run file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-kas/log/run").with_content(%r{exec svlogd -tt /var/log/gitlab/gitlab-kas})
-    end
-
-    it 'correctly renders the KAS config file' do
-      expect(gitlab_kas_config_yml).to(
-        include(
-          agent: hash_including(
-            listen: {
-              network: 'tcp',
-              address: 'localhost:5006',
-              websocket: false
-            }
-          ),
-          observability: {
-            logging: {
-              level: 'debug'
-            },
-            usage_reporting_period: '120s',
-            sentry: {
-              dsn: 'https://my_key:my_secret@sentry.io/test_project',
-              environment: 'production'
-            }
-          }
-        )
-      )
-    end
-
-    it 'correctly renders the KAS authentication secret files' do
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-kas/authentication_secret_file").with_content(api_secret_key)
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-kas/private_api_authentication_secret_file").with_content(private_api_secret_key)
-    end
-  end
-
-  describe 'gitlab.yml configuration' do
-    let(:gitlab_yml) { chef_run_load_yaml_template(chef_run, '/var/opt/gitlab/gitlab-rails/etc/gitlab.yml') }
-
-    context 'with defaults' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com'
-        )
-      end
-
-      it 'renders gitlab_kas enabled with default URLs in config/gitlab.yml' do
-        expect(gitlab_yml[:production][:gitlab_kas]).to include(
-          enabled: true,
-          external_url: 'wss://gitlab.example.com/-/kubernetes-agent/',
-          internal_url: 'grpc://localhost:8153',
-          external_k8s_proxy_url: 'https://gitlab.example.com/-/kubernetes-agent/k8s-proxy/'
-        )
-      end
-    end
-
-    context 'when not https' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'http://gitlab.example.com'
-        )
-      end
-
-      it 'has exernal URL with scheme `ws` instead of `wss`' do
-        expect(gitlab_yml[:production][:gitlab_kas]).to include(
-          external_url: 'ws://gitlab.example.com/-/kubernetes-agent/'
-        )
-      end
-    end
-
-    context 'with custom listen addresses' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          gitlab_kas: {
-            enable: true,
-            listen_address: 'custom-address:1234',
-            internal_api_listen_address: 'custom-api-address:9999'
-          }
-        )
-      end
-
-      it 'derives the external URLs from the top level external URL, and the internal URL from the listen address' do
-        expect(gitlab_yml[:production][:gitlab_kas]).to include(
-          enabled: true,
-          external_url: 'wss://gitlab.example.com/-/kubernetes-agent/',
-          internal_url: 'grpc://custom-api-address:9999',
-          external_k8s_proxy_url: 'https://gitlab.example.com/-/kubernetes-agent/k8s-proxy/'
-        )
-      end
-    end
-
-    context 'with explicitly configured URLs' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          gitlab_rails: {
-            gitlab_kas_external_url: 'wss://kas.example.com',
-            gitlab_kas_internal_url: 'grpc://kas.internal',
-            gitlab_kas_external_k8s_proxy_url: 'https://kas.example.com/k8s-proxy'
-          }
-        )
-      end
-
-      it 'uses the explicitly configured URL' do
-        expect(gitlab_yml[:production][:gitlab_kas]).to include(
-          external_url: 'wss://kas.example.com',
-          internal_url: 'grpc://kas.internal',
-          external_k8s_proxy_url: 'https://kas.example.com/k8s-proxy'
-        )
-      end
-    end
-  end
-
-  describe 'logrotate settings' do
-    context 'default values' do
-      it_behaves_like 'configured logrotate service', 'gitlab-kas', 'git', 'git'
-    end
-
-    context 'specified username and group' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          user: {
-            username: 'foo',
-            group: 'bar'
-          }
-        )
-      end
-
-      it_behaves_like 'configured logrotate service', 'gitlab-kas', 'foo', 'bar'
-    end
-  end
-
-  describe 'redis config' do
-    context 'when there is a password' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          gitlab_kas: { enable: true },
-          gitlab_rails: { redis_password: 'the-password' }
-        )
-      end
-
-      it 'writes password_file into the kas config' do
-        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
-          kas_redis_cfg = YAML.safe_load(content)['redis']
-          expect(kas_redis_cfg).to(
-            include(
-              'password_file' => '/var/opt/gitlab/gitlab-kas/redis_password_file'
-            )
-          )
-        }
-      end
-      it 'renders the password file' do
-        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/redis_password_file').with_content('the-password')
-      end
-    end
-
-    context 'when there is no password' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          gitlab_kas: { enable: true }
-        )
-      end
-
-      it 'does not write password_file into the config' do
-        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
-          kas_cfg = YAML.safe_load(content)
-          expect(kas_cfg['redis']).not_to include('password_file')
-        }
-      end
-
-      it 'renders no password file' do
-        expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-kas/redis_password_file")
-      end
-    end
-
-    context 'without sentinel host only' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          gitlab_kas: {
-            enable: true
-          },
-          gitlab_rails: {
-            redis_host: 'the-host'
-          }
-        )
-      end
-
-      it 'renders a single server configuration in to the kas config' do
-        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
-          kas_redis_cfg = YAML.safe_load(content)['redis']
-          expect(kas_redis_cfg).to(
-            include(
-              'network' => 'tcp',
-              'server' => {
-                'address' => 'the-host:6379'
-              }
-            )
-          )
-          expect(kas_redis_cfg).not_to(include('sentinel'))
-        }
-      end
-    end
-
-    context 'without sentinel host and port' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          gitlab_rails: {
-            redis_host: 'the-host',
-            redis_port: 12345,
-          }
-        )
-      end
-
-      it 'renders a single server configuration in to the kas config' do
-        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
-          kas_redis_cfg = YAML.safe_load(content)['redis']
-          expect(kas_redis_cfg).to(
-            include(
-              'network' => 'tcp',
-              'server' => {
-                'address' => 'the-host:12345'
-              }
-            )
-          )
-          expect(kas_redis_cfg).not_to(include('sentinel'))
-        }
-      end
-    end
-
-    context 'without sentinel but with tls enabled' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          gitlab_rails: {
-            redis_host: 'the-host',
-            redis_port: 12345,
-            redis_ssl: true,
-          }
-        )
-      end
-
-      it 'renders a configuration with tls enabled in to the kas config' do
-        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
-          kas_redis_cfg = YAML.safe_load(content)['redis']
-          expect(kas_redis_cfg).to(
-            include(
-              'network' => 'tcp',
-              'tls' => {
-                'enabled' => true
-              },
-              'server' => {
-                'address' => 'the-host:12345'
-              }
-            )
-          )
-        }
-      end
-    end
-
-    context 'with sentinel' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          gitlab_rails: {
-            redis_sentinels: [
-              { host: 'a', port: 1 },
-              { host: 'b', port: 2 },
-              { host: 'c' }
-            ]
-          },
-          redis: {
-            master_name: 'example-redis'
-          }
-        )
-      end
-
-      it 'renders a single server configuration in to the kas config' do
-        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-kas/gitlab-kas-config.yml').with_content { |content|
-          kas_redis_cfg = YAML.safe_load(content)['redis']
-          expect(kas_redis_cfg).to(
-            include(
-              'network' => 'tcp',
-              'sentinel' => {
-                'master_name' => 'example-redis',
-                'addresses' => [
-                  'a:1',
-                  'b:2',
-                  'c:6379'
-                ]
-              }
-            )
-          )
-          expect(kas_redis_cfg).not_to(include('server'))
-        }
-      end
-    end
-  end
-
-  describe 'TLS listen config' do
-    context 'when all certificates and keys are defined' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          gitlab_kas: {
-            enable: true,
-            listen_websocket: false,
-            certificate_file: '/path/to/cert.pem',
-            key_file: '/path/to/key.pem',
-            internal_api_certificate_file: '/path/to/internal-api-cert.pem',
-            internal_api_key_file: '/path/to/internal-api-key.pem',
-            kubernetes_api_certificate_file: '/path/to/kubernetes-api-cert.pem',
-            kubernetes_api_key_file: '/path/to/kubernetes-api-key.pem',
-            private_api_certificate_file: '/path/to/private-api-cert.pem',
-            private_api_key_file: '/path/to/private-api-key.pem'
-          }
-        )
-      end
-
-      it 'correctly renders the KAS config file' do
-        expect(gitlab_kas_config_yml).to(
-          include(
-            agent: hash_including(
-              listen: hash_including(
-                certificate_file: '/path/to/cert.pem',
-                key_file: '/path/to/key.pem'
-              ),
-              kubernetes_api: hash_including(
-                listen: hash_including(
-                  certificate_file: '/path/to/kubernetes-api-cert.pem',
-                  key_file: '/path/to/kubernetes-api-key.pem'
-                )
-              )
-            ),
-            api: {
-              listen: hash_including(
-                certificate_file: '/path/to/internal-api-cert.pem',
-                key_file: '/path/to/internal-api-key.pem'
-              ),
-            },
-            private_api: {
-              listen: hash_including(
-                certificate_file: '/path/to/private-api-cert.pem',
-                key_file: '/path/to/private-api-key.pem'
-              )
-            }
-          )
-        )
-      end
-    end
-
-    context 'when certificate/key bundles are not correctly defined' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          gitlab_kas: {
-            enable: true,
-            certificate_file: '/path/to/file.pem',
-            internal_api_certificate_file: '/path/to/file.pem',
-            kubernetes_api_key_file: '/path/to/file.pem',
-            private_api_key_file: '/path/to/file.pem'
-          }
-        )
-      end
-
-      it 'renders no certificate or key configuration' do
-        expect(gitlab_kas_config_yml).not_to(include('/path/to/file.pem'))
-      end
-    end
-
-    context 'when the certificate/key bundle is defined and websocket tunneling is enabled' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'https://gitlab.example.com',
-          gitlab_kas: {
-            enable: true,
-            listen_websocket: true,
-            certificate_file: '/path/to/cert.pem',
-            key_file: '/path/to/key.pem',
-          }
-        )
-      end
-
-      it 'logs a warning' do
-        expect(chef_run).to run_ruby_block('websocket TLS termination')
-      end
-    end
-  end
-
-  def chef_run_load_yaml_template(chef_run, path)
-    template = chef_run.template(path)
-    file_content = ChefSpec::Renderer.new(chef_run, template).content
-    YAML.safe_load(file_content, [], [], true, symbolize_names: true)
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/database_yml_spec.rb b/spec/chef/recipes/gitlab-rails/database_yml_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2RhdGFiYXNlX3ltbF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/database_yml_spec.rb
+++ /dev/null
@@ -1,277 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  describe 'Database settings' do
-    let(:chef_run) { ChefSpec::SoloRunner.new(step_into: 'templatesymlink').converge('gitlab::default') }
-    let(:database_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/database.yml') }
-    let(:database_yml_file_content) { ChefSpec::Renderer.new(chef_run, database_yml_template).content }
-    let(:database_yml) { YAML.safe_load(database_yml_file_content, [], [], true, symbolize_names: true) }
-    let(:default_content) do
-      {
-        main: {
-          adapter: 'postgresql',
-          application_name: nil,
-          collation: nil,
-          connect_timeout: nil,
-          database: "gitlabhq_production",
-          database_tasks: true,
-          encoding: "unicode",
-          host: "/var/opt/gitlab/postgresql",
-          keepalives: nil,
-          keepalives_count: nil,
-          keepalives_idle: nil,
-          keepalives_interval: nil,
-          load_balancing: {
-            hosts: []
-          },
-          password: nil,
-          port: 5432,
-          prepared_statements: false,
-          socket: nil,
-          sslca: nil,
-          sslcompression: 0,
-          sslmode: nil,
-          sslrootcert: nil,
-          statement_limit: 1000,
-          tcp_user_timeout: nil,
-          username: "gitlab",
-          variables: {
-            statement_timeout: nil
-          }
-        }
-      }
-    end
-
-    before do
-      allow(Gitlab).to receive(:[]).and_call_original
-      allow(File).to receive(:symlink?).and_call_original
-    end
-
-    context 'with default settings' do
-      it 'renders database.yml with main database and default values' do
-        expect(database_yml[:production]).to eq(default_content)
-      end
-    end
-
-    context 'with user provided settings' do
-      context 'via top level db_* keys' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              db_database: 'foobar'
-            }
-          )
-        end
-
-        it 'renders database.yml with user specified values for main database' do
-          expect(database_yml[:production][:main][:database]).to eq('foobar')
-          expect(database_yml[:production][:main][:database_tasks]).to eq(true)
-        end
-      end
-
-      context 'via top level db_* keys and overwritten main:' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              db_database: 'foobar',
-              db_database_tasks: true,
-              databases: {
-                main: {
-                  enable: true,
-                  db_database_tasks: false
-                }
-              }
-            }
-          )
-        end
-
-        it 'renders database.yml with user specified values for main database' do
-          expect(database_yml[:production][:main][:database]).to eq('foobar')
-          expect(database_yml[:production][:main][:database_tasks]).to eq(false)
-        end
-      end
-
-      context "for main database via gitlab_rails['databases']['main'] setting" do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              databases: {
-                main: {
-                  enable: true,
-                  db_database: 'foobar'
-                }
-              }
-            }
-          )
-        end
-
-        it 'renders database.yml with user specified values for main database' do
-          expect(database_yml[:production][:main][:database]).to eq('foobar')
-        end
-      end
-
-      context 'with additional databases specified' do
-        context 'when using the same database as main:' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                databases: {
-                  ci: {
-                    enable: true
-                  }
-                }
-              }
-            )
-          end
-
-          it 'renders database.yml with main stanza first' do
-            expect(database_yml_file_content).to match("production:\n  main:")
-          end
-
-          it 'renders database.yml with both main and additional databases using default values, but disabled database_tasks' do
-            ci_content = default_content[:main].dup
-            ci_content[:database_tasks] = false
-            expected_output = default_content.merge(ci: ci_content)
-
-            expect(database_yml[:production]).to eq(expected_output)
-          end
-        end
-
-        context 'when using a different database to main:' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                databases: {
-                  ci: {
-                    enable: true,
-                    db_database: 'gitlabhq_production_ci'
-                  }
-                }
-              }
-            )
-          end
-
-          it 'renders database.yml with main stanza first' do
-            expect(database_yml_file_content).to match("production:\n  main:")
-          end
-
-          it 'renders database.yml with both main and additional databases using default values, and enabled database_tasks' do
-            ci_content = default_content[:main].dup
-            ci_content[:database] = 'gitlabhq_production_ci'
-            ci_content[:database_tasks] = true
-            expected_output = default_content.merge(ci: ci_content)
-
-            expect(database_yml[:production]).to eq(expected_output)
-          end
-        end
-
-        context 'when db_database_tasks is explicitly enabled in main, but disabled in CI' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                databases: {
-                  main: {
-                    db_connect_timeout: 30,
-                    db_database_tasks: true
-                  },
-                  ci: {
-                    enable: true,
-                    db_host: 'patroni-ci',
-                    db_connect_timeout: 50,
-                    db_database_tasks: false
-                  }
-                }
-              }
-            )
-          end
-
-          it 'renders database.yml with user specified DB settings' do
-            expect(database_yml[:production][:main][:connect_timeout]).to eq(30)
-            expect(database_yml[:production][:main][:database_tasks]).to eq(true)
-            expect(database_yml[:production][:ci][:connect_timeout]).to eq(50)
-            expect(database_yml[:production][:ci][:database_tasks]).to eq(false)
-          end
-        end
-
-        context 'with different settings for main database and additional databases' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                databases: {
-                  main: {
-                    db_connect_timeout: 30,
-                  },
-                  ci: {
-                    enable: true,
-                    db_connect_timeout: 50,
-                    db_database_tasks: false
-                  }
-                }
-              }
-            )
-          end
-
-          it 'renders database.yml with user specified DB settings' do
-            expect(database_yml[:production][:main][:connect_timeout]).to eq(30)
-            expect(database_yml[:production][:main][:database_tasks]).to eq(true)
-            expect(database_yml[:production][:ci][:connect_timeout]).to eq(50)
-            expect(database_yml[:production][:ci][:database_tasks]).to eq(false)
-          end
-        end
-
-        context 'with additional database specified but not enabled' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                databases: {
-                  main: {
-                    db_connect_timeout: 30,
-                  },
-                  ci: {
-                    db_connect_timeout: 50
-                  }
-                }
-              }
-            )
-          end
-
-          it 'renders database.yml without additional database' do
-            expect(database_yml[:production].keys).not_to include('ci')
-          end
-        end
-
-        context 'with invalid additional database specified' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                databases: {
-                  foobar: {
-                    enable: true,
-                    db_database: 'gitlabhq_foobar'
-                  },
-                  johndoe: {
-                    db_database: 'gitlabhq_johndoe'
-                  },
-                  ci: {
-                    enable: true,
-                    db_database: 'gitlabhq_ci'
-                  }
-                }
-              }
-            )
-          end
-
-          it 'raises warning about invalid database' do
-            chef_run
-            expect_logged_warning("Additional database `foobar` not supported in Rails application. It will be ignored.")
-          end
-
-          it 'does not raise warning about invalid database that is not enabled' do
-            chef_run
-            expect(LoggingHelper.messages).not_to include(kind: :warning, message: /Additional database `johndoe` not supported/)
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/backup_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/backup_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvYmFja3VwX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/backup_spec.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-  include_context 'object storage config'
-
-  describe 'Backup settings' do
-    let(:backup_settings) { gitlab_yml[:production][:backup] }
-
-    context 'with default values' do
-      it 'renders with default backup settings' do
-        expect(backup_settings).to eq(
-          archive_permissions: nil,
-          gitaly_backup_path: '/opt/gitlab/embedded/bin/gitaly-backup',
-          keep_time: nil,
-          path: '/var/opt/gitlab/backups',
-          pg_schema: nil,
-          upload: {
-            connection: nil,
-            encryption: nil,
-            encryption_key: nil,
-            multipart_chunk_size: nil,
-            remote_directory: nil,
-            storage_options: {},
-            storage_class: nil
-          }
-        )
-      end
-    end
-
-    context 'with user specified gitaly-backup path set' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            backup_gitaly_backup_path: '/some other/bin/gitaly-backup'
-          }
-        )
-      end
-
-      it 'renders with the user specified gitaly-backup path' do
-        expect(backup_settings).to include(
-          gitaly_backup_path: '/some other/bin/gitaly-backup'
-        )
-      end
-    end
-
-    context 'remote upload', :aggregate_failures do
-      let(:upload_settings) { backup_settings[:upload] }
-
-      context 'with Server-Side Encryption' do
-        context 'with Amazon S3-Managed Keys (SSE-S3)' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                'backup_upload_connection' => aws_connection_hash,
-                'backup_encryption' => 'AES256'
-              }
-            )
-          end
-
-          it 'renders encryption option' do
-            expect(upload_settings[:encryption]).to eq('AES256')
-            expect(upload_settings[:encryption_key]).to be_nil
-            expect(upload_settings[:storage_options]).to eq({})
-          end
-        end
-
-        context 'with Customer Master Keys (CMKs) Stored in AWS Key Management Service (SSE-KMS)' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                'backup_upload_connection' => aws_connection_hash,
-                'backup_upload_storage_options' => aws_storage_options_hash
-              }
-            )
-          end
-
-          it 'renders storage options' do
-            expect(upload_settings[:encryption]).to be_nil
-            expect(upload_settings[:encryption_key]).to be_nil
-            expect(upload_settings[:storage_options]).to eq(aws_storage_options)
-          end
-        end
-
-        context 'with Customer-Provided Keys (SSE-C)' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                'backup_upload_connection' => aws_connection_hash,
-                'backup_encryption' => 'AES256',
-                'backup_encryption_key' => 'A12345678'
-              }
-            )
-          end
-
-          it 'renders encryption and encryption key options' do
-            expect(upload_settings[:encryption]).to eq('AES256')
-            expect(upload_settings[:encryption_key]).to eq('A12345678')
-            expect(upload_settings[:storage_options]).to eq({})
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/consul_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/consul_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvY29uc3VsX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/consul_spec.rb
+++ /dev/null
@@ -1,123 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'Consul settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml without Consul settings' do
-        expect(gitlab_yml[:production][:consul]).to eq(
-          api_url: ""
-        )
-      end
-    end
-
-    context 'with user specified values' do
-      context 'when bundled consul is enabled' do
-        before do
-          stub_gitlab_rb(
-            consul: {
-              enable: true
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with default Consul settings' do
-          expect(gitlab_yml[:production][:consul]).to eq(
-            api_url: 'http://localhost:8500'
-          )
-        end
-      end
-
-      context 'when Consul is running on non-default location' do
-        context 'when setting client_addr and ports' do
-          before do
-            stub_gitlab_rb(
-              consul: {
-                enable: true,
-                configuration: {
-                  client_addr: '10.0.0.1',
-                  ports: {
-                    http: 1234
-                  }
-                }
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with specified Consul settings' do
-            expect(gitlab_yml[:production][:consul]).to eq(
-              api_url: 'http://10.0.0.1:1234'
-            )
-          end
-        end
-
-        context 'when setting addresses and ports' do
-          before do
-            stub_gitlab_rb(
-              consul: {
-                enable: true,
-                configuration: {
-                  addresses: {
-                    http: '10.0.1.2',
-                  },
-                  ports: {
-                    http: 1234
-                  }
-                }
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with specified Consul settings' do
-            expect(gitlab_yml[:production][:consul]).to eq(
-              api_url: 'http://10.0.1.2:1234'
-            )
-          end
-        end
-
-        context 'when http port is disabled via negative port number' do
-          before do
-            stub_gitlab_rb(
-              consul: {
-                enable: true,
-                configuration: {
-                  addresses: {
-                    https: '10.0.1.2',
-                  },
-                  ports: {
-                    http: -1,
-                    https: 1234
-                  }
-                }
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with specified Consul settings' do
-            expect(gitlab_yml[:production][:consul]).to eq(
-              api_url: 'https://10.0.1.2:1234'
-            )
-          end
-        end
-      end
-
-      context 'when tls is enabled' do
-        before do
-          stub_gitlab_rb(
-            consul: {
-              enable: true,
-              use_tls: true
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with specified Consul settings' do
-          expect(gitlab_yml[:production][:consul]).to eq(
-            api_url: 'https://localhost:8501'
-          )
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/cron_jobs_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/cron_jobs_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvY3Jvbl9qb2JzX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/cron_jobs_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'cron settings' do
-    context 'with default values' do
-      it "renders gitlab.yml without any cron settings" do
-        expect(gitlab_yml[:production][:cron_jobs]).to be nil
-      end
-    end
-
-    context 'with user specified values' do
-      using RSpec::Parameterized::TableSyntax
-
-      where(:gitlab_yml_setting, :gitlab_rb_setting) do
-        'admin_email_worker'                                     | 'admin_email_worker_cron'
-        'analytics_devops_adoption_create_all_snapshots_worker'  | 'analytics_devops_adoption_create_all_snapshots_worker'
-        'analytics_usage_trends_count_job_trigger_worker'        | 'analytics_usage_trends_count_job_trigger_worker_cron'
-        'ci_archive_traces_cron_worker'                          | 'ci_archive_traces_cron_worker_cron'
-        'ci_platform_metrics_update_cron_worker'                 | 'ci_platform_metrics_update_cron_worker'
-        'elastic_index_bulk_cron_worker'                         | 'elastic_index_bulk_cron'
-        'environments_auto_stop_cron_worker'                     | 'environments_auto_stop_cron_worker_cron'
-        'expire_build_artifacts_worker'                          | 'expire_build_artifacts_worker_cron'
-        'geo_file_download_dispatch_worker'                      | 'geo_file_download_dispatch_worker_cron'
-        'geo_migrated_local_files_clean_up_worker'               | 'geo_migrated_local_files_clean_up_worker_cron'
-        'geo_prune_event_log_worker'                             | 'geo_prune_event_log_worker_cron'
-        'geo_repository_sync_worker'                             | 'geo_repository_sync_worker_cron'
-        'geo_repository_verification_primary_batch_worker'       | 'geo_repository_verification_primary_batch_worker_cron'
-        'geo_repository_verification_secondary_scheduler_worker' | 'geo_repository_verification_secondary_scheduler_worker_cron'
-        'geo_secondary_registry_consistency_worker'              | 'geo_secondary_registry_consistency_worker'
-        'geo_secondary_usage_data_cron_worker'                   | 'geo_secondary_usage_data_cron_worker'
-        'historical_data_worker'                                 | 'historical_data_worker_cron'
-        'ldap_group_sync_worker'                                 | 'ldap_group_sync_worker_cron'
-        'ldap_sync_worker'                                       | 'ldap_sync_worker_cron'
-        'member_invitation_reminder_emails_worker'               | 'member_invitation_reminder_emails_worker_cron'
-        'pages_domain_removal_cron_worker'                       | 'pages_domain_removal_cron_worker'
-        'pages_domain_ssl_renewal_cron_worker'                   | 'pages_domain_ssl_renewal_cron_worker'
-        'pages_domain_verification_cron_worker'                  | 'pages_domain_verification_cron_worker'
-        'personal_access_tokens_expired_notification_worker'     | 'personal_access_tokens_expired_notification_worker_cron'
-        'personal_access_tokens_expiring_worker'                 | 'personal_access_tokens_expiring_worker_cron'
-        'pipeline_schedule_worker'                               | 'pipeline_schedule_worker_cron'
-        'pseudonymizer_worker'                                   | 'pseudonymizer_worker_cron'
-        'remove_unaccepted_member_invites_worker'                | 'remove_unaccepted_member_invites_cron_worker'
-        'repository_archive_cache_worker'                        | 'repository_archive_cache_worker_cron'
-        'repository_check_worker'                                | 'repository_check_worker_cron'
-        'schedule_migrate_external_diffs_worker'                 | 'schedule_migrate_external_diffs_worker_cron'
-        'stuck_ci_jobs_worker'                                   | 'stuck_ci_jobs_worker_cron'
-        'user_status_cleanup_batch_worker'                       | 'user_status_cleanup_batch_worker_cron'
-        'namespaces_in_product_marketing_emails_worker'          | 'namespaces_in_product_marketing_emails_worker_cron'
-        'ssh_keys_expired_notification_worker'                   | 'ssh_keys_expired_notification_worker_cron'
-        'ssh_keys_expiring_soon_notification_worker'             | 'ssh_keys_expiring_soon_notification_worker_cron'
-        'loose_foreign_keys_cleanup_worker'                      | 'loose_foreign_keys_cleanup_worker_cron'
-      end
-
-      with_them do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              gitlab_rb_setting => '1 2 3 4 5'
-            }.transform_keys(&:to_sym)
-          )
-        end
-
-        it "renders gitlab.yml with user specified cron settings" do
-          config = gitlab_yml[:production][:cron_jobs][gitlab_yml_setting.to_sym][:cron]
-          expect(config).to eq '1 2 3 4 5'
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/extra_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/extra_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvZXh0cmFfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/extra_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'extra settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml without extra settings' do
-        expect(gitlab_yml[:production][:extra]).to be nil
-      end
-    end
-
-    context 'with user specified values' do
-      describe 'matomo settings' do
-        context 'with just matomo_url specified' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                extra_matomo_url: 'http://foobar.com'
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with default values for other matomo settings' do
-            expect(gitlab_yml[:production][:extra][:matomo_url]).to eq('http://foobar.com')
-            expect(gitlab_yml[:production][:extra][:matomo_site_id]).to be nil
-            expect(gitlab_yml[:production][:extra][:matomo_disable_cookies]).to be nil
-          end
-        end
-
-        context 'with all settings specified' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                extra_matomo_url: 'http://foobar.com',
-                extra_matomo_site_id: 'foobar',
-                extra_matomo_disable_cookies: true
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with specified matomo settings' do
-            expect(gitlab_yml[:production][:extra][:matomo_url]).to eq('http://foobar.com')
-            expect(gitlab_yml[:production][:extra][:matomo_site_id]).to eq('foobar')
-            expect(gitlab_yml[:production][:extra][:matomo_disable_cookies]).to be true
-          end
-        end
-      end
-
-      describe 'one_trust_id setting' do
-        before do
-          stub_gitlab_rb(gitlab_rails: { extra_one_trust_id: '0000-0000-test' })
-        end
-
-        it 'renders gitlab.yml with the provided value' do
-          expect(gitlab_yml[:production][:extra][:one_trust_id]).to eq('0000-0000-test')
-        end
-      end
-
-      describe 'google_tag_manager_nonce_id setting' do
-        before do
-          stub_gitlab_rb(gitlab_rails: { extra_google_tag_manager_nonce_id: '0000-0000' })
-        end
-
-        it 'renders gitlab.yml with the provided value' do
-          expect(gitlab_yml[:production][:extra][:google_tag_manager_nonce_id]).to eq('0000-0000')
-        end
-      end
-
-      context 'bizible' do
-        context 'when true' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: { bizible: true }
-            )
-          end
-
-          it 'should set bizible to true' do
-            expect(chef_run).to create_templatesymlink('Create a gitlab.yml and create a symlink to Rails root').with_variables(
-              hash_including(
-                'bizible' => true
-              )
-            )
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/forti_authenticator_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/forti_authenticator_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvZm9ydGlfYXV0aGVudGljYXRvcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/forti_authenticator_spec.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  using RSpec::Parameterized::TableSyntax
-  include_context 'gitlab-rails'
-
-  describe 'FortiAuthenticator settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml with FortiAuthenticator disabled' do
-        expect(gitlab_yml[:production][:forti_authenticator]).to eq(
-          enabled: false
-        )
-      end
-    end
-
-    context 'with user specified values' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            forti_authenticator_enabled: true,
-            forti_authenticator_host: 'forti_authenticator.example.com',
-            forti_authenticator_port: 444,
-            forti_authenticator_username: 'janedoe',
-            forti_authenticator_access_token: '123s3cr3t456'
-          }
-        )
-      end
-
-      it 'renders gitlab.yml with user specified values' do
-        expect(gitlab_yml[:production][:forti_authenticator]).to eq(
-          enabled: true,
-          host: 'forti_authenticator.example.com',
-          port: 444,
-          username: 'janedoe',
-          access_token: '123s3cr3t456'
-        )
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/forti_token_cloud_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/forti_token_cloud_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvZm9ydGlfdG9rZW5fY2xvdWRfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/forti_token_cloud_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  using RSpec::Parameterized::TableSyntax
-  include_context 'gitlab-rails'
-
-  describe 'FortiToken Cloud settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml with FortiAuthenticator disabled' do
-        expect(gitlab_yml[:production][:forti_token_cloud]).to eq(
-          enabled: false
-        )
-      end
-    end
-
-    context 'with user specified values' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            forti_token_cloud_enabled: true,
-            forti_token_cloud_client_id: 'forti_token_cloud_client_id',
-            forti_token_cloud_client_secret: '123s3cr3t456'
-          }
-        )
-      end
-
-      it 'renders gitlab.yml with user specified values' do
-        expect(gitlab_yml[:production][:forti_token_cloud]).to eq(
-          enabled: true,
-          client_id: 'forti_token_cloud_client_id',
-          client_secret: '123s3cr3t456'
-        )
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/geo_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/geo_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvZ2VvX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/geo_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'Geo settings' do
-    context 'with default configuration' do
-      it 'renders gitlab.yml with default values' do
-        expect(gitlab_yml[:production][:geo]).to eq(
-          node_name: nil,
-          registry_replication: {
-            enabled: nil,
-            primary_api_url: nil
-          }
-        )
-      end
-    end
-
-    context 'with user specified configuration' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            geo_node_name: 'foobar',
-            geo_registry_replication_enabled: true,
-            geo_registry_replication_primary_api_url: 'https://example.com'
-          }
-        )
-      end
-
-      it 'renders gitlab.yml with user specified values' do
-        expect(gitlab_yml[:production][:geo]).to eq(
-          node_name: 'foobar',
-          registry_replication: {
-            enabled: true,
-            primary_api_url: 'https://example.com'
-          }
-        )
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/gitaly_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/gitaly_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvZ2l0YWx5X3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/gitaly_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'Gitaly settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml without Gitaly token set' do
-        expect(gitlab_yml[:production][:gitaly]).to eq(
-          client_path: "/opt/gitlab/embedded/bin",
-          token: ""
-        )
-      end
-    end
-
-    context 'with user specified token' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            gitaly_token: 'token123456'
-          }
-        )
-      end
-
-      it 'renders gitlab.yml with user specified token' do
-        expect(gitlab_yml[:production][:gitaly]).to eq(
-          client_path: "/opt/gitlab/embedded/bin",
-          token: 'token123456'
-        )
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/gitlab_shell_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/gitlab_shell_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvZ2l0bGFiX3NoZWxsX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/gitlab_shell_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'GitLab Shell settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml with default values' do
-        expect(gitlab_yml[:production][:gitlab_shell]).to eq(
-          authorized_keys_file: '/var/opt/gitlab/.ssh/authorized_keys',
-          git_timeout: 10800,
-          hooks_path: '/opt/gitlab/embedded/service/gitlab-shell/hooks/',
-          path: '/opt/gitlab/embedded/service/gitlab-shell/',
-          receive_pack: nil,
-          ssh_port: nil,
-          upload_pack: nil
-        )
-      end
-    end
-
-    context 'with user specified values' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            gitlab_shell_git_timeout: 99999,
-            gitlab_shell_hooks_path: '/tmp/hooks',
-            gitlab_shell_path: '/tmp/shell',
-            gitlab_shell_upload_pack: true,
-            gitlab_shell_receive_pack: true,
-            gitlab_shell_ssh_port: 123,
-          }
-        )
-      end
-
-      it 'renders gitlab.yml with user specified values' do
-        expect(gitlab_yml[:production][:gitlab_shell]).to eq(
-          authorized_keys_file: '/var/opt/gitlab/.ssh/authorized_keys',
-          git_timeout: 99999,
-          hooks_path: '/tmp/hooks',
-          path: '/tmp/shell',
-          receive_pack: true,
-          ssh_port: 123,
-          upload_pack: true
-        )
-      end
-
-      describe 'authorized_keys_file' do
-        context 'when explicitly configured' do
-          before do
-            stub_gitlab_rb(
-              gitlab_shell: {
-                auth_file: '/tmp/authorized_keys'
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with user specified path' do
-            expect(gitlab_yml[:production][:gitlab_shell][:authorized_keys_file]).to eq('/tmp/authorized_keys')
-          end
-        end
-
-        context 'when the user home directory is specified' do
-          before do
-            stub_gitlab_rb(
-              user: {
-                home: '/tmp/user'
-              }
-            )
-          end
-
-          it 'defaults to auth file within specified home directory' do
-            expect(gitlab_yml[:production][:gitlab_shell][:authorized_keys_file]).to eq('/tmp/user/.ssh/authorized_keys')
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/gitlab_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/gitlab_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvZ2l0bGFiX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/gitlab_spec.rb
+++ /dev/null
@@ -1,207 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'GitLab Application Settings' do
-    describe 'Content Security Policy settings' do
-      context 'with default configuration' do
-        it 'renders gitlab.yml without content security policy settings' do
-          expect(gitlab_yml[:production][:gitlab][:content_security_policy]).to be nil
-        end
-      end
-
-      context 'with user specified configuration' do
-        context 'with content security policy disabled' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                content_security_policy: {
-                  enabled: false
-                }
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with content security policy disabled' do
-            expect(gitlab_yml[:production][:gitlab][:content_security_policy]).to eq(
-              enabled: false
-            )
-          end
-        end
-
-        context 'for various content security policy related settings' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                content_security_policy: {
-                  enabled: true,
-                  report_only: true,
-                  directives: {
-                    default_src: "'self'",
-                    script_src: "'self' http://recaptcha.net",
-                    worker_src: "'self'"
-                  }
-                }
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with user specified values for content security policy' do
-            expect(gitlab_yml[:production][:gitlab][:content_security_policy]).to eq(
-              enabled: true,
-              report_only: true,
-              directives: {
-                default_src: "'self'",
-                script_src: "'self' http://recaptcha.net",
-                worker_src: "'self'"
-              }
-            )
-          end
-        end
-      end
-    end
-
-    describe 'SMIME email settings' do
-      context 'with default configuration' do
-        it 'renders gitlab.yml with SMIME email settings disabled' do
-          expect(gitlab_yml[:production][:gitlab][:email_smime]).to eq(
-            enabled: false,
-            cert_file: '/etc/gitlab/ssl/gitlab_smime.crt',
-            key_file: '/etc/gitlab/ssl/gitlab_smime.key',
-            ca_certs_file: nil
-          )
-        end
-      end
-
-      context 'with user specified configuration' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              gitlab_email_smime_enabled: true,
-              gitlab_email_smime_key_file: '/etc/gitlab/ssl/custom_gitlab_smime.key',
-              gitlab_email_smime_cert_file: '/etc/gitlab/ssl/custom_gitlab_smime.crt',
-              gitlab_email_smime_ca_certs_file: '/etc/gitlab/ssl/custom_gitlab_smime_cas.crt'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with user specified values for SMIME email settings' do
-          expect(gitlab_yml[:production][:gitlab][:email_smime]).to eq(
-            enabled: true,
-            cert_file: '/etc/gitlab/ssl/custom_gitlab_smime.crt',
-            key_file: '/etc/gitlab/ssl/custom_gitlab_smime.key',
-            ca_certs_file: '/etc/gitlab/ssl/custom_gitlab_smime_cas.crt'
-          )
-        end
-      end
-    end
-
-    describe 'Allowed hosts' do
-      context 'with default configuration' do
-        it 'does not render allowed_hosts in gitlab.yml' do
-          expect(gitlab_yml[:production][:gitlab][:allowed_hosts]).to be nil
-        end
-      end
-
-      context 'with user specified configuration' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              allowed_hosts: ['example.com', 'foobar.com']
-            }
-          )
-        end
-
-        it 'renders specified allowed_hosts in gitlab.yml' do
-          expect(gitlab_yml[:production][:gitlab][:allowed_hosts]).to eq(['example.com', 'foobar.com'])
-        end
-      end
-    end
-
-    describe 'Application settings cache expiry' do
-      context 'with default configuration' do
-        it 'renders gitlab.yml without application settings cache expiry' do
-          expect(gitlab_yml[:production][:gitlab][:application_settings_cache_seconds]).to be nil
-        end
-      end
-
-      context 'with user specified configuration' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              application_settings_cache_seconds: 30
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with user specified value for application settings cache expiry' do
-          expect(gitlab_yml[:production][:gitlab][:application_settings_cache_seconds]).to eq(30)
-        end
-      end
-    end
-
-    describe 'Maximum Request Duration' do
-      context 'with default configuration' do
-        it 'renders gitlab.yml with default value for maximum request duration' do
-          expect(gitlab_yml[:production][:gitlab][:max_request_duration_seconds]).to eq(57)
-        end
-
-        context 'with default configuration and Unicorn as webserver' do
-          before do
-            stub_gitlab_rb(
-              puma: { enable: false },
-              unicorn: { enable: true }
-            )
-          end
-
-          it 'renders gitlab.yml with default value for maximum request duration' do
-            expect(gitlab_yml[:production][:gitlab][:max_request_duration_seconds]).to eq(57)
-          end
-        end
-      end
-
-      context 'with user specified configuration' do
-        context 'for worker_timeout' do
-          using RSpec::Parameterized::TableSyntax
-
-          where(:web_worker, :configured_timeout, :expected_duration) do
-            :unicorn | 30   | 29
-            :unicorn | "30" | 29
-            :puma    | 120  | 114
-          end
-
-          with_them do
-            before do
-              stub_gitlab_rb(
-                unicorn: { enable: web_worker == :unicorn, worker_timeout: configured_timeout },
-                puma: { enable: web_worker == :puma, worker_timeout: configured_timeout }
-              )
-            end
-
-            it 'renders gitlab.yml with maximum request duration computed from configured worker timeout' do
-              expect(gitlab_yml[:production][:gitlab][:max_request_duration_seconds]).to eq(expected_duration)
-            end
-          end
-        end
-
-        context 'for max_request_duration_seconds' do
-          before do
-            stub_gitlab_rb(
-              puma: {
-                worker_timeout: 120
-              },
-              gitlab_rails: {
-                max_request_duration_seconds: 100
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with user specified value for maximum request duration' do
-            expect(gitlab_yml[:production][:gitlab][:max_request_duration_seconds]).to eq(100)
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/incoming_email_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/incoming_email_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvaW5jb21pbmdfZW1haWxfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/incoming_email_spec.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'incoming email settings' do
-    describe 'log file location' do
-      context 'with default values' do
-        it 'renders gitlab.yml with default value for mailroom log file path' do
-          expect(gitlab_yml[:production][:incoming_email][:log_path]).to eq '/var/log/gitlab/mailroom/mail_room_json.log'
-        end
-      end
-
-      context 'with custom log file path specified' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              incoming_email_log_file: '/my/custom/log/file'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with specified value for mailroom log file path' do
-          expect(gitlab_yml[:production][:incoming_email][:log_path]).to eq '/my/custom/log/file'
-        end
-      end
-
-      context 'with custom log directory specified' do
-        before do
-          stub_gitlab_rb(
-            mailroom: {
-              log_directory: '/my/custom/directory'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with default log file name inside specified directory' do
-          expect(gitlab_yml[:production][:incoming_email][:log_path]).to eq '/my/custom/directory/mail_room_json.log'
-        end
-      end
-
-      context 'with custom log file and directory specified' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              incoming_email_log_file: '/foobar.log'
-            },
-            mailroom: {
-              log_directory: '/my/custom/directory'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with specified log file path irrespective of specified directory' do
-          expect(gitlab_yml[:production][:incoming_email][:log_path]).to eq '/foobar.log'
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/ldap_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/ldap_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvbGRhcF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/ldap_spec.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'LDAP server configuration' do
-    context 'with default configuration' do
-      it 'renders gitlab.yml with LDAP disabled' do
-        expect(gitlab_yml[:production][:ldap]).to eq(
-          enabled: false,
-          active_directory: nil,
-          admin_group: nil,
-          allow_username_or_email_login: nil,
-          base: nil,
-          bind_dn: nil,
-          group_base: nil,
-          host: nil,
-          lowercase_usernames: nil,
-          method: nil,
-          password: nil,
-          port: nil,
-          prevent_ldap_sign_in: false,
-          sync_ssh_keys: nil,
-          sync_time: nil,
-          uid: nil,
-          user_filter: nil
-        )
-      end
-    end
-
-    context 'with user specified configuration' do
-      context 'when using new syntax for LDAP servers' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              ldap_enabled: true,
-              prevent_ldap_sign_in: false,
-              ldap_sync_time: 20,
-              ldap_servers: {
-                main: {
-                  label: 'LDAP Primary',
-                  host: 'primary.ldap',
-                  port: 389,
-                  uid: 'uid',
-                  encryption: 'plain',
-                  password: 's3cr3t',
-                  base: 'dc=example,dc=com',
-                  user_filter: ''
-                },
-                secondary: {
-                  label: 'LDAP Secondary',
-                  host: 'secondary.ldap',
-                  port: 389,
-                  uid: 'uid',
-                  encryption: 'plain',
-                  bind_dn: 'dc=example,dc=com',
-                  password: 's3cr3t',
-                  smartcard_auth: 'required',
-                  base: '',
-                  user_filter: '',
-                }
-              }
-            })
-        end
-
-        it 'renders gitlab.yml with user specified values' do
-          expected_output = {
-            enabled: true,
-            prevent_ldap_sign_in: false,
-            sync_time: 20,
-            servers: {
-              main: {
-                label: 'LDAP Primary',
-                host: 'primary.ldap',
-                port: 389,
-                uid: 'uid',
-                encryption: 'plain',
-                password: 's3cr3t',
-                base: 'dc=example,dc=com',
-                user_filter: ''
-              },
-              secondary: {
-                label: 'LDAP Secondary',
-                host: 'secondary.ldap',
-                port: 389,
-                uid: 'uid',
-                encryption: 'plain',
-                bind_dn: 'dc=example,dc=com',
-                password: 's3cr3t',
-                smartcard_auth: 'required',
-                base: '',
-                user_filter: '',
-              }
-            }
-          }
-
-          expect(gitlab_yml[:production][:ldap]).to eq(expected_output)
-        end
-      end
-
-      context 'when using old syntax with single LDAP server' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              ldap_enabled: true,
-              prevent_ldap_sign_in: false,
-              ldap_host: 'primary.ldap',
-              ldap_port: 389,
-              ldap_uid: 'uid',
-              ldap_password: 's3cr3t',
-              ldap_base: 'dc=example,dc=com',
-              ldap_user_filter: '',
-              ldap_method: 'plain',
-              ldap_bind_dn: 'foobar',
-              ldap_active_directory: 'asdf',
-              ldap_allow_username_or_email_login: false,
-              ldap_lowercase_usernames: true,
-              ldap_group_base: 'dc-example.com',
-              ldap_admin_group: 'foo',
-              ldap_sync_ssh_keys: true,
-              ldap_sync_time: 10
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with user specified values' do
-          expected_output = {
-            enabled: true,
-            prevent_ldap_sign_in: false,
-            sync_time: 10,
-            host: 'primary.ldap',
-            port: 389,
-            uid: 'uid',
-            password: 's3cr3t',
-            base: 'dc=example,dc=com',
-            user_filter: '',
-            method: 'plain',
-            bind_dn: 'foobar',
-            active_directory: 'asdf',
-            allow_username_or_email_login: false,
-            lowercase_usernames: true,
-            group_base: 'dc-example.com',
-            admin_group: 'foo',
-            sync_ssh_keys: 'true',
-          }
-
-          expect(gitlab_yml[:production][:ldap]).to eq(expected_output)
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/mattermost_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/mattermost_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvbWF0dGVybW9zdF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/mattermost_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  using RSpec::Parameterized::TableSyntax
-  include_context 'gitlab-rails'
-
-  describe 'Mattermost settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml with Mattermost disabled' do
-        expect(gitlab_yml[:production][:mattermost]).to eq(
-          enabled: false,
-          host: nil
-        )
-      end
-    end
-
-    context 'with user specified values' do
-      context 'when mattermost_external_url is specified' do
-        before do
-          stub_gitlab_rb(
-            mattermost_external_url: 'http://mattermost.example.com'
-          )
-        end
-
-        it 'renders gitlab.yml with specified Mattermost settings' do
-          expect(gitlab_yml[:production][:mattermost]).to eq(
-            enabled: true,
-            host: 'http://mattermost.example.com'
-          )
-        end
-      end
-
-      context 'when mattermost is running on a different machine' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              mattermost_host: 'http://mattermost.example.com'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with specified Mattermost settings' do
-          expect(gitlab_yml[:production][:mattermost]).to eq(
-            enabled: true,
-            host: 'http://mattermost.example.com'
-          )
-        end
-      end
-
-      context 'when both gitlab-rails.mattermost_host and mattermost_external_url is set' do
-        before do
-          stub_gitlab_rb(
-            mattermost_external_url: 'http://foobar.com',
-            gitlab_rails: {
-              mattermost_host: 'http://mattermost.example.com'
-            }
-          )
-        end
-
-        it 'mattermost_external_url is used' do
-          expect(gitlab_yml[:production][:mattermost][:host]).to eq('http://foobar.com')
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/monitoring_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/monitoring_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvbW9uaXRvcmluZ19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/monitoring_spec.rb
+++ /dev/null
@@ -1,143 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'Monitoring settings' do
-    context 'with default configuration' do
-      it 'renders gitlab.yml with default values' do
-        expect(gitlab_yml[:production][:monitoring]).to eq(
-          ip_whitelist: %w[127.0.0.0/8 ::1/128],
-          sidekiq_exporter: {
-            enabled: true,
-            log_enabled: false,
-            port: 8082,
-            address: '127.0.0.1'
-          },
-          sidekiq_health_checks: {
-            enabled: true,
-            log_enabled: false,
-            port: 8082,
-            address: '127.0.0.1'
-          },
-          web_exporter: {
-            enabled: false,
-            port: 8083,
-            address: '127.0.0.1'
-          }
-        )
-      end
-    end
-
-    context 'with user specified configuration' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            monitoring_whitelist: %w[1.0.0.0 2.0.0.0],
-          }
-        )
-      end
-
-      it 'renders gitlab.rb with user specified values' do
-        expect(gitlab_yml[:production][:monitoring][:ip_whitelist]).to eq(%w[1.0.0.0 2.0.0.0])
-      end
-
-      context 'when sidekiq exports logs' do
-        before do
-          stub_gitlab_rb(
-            sidekiq: {
-              metrics_enabled: false,
-              exporter_log_enabled: true,
-              listen_address: '1.2.3.4',
-              listen_port: 1234
-            }
-          )
-        end
-
-        it 'renders gitlab.rb with user specified values' do
-          expect(gitlab_yml[:production][:monitoring][:sidekiq_exporter]).to eq(
-            enabled: false,
-            log_enabled: true,
-            port: 1234,
-            address: '1.2.3.4'
-          )
-        end
-      end
-
-      context 'when sidekiq exporter uses custom settings' do
-        let(:exporter_settings) do
-          {
-            sidekiq: {
-              metrics_enabled: false,
-              exporter_log_enabled: true,
-              listen_address: '1.2.2.2',
-              listen_port: 2222
-            }
-          }
-        end
-
-        before do
-          stub_gitlab_rb(exporter_settings.merge(health_checks_settings))
-        end
-
-        context 'when health-checks are not configured manually' do
-          let(:health_checks_settings) { {} }
-
-          it 'defaults health-checks to exporter settings' do
-            expect(gitlab_yml[:production][:monitoring][:sidekiq_health_checks]).to eq(
-              enabled: false,
-              log_enabled: true,
-              port: 2222,
-              address: '1.2.2.2'
-            )
-          end
-        end
-
-        context 'when health-checks are configured manually' do
-          let(:health_checks_settings) do
-            {
-              sidekiq: {
-                health_checks_enabled: true,
-                health_checks_log_enabled: true,
-                health_checks_listen_address: '1.2.3.5',
-                health_checks_listen_port: 1235
-              }
-            }
-          end
-
-          it 'uses custom health-checks settings' do
-            expect(gitlab_yml[:production][:monitoring][:sidekiq_health_checks]).to eq(
-              enabled: true,
-              log_enabled: true,
-              port: 1235,
-              address: '1.2.3.5'
-            )
-          end
-        end
-      end
-
-      context 'when webserver exports are enabled' do
-        context 'with Puma' do
-          before do
-            stub_gitlab_rb(
-              puma: {
-                enable: true,
-                exporter_enabled: false,
-                exporter_address: '1.2.3.4',
-                exporter_port: 1234
-              }
-            )
-          end
-
-          it 'renders gitlab.rb with user specified values' do
-            expect(gitlab_yml[:production][:monitoring][:web_exporter]).to eq(
-              enabled: false,
-              port: 1234,
-              address: '1.2.3.4'
-            )
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/object_storage_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/object_storage_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvb2JqZWN0X3N0b3JhZ2Vfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/object_storage_spec.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'object storage settings' do
-    include_context 'object storage config'
-
-    describe 'consolidated object storage settings' do
-      context 'with default values' do
-        it 'renders gitlab.yml without consolidated object storage settings' do
-          expect(gitlab_yml[:production][:object_store]).to be_nil
-        end
-      end
-
-      context 'with user specified values' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              object_store: {
-                enabled: true,
-                connection: aws_connection_hash,
-                storage_options: aws_storage_options_hash,
-                objects: object_config,
-                proxy_download: true,
-              }
-            }
-          )
-        end
-
-        it 'generates gitlab.yml properly with specified values' do
-          expect(gitlab_yml[:production][:object_store]).to eq(
-            enabled: true,
-            connection: aws_connection_data,
-            storage_options: aws_storage_options,
-            objects: object_config,
-            proxy_download: true
-          )
-        end
-      end
-    end
-
-    describe 'individual object storage settings' do
-      # Parameters are:
-      # 1. Component name
-      # 2. Default settings deviating from general pattern
-      # 3. Whether Workhorse acceleration is in place - decides whether to
-      #    include background_upload, direct_upload, proxy_download etc.
-      include_examples 'renders object storage settings in gitlab.yml', 'artifacts'
-      include_examples 'renders object storage settings in gitlab.yml', 'uploads'
-      include_examples 'renders object storage settings in gitlab.yml', 'external_diffs', { remote_directory: 'external-diffs' }
-      include_examples 'renders object storage settings in gitlab.yml', 'lfs', { remote_directory: 'lfs-objects' }
-      include_examples 'renders object storage settings in gitlab.yml', 'packages'
-      include_examples 'renders object storage settings in gitlab.yml', 'dependency_proxy'
-      include_examples 'renders object storage settings in gitlab.yml', 'terraform_state', { remote_directory: 'terraform' }, false
-      include_examples 'renders object storage settings in gitlab.yml', 'ci_secure_files', { remote_directory: 'ci-secure-files' }, false
-      include_examples 'renders object storage settings in gitlab.yml', 'pages', {}, false
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/omniauth_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/omniauth_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvb21uaWF1dGhfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/omniauth_spec.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  using RSpec::Parameterized::TableSyntax
-  include_context 'gitlab-rails'
-
-  describe 'omniauth settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml with omniauth allow_single_sign_on set to saml' do
-        expect(gitlab_yml[:production][:omniauth][:allow_single_sign_on]).to eq(['saml'])
-      end
-
-      it 'renders gitlab.yml with other omniauth settings set to nil' do
-        expect(gitlab_yml[:production][:omniauth]).to eq(
-          allow_bypass_two_factor: nil,
-          auto_link_ldap_user: nil,
-          auto_link_saml_user: nil,
-          auto_link_user: nil,
-          allow_single_sign_on: ['saml'],
-          auto_sign_in_with_provider: nil,
-          block_auto_created_users: nil,
-          enabled: nil,
-          external_providers: nil,
-          providers: nil
-        )
-      end
-    end
-
-    context 'with user specified values' do
-      describe 'saml_message_max_byte_size' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              omniauth_saml_message_max_byte_size: 100000
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with custom value' do
-          expect(gitlab_yml[:production][:omniauth][:saml_message_max_byte_size]).to eq(100000)
-        end
-      end
-
-      describe 'settings that take in boolean values' do
-        where(:gitlab_yml_setting, :gitlab_rb_setting) do
-          'enabled'                  | 'omniauth_enabled'
-          'block_auto_created_users' | 'omniauth_block_auto_created_users'
-          'auto_link_ldap_user'      | 'omniauth_auto_link_ldap_user'
-          'auto_link_saml_user'      | 'omniauth_auto_link_saml_user'
-        end
-
-        with_them do
-          context "with #{params[:gitlab_rb_setting]} set to true" do
-            before do
-              stub_gitlab_rb(
-                gitlab_rails: {
-                  gitlab_rb_setting => true
-                }.transform_keys(&:to_sym)
-              )
-            end
-
-            it "renders gitlab.yml with #{params[:gitlab_yml_setting]} set to true" do
-              expect(gitlab_yml[:production][:omniauth][gitlab_yml_setting.to_sym]).to be true
-            end
-          end
-        end
-      end
-
-      describe 'settings that take in multiple types of values' do
-        where(:gitlab_yml_setting, :gitlab_rb_setting) do
-          'allow_single_sign_on'       | 'omniauth_allow_single_sign_on'
-          'sync_email_from_provider'   | 'omniauth_sync_email_from_provider'
-          'sync_profile_from_provider' | 'omniauth_sync_profile_from_provider'
-          'allow_bypass_two_factor'    | 'omniauth_allow_bypass_two_factor'
-          'sync_profile_attributes'    | 'omniauth_sync_profile_attributes'
-          'auto_link_user'             | 'omniauth_auto_link_user'
-        end
-
-        with_them do
-          # Testing scenarios where settings are set to boolean true, string,
-          # and array of strings.
-          [true, 'foo', ['bar', 'tar']].each do |value|
-            context "with #{params[:gitlab_rb_setting]} set to #{value}" do
-              before(:each) do
-                stub_gitlab_rb(
-                  gitlab_rails: {
-                    gitlab_rb_setting => value
-                  }.transform_keys(&:to_sym)
-                )
-              end
-
-              it "renders gitlab.yml with #{params[:gitlab_yml_setting]} set to #{value}" do
-                expect(gitlab_yml[:production][:omniauth][gitlab_yml_setting.to_sym]).to eq value
-              end
-            end
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/pages_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/pages_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvcGFnZXNfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/pages_spec.rb
+++ /dev/null
@@ -1,208 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  using RSpec::Parameterized::TableSyntax
-  include_context 'gitlab-rails'
-  include_context 'object storage config'
-
-  let(:aws_connection_data) { JSON.parse(aws_connection_hash.to_json, symbolize_names: true) }
-
-  describe 'Pages settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml with Pages disabled' do
-        expect(gitlab_yml[:production][:pages]).to eq(
-          enabled: false,
-          access_control: false,
-          artifacts_server: true,
-          external_http: false,
-          external_https: false,
-          host: nil,
-          https: false,
-          object_store: {
-            connection: {},
-            enabled: false,
-            remote_directory: "pages"
-          },
-          local_store: {
-            enabled: true,
-            path: "/var/opt/gitlab/gitlab-rails/shared/pages",
-          },
-          path: "/var/opt/gitlab/gitlab-rails/shared/pages",
-          port: nil
-        )
-      end
-    end
-
-    context 'with user specified values' do
-      context 'when Pages deployed along with GitLab' do
-        before do
-          stub_gitlab_rb(
-            external_url: 'https://gitlab.example.com',
-            pages_external_url: 'https://pages.example.com',
-            gitlab_pages: {
-              access_control: true,
-              artifacts_server: false,
-              external_http: ['1.2.3.4']
-            },
-            gitlab_rails: {
-              pages_path: '/random/path',
-              pages_object_store_enabled: true,
-              pages_object_store_remote_directory: 'foobar',
-              pages_object_store_connection: aws_connection_hash
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with user specified values' do
-          expect(gitlab_yml[:production][:pages]).to eq(
-            {
-              enabled: true,
-              access_control: true,
-              path: '/random/path',
-              host: 'pages.example.com',
-              port: 443,
-              https: true,
-              external_http: true,
-              external_https: false,
-              artifacts_server: false,
-              object_store: {
-                enabled: true,
-                remote_directory: 'foobar',
-                connection: aws_connection_data
-              },
-              local_store: {
-                enabled: true,
-                path: '/random/path'
-              }
-            }
-          )
-        end
-      end
-
-      context 'when Pages deployed external to GitLab' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              pages_enabled: true,
-              pages_path: '/random/path',
-              pages_host: 'pages.example.com',
-              pages_port: 443,
-              pages_https: true,
-              pages_object_store_enabled: true,
-              pages_object_store_remote_directory: 'foobar',
-              pages_object_store_connection: aws_connection_hash
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with user specified values' do
-          expect(gitlab_yml[:production][:pages]).to eq(
-            {
-              enabled: true,
-              access_control: false,
-              path: '/random/path',
-              host: 'pages.example.com',
-              port: 443,
-              https: true,
-              external_http: false,
-              external_https: false,
-              artifacts_server: true,
-              object_store: {
-                enabled: true,
-                remote_directory: 'foobar',
-                connection: aws_connection_data
-              },
-              local_store: {
-                enabled: true,
-                path: '/random/path'
-              }
-            }
-          )
-        end
-      end
-
-      describe 'for external HTTPS settings' do
-        context 'when external_https is used' do
-          before do
-            stub_gitlab_rb(
-              external_url: 'https://gitlab.example.com',
-              pages_external_url: 'https://pages.example.com',
-              gitlab_pages: {
-                external_https: ['1.2.3.4']
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with pages.external_https set to true' do
-            expect(gitlab_yml[:production][:pages][:external_https]).to be true
-          end
-        end
-
-        context 'when external_https_proxyv2 is used' do
-          before do
-            stub_gitlab_rb(
-              external_url: 'https://gitlab.example.com',
-              pages_external_url: 'https://pages.example.com',
-              gitlab_pages: {
-                external_https_proxyv2: ['1.2.3.4']
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with pages.external_https set to true' do
-            expect(gitlab_yml[:production][:pages][:external_https]).to be true
-          end
-        end
-      end
-
-      describe 'Pages local store settings' do
-        context 'when neither Pages path nor local store path is specified' do
-          before do
-            stub_gitlab_rb(
-              external_url: 'https://gitlab.example.com',
-              pages_external_url: 'https://pages.example.com'
-            )
-          end
-
-          it 'renders gitlab.yml with Pages local store path set to default Pages path' do
-            expect(gitlab_yml[:production][:pages][:local_store][:path]).to eq('/var/opt/gitlab/gitlab-rails/shared/pages')
-          end
-        end
-
-        context 'when Pages path is specified but not local store path' do
-          before do
-            stub_gitlab_rb(
-              external_url: 'https://gitlab.example.com',
-              pages_external_url: 'https://pages.example.com',
-              gitlab_rails: {
-                pages_local_store_enabled: true,
-                pages_path: '/tmp/test'
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with Pages local store path set to Pages path' do
-            expect(gitlab_yml[:production][:pages][:local_store][:path]).to eq('/tmp/test')
-          end
-        end
-
-        context 'when Pages local store path is different than Pages path' do
-          before do
-            stub_gitlab_rb(
-              external_url: 'https://gitlab.example.com',
-              pages_external_url: 'https://pages.example.com',
-              gitlab_rails: {
-                pages_path: '/tmp/test',
-                pages_local_store_path: '/another/path'
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with specified Pages local store path' do
-            expect(gitlab_yml[:production][:pages][:local_store][:path]).to eq('/another/path')
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/prometheus_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/prometheus_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvcHJvbWV0aGV1c19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/prometheus_spec.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'Prometheus settings' do
-    context 'with default configuration' do
-      it 'renders gitlab.yml with bundled Prometheus default values' do
-        expect(gitlab_yml[:production][:prometheus]).to eq(
-          enabled: true,
-          server_address: 'localhost:9090'
-        )
-      end
-    end
-
-    context 'with user specified configuation' do
-      context 'when bundled Prometheus is running on non-default address' do
-        before do
-          stub_gitlab_rb(
-            prometheus: {
-              listen_address: '0.0.0.0:9191'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with correct values' do
-          expect(gitlab_yml[:production][:prometheus]).to eq(
-            enabled: true,
-            server_address: '0.0.0.0:9191'
-          )
-        end
-      end
-
-      context 'when bundled Prometheus is running on non-default address with only port specified' do
-        before do
-          stub_gitlab_rb(
-            prometheus: {
-              listen_address: ':8080'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with server address rendered as a string' do
-          expect(gitlab_yml[:production][:prometheus]).to eq(
-            enabled: true,
-            server_address: ':8080'
-          )
-        end
-      end
-
-      context 'when configured with an external and internal Prometheus addresses' do
-        before do
-          stub_gitlab_rb(
-            prometheus: {
-              listen_address: '0.0.0.0:9191'
-            },
-            gitlab_rails: {
-              prometheus_address: '1.1.1.1:2222'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with the external address' do
-          expect(gitlab_yml[:production][:prometheus]).to eq(
-            enabled: true,
-            server_address: '1.1.1.1:2222'
-          )
-        end
-      end
-
-      context 'when bundled Prometheus is disabled and no external Prometheus address is specified' do
-        before do
-          stub_gitlab_rb(
-            prometheus: {
-              enable: false
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with Prometheus disabled' do
-          expect(gitlab_yml[:production][:prometheus][:enabled]).to be false
-        end
-      end
-
-      context 'when bundled Prometheus is disabled but an external Prometheus address is specified' do
-        before do
-          stub_gitlab_rb(
-            prometheus: {
-              enable: false,
-            },
-            gitlab_rails: {
-              prometheus_address: '1.2.3.4:2222'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with external Prometheus addreess' do
-          expect(gitlab_yml[:production][:prometheus]).to eq(
-            enabled: true,
-            server_address: '1.2.3.4:2222'
-          )
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/pseudonymizer_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/pseudonymizer_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvcHNldWRvbnltaXplcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/pseudonymizer_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-  include_context 'object storage config'
-
-  describe 'pseudonymizer settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml with pseudonymizer disabled' do
-        config = gitlab_yml[:production][:pseudonymizer]
-        expect(config).to eq(
-          {
-            manifest: nil,
-            upload: {
-              connection: {},
-              remote_directory: nil
-            }
-          }
-        )
-      end
-    end
-
-    context 'with user specified values' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            pseudonymizer_manifest: 'another/path/manifest.yml',
-            pseudonymizer_upload_remote_directory: 'gitlab-pseudo',
-            pseudonymizer_upload_connection: aws_connection_hash,
-          }
-        )
-      end
-      it 'renders gitlab.yml with user specified values' do
-        config = gitlab_yml[:production][:pseudonymizer]
-        expect(config).to eq(
-          {
-            manifest: 'another/path/manifest.yml',
-            upload: {
-              connection: aws_connection_data,
-              remote_directory: 'gitlab-pseudo'
-            }
-          }
-        )
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/registry_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/registry_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvcmVnaXN0cnlfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/registry_spec.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'Registry settings' do
-    context 'with default configuration' do
-      context 'when GitLab is running under http' do
-        it 'renders gitlab.yml without Registry settings' do
-          expect(gitlab_yml[:production][:registry]).to eq(
-            enabled: false,
-            api_url: nil,
-            host: nil,
-            port: nil,
-            issuer: 'omnibus-gitlab-issuer',
-            key: '/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key',
-            notification_secret: nil,
-            path: nil
-          )
-        end
-      end
-
-      context 'when GitLab is running under HTTPS using Omnibus automated TLS certificates' do
-        before do
-          stub_gitlab_rb(
-            external_url: 'https://gitlab.example.com'
-          )
-        end
-
-        it 'automatically enables Registry and renders gitlab.yml accordingly' do
-          expect(gitlab_yml[:production][:registry]).to eq(
-            enabled: true,
-            host: 'gitlab.example.com',
-            port: 5050,
-            api_url: 'http://localhost:5000',
-            issuer: 'omnibus-gitlab-issuer',
-            key: '/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key',
-            notification_secret: nil,
-            path: '/var/opt/gitlab/gitlab-rails/shared/registry'
-          )
-        end
-      end
-
-      context 'with user specified configuration' do
-        context 'when URL for bundled Registry is specified' do
-          before do
-            stub_gitlab_rb(
-              registry_external_url: 'http://registry.example.com'
-            )
-          end
-
-          it 'renders gitlab.yml with specified Registry URL and other default Registry settings' do
-            expect(gitlab_yml[:production][:registry]).to eq(
-              enabled: true,
-              host: 'registry.example.com',
-              port: nil,
-              api_url: 'http://localhost:5000',
-              issuer: 'omnibus-gitlab-issuer',
-              key: '/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key',
-              notification_secret: nil,
-              path: '/var/opt/gitlab/gitlab-rails/shared/registry'
-            )
-          end
-        end
-
-        context 'when other Registry configuration are specified' do
-          before do
-            stub_gitlab_rb(
-              registry_external_url: 'http://registry.example.com:1234',
-              registry: {
-                registry_http_addr: 'localhost:1111'
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with specified Registry settings' do
-            expect(gitlab_yml[:production][:registry]).to eq(
-              enabled: true,
-              host: 'registry.example.com',
-              port: 1234,
-              api_url: 'http://localhost:1111',
-              issuer: 'omnibus-gitlab-issuer',
-              key: '/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key',
-              notification_secret: nil,
-              path: '/var/opt/gitlab/gitlab-rails/shared/registry'
-            )
-          end
-        end
-
-        context 'with external Registry' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                registry_enabled: 'true',
-                registry_key_path: '/fake/path',
-                registry_host: 'registry.example.com',
-                registry_api_url: 'http://registry.example.com:1234',
-                registry_port: 1234,
-                registry_issuer: 'foobar',
-                registry_notification_secret: 'qwerty',
-                registry_path: '/tmp/registry'
-              }
-            )
-          end
-
-          it 'renders gitlab.yml with correct registry settings' do
-            expect(gitlab_yml[:production][:registry]).to eq(
-              enabled: true,
-              host: 'registry.example.com',
-              port: 1234,
-              api_url: 'http://registry.example.com:1234',
-              issuer: 'foobar',
-              key: '/fake/path',
-              notification_secret: 'qwerty',
-              path: '/tmp/registry'
-            )
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/repositories_storages_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/repositories_storages_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvcmVwb3NpdG9yaWVzX3N0b3JhZ2VzX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/repositories_storages_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  using RSpec::Parameterized::TableSyntax
-  include_context 'gitlab-rails'
-
-  describe 'repositories storages settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml with default repository storage settings' do
-        expect(gitlab_yml[:production][:repositories][:storages]).to eq(
-          default: {
-            gitaly_address: 'unix:/var/opt/gitlab/gitaly/gitaly.socket',
-            path: '/var/opt/gitlab/git-data/repositories'
-          }
-        )
-      end
-    end
-
-    context 'with user specified values' do
-      before do
-        stub_gitlab_rb(
-          git_data_dirs: {
-            second_storage: {
-              path: '/tmp/foobar',
-              gitaly_address: 'unix:/var/gitaly.socket'
-            }
-          }
-        )
-      end
-
-      it 'renders gitlab.yml with specified repository storage settings' do
-        expect(gitlab_yml[:production][:repositories][:storages]).to eq(
-          second_storage: {
-            gitaly_address: 'unix:/var/gitaly.socket',
-            path: '/tmp/foobar/repositories'
-          }
-        )
-      end
-
-      context 'when path is not provided for a storage' do
-        before do
-          stub_gitlab_rb(
-            git_data_dirs: {
-              default: {
-                gitaly_address: 'unix:/var/gitaly.socket'
-              }
-            }
-          )
-        end
-
-        it 'sets the default path' do
-          expect(gitlab_yml[:production][:repositories][:storages]).to eq(
-            default: {
-              gitaly_address: 'unix:/var/gitaly.socket',
-              path: '/var/opt/gitlab/git-data/repositories'
-            }
-          )
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/sentry_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/sentry_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvc2VudHJ5X3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/sentry_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'sentry settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml with sentry disabled' do
-        expect(gitlab_yml[:production][:sentry]).to eq(
-          {
-            enabled: false,
-            dsn: nil,
-            clientside_dsn: nil,
-            environment: nil
-          }
-        )
-      end
-    end
-
-    context 'when the user enables sentry' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            sentry_enabled: true,
-            sentry_dsn: 'https://708cd0ca88972f04d5c836a395b8db63@example.com/76',
-            sentry_clientside_dsn: 'https://708cd0ca88972f04d5c836a395b8db63@example.com/77',
-            sentry_environment: 'testing'
-          }
-        )
-      end
-
-      it 'renders gitlab.yml with user specified values' do
-        expect(gitlab_yml[:production][:sentry]).to eq(
-          {
-            enabled: true,
-            dsn: 'https://708cd0ca88972f04d5c836a395b8db63@example.com/76',
-            clientside_dsn: 'https://708cd0ca88972f04d5c836a395b8db63@example.com/77',
-            environment: 'testing'
-          }
-        )
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/service_desk_email_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/service_desk_email_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvc2VydmljZV9kZXNrX2VtYWlsX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/service_desk_email_spec.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'service desk email settings' do
-    describe 'log file location' do
-      context 'with default values' do
-        it 'renders gitlab.yml with default value for mailroom log file path' do
-          expect(gitlab_yml[:production][:service_desk_email][:log_path]).to eq '/var/log/gitlab/mailroom/mail_room_json.log'
-        end
-      end
-
-      context 'with custom log file path specified' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              service_desk_email_log_file: '/my/custom/log/file'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with specified value for mailroom log file path' do
-          expect(gitlab_yml[:production][:service_desk_email][:log_path]).to eq '/my/custom/log/file'
-        end
-      end
-
-      context 'with custom log directory specified' do
-        before do
-          stub_gitlab_rb(
-            mailroom: {
-              log_directory: '/my/custom/directory'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with default log file name inside specified directory' do
-          expect(gitlab_yml[:production][:service_desk_email][:log_path]).to eq '/my/custom/directory/mail_room_json.log'
-        end
-      end
-
-      context 'with custom log file and directory specified' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              service_desk_email_log_file: '/foobar.log'
-            },
-            mailroom: {
-              log_directory: '/my/custom/directory'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with specified log file path irrespective of specified directory' do
-          expect(gitlab_yml[:production][:service_desk_email][:log_path]).to eq '/foobar.log'
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/shutdown_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/shutdown_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvc2h1dGRvd25fc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/shutdown_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'shutdown settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml with default value for blackout seconds' do
-        expect(gitlab_yml[:production][:shutdown][:blackout_seconds]).to eq(10)
-      end
-    end
-
-    context 'with user specified values' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            shutdown_blackout_seconds: 30
-          }
-        )
-      end
-
-      it 'renders gitlab.yml with specified value for blackout seconds' do
-        expect(gitlab_yml[:production][:shutdown][:blackout_seconds]).to eq(30)
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/sidekiq_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/sidekiq_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvc2lkZWtpcV9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/sidekiq_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'sidekiq settings' do
-    describe 'log_format' do
-      context 'with default values' do
-        it 'renders gitlab.yml with sidekiq log format set to json' do
-          expect(gitlab_yml[:production][:sidekiq][:log_format]).to eq('json')
-        end
-      end
-
-      context 'with user specified value' do
-        before do
-          stub_gitlab_rb(
-            sidekiq: {
-              log_format: 'text'
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with user specified value for sidekiq log format' do
-          expect(gitlab_yml[:production][:sidekiq][:log_format]).to eq('text')
-        end
-      end
-    end
-
-    describe 'routing_rules' do
-      context 'with default values' do
-        it 'renders gitlab.yml without routing_rules' do
-          expect(gitlab_yml[:production][:sidekiq]).not_to include(:routing_rules)
-        end
-      end
-
-      context 'with an empty array' do
-        before do
-          stub_gitlab_rb(
-            sidekiq: {
-              routing_rules: []
-            }
-          )
-        end
-
-        it 'renders gitlab.yml without routing_rules' do
-          expect(gitlab_yml[:production][:sidekiq]).not_to include(:routing_rules)
-        end
-      end
-
-      context 'with a valid routing rules list' do
-        before do
-          stub_gitlab_rb(
-            sidekiq: {
-              routing_rules: [
-                ["resource_boundary=cpu", "cpu_boundary"],
-                ["feature_category=pages", nil],
-                ["feature_category=search", ''],
-                ["feature_category=memory|resource_boundary=memory", ''],
-                ["*", "default"]
-              ]
-            }
-          )
-        end
-
-        it 'renders gitlab.yml with user specified value for sidekiq routing rules' do
-          expect(gitlab_yml[:production][:sidekiq][:routing_rules]).to eq(
-            [
-              ["resource_boundary=cpu", "cpu_boundary"],
-              ["feature_category=pages", nil],
-              ["feature_category=search", ""],
-              ["feature_category=memory|resource_boundary=memory", ""],
-              ["*", "default"]
-            ]
-          )
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/smartcard_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/smartcard_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvc21hcnRjYXJkX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/smartcard_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  include_context 'gitlab-rails'
-
-  describe 'Smartcard authentication settings' do
-    context 'with default configuration' do
-      it 'renders gitlab.yml with Smartcard authentication disabled' do
-        expect(gitlab_yml[:production][:smartcard]).to eq(
-          enabled: false,
-          ca_file: '/etc/gitlab/ssl/CA.pem',
-          client_certificate_required_host: nil,
-          client_certificate_required_port: 3444,
-          required_for_git_access: false,
-          san_extensions: false
-        )
-      end
-    end
-
-    context 'with user specified configuration' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            smartcard_enabled: true,
-            smartcard_ca_file: '/foobar/CA.pem',
-            smartcard_client_certificate_required_host: 'smartcard.gitlab.example.com',
-            smartcard_client_certificate_required_port: 123,
-            smartcard_required_for_git_access: true,
-            smartcard_san_extensions: true
-          }
-        )
-      end
-
-      it 'renders gitlab.yml with user specified values for Smartcard authentication' do
-        expect(gitlab_yml[:production][:smartcard]).to eq(
-          enabled: true,
-          ca_file: '/foobar/CA.pem',
-          client_certificate_required_host: 'smartcard.gitlab.example.com',
-          client_certificate_required_port: 123,
-          required_for_git_access: true,
-          san_extensions: true
-        )
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails/gitlab_yml/unleash_spec.rb b/spec/chef/recipes/gitlab-rails/gitlab_yml/unleash_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzL2dpdGxhYl95bWwvdW5sZWFzaF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails/gitlab_yml/unleash_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  using RSpec::Parameterized::TableSyntax
-  include_context 'gitlab-rails'
-
-  describe 'Unleash settings' do
-    context 'with default values' do
-      it 'renders gitlab.yml unleash disabled' do
-        expect(gitlab_yml[:production][:feature_flags][:unleash]).to eq(
-          enabled: false
-        )
-      end
-    end
-
-    context 'with user specified values' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            feature_flags_unleash_enabled: true,
-            feature_flags_unleash_url: 'foobar.com',
-            feature_flags_unleash_app_name: 'GitLab Production',
-            feature_flags_unleash_instance_id: 'foobar'
-          }
-        )
-      end
-
-      it 'renders gitlab.yml with user specified values' do
-        expect(gitlab_yml[:production][:feature_flags][:unleash]).to eq(
-          enabled: true,
-          url: 'foobar.com',
-          app_name: 'GitLab Production',
-          instance_id: 'foobar'
-        )
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-rails_spec.rb b/spec/chef/recipes/gitlab-rails_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXJhaWxzX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-rails_spec.rb
+++ /dev/null
@@ -1,1147 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-rails' do
-  using RSpec::Parameterized::TableSyntax
-
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink runit_service)).converge('gitlab::default') }
-  let(:redis_instances) { %w(cache queues shared_state trace_chunks rate_limiting sessions) }
-  let(:config_dir) { '/var/opt/gitlab/gitlab-rails/etc/' }
-  let(:default_vars) do
-    {
-      'HOME' => '/var/opt/gitlab',
-      'RAILS_ENV' => 'production',
-      'SIDEKIQ_MEMORY_KILLER_MAX_RSS' => '2000000',
-      'BUNDLE_GEMFILE' => '/opt/gitlab/embedded/service/gitlab-rails/Gemfile',
-      'PATH' => '/opt/gitlab/bin:/opt/gitlab/embedded/bin:/bin:/usr/bin',
-      'ICU_DATA' => '/opt/gitlab/embedded/share/icu/current',
-      'PYTHONPATH' => '/opt/gitlab/embedded/lib/python3.9/site-packages',
-      'EXECJS_RUNTIME' => 'Disabled',
-      'TZ' => ':/etc/localtime',
-      'LD_PRELOAD' => '/opt/gitlab/embedded/lib/libjemalloc.so',
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
-      'SSL_CERT_FILE' => '/opt/gitlab/embedded/ssl/cert.pem'
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow(File).to receive(:symlink?).and_call_original
-  end
-
-  context 'with defaults' do
-    it 'creates a default VERSION file and restarts services' do
-      expect(chef_run).to create_version_file('Create version file for Rails').with(
-        version_file_path: '/var/opt/gitlab/gitlab-rails/RUBY_VERSION',
-        version_check_cmd: '/opt/gitlab/embedded/bin/ruby --version'
-      )
-
-      dependent_services = []
-      dependent_services.each do |svc|
-        expect(chef_run.version_file('Create version file for Rails')).to notify("runit_service[#{svc}]").to(:restart)
-      end
-    end
-  end
-
-  context 'when manage-storage-directories is disabled' do
-    cached(:chef_run) do
-      RSpec::Mocks.with_temporary_scope do
-        stub_gitlab_rb(gitlab_rails: { shared_path: '/tmp/shared',
-                                       uploads_directory: '/tmp/uploads',
-                                       uploads_storage_path: '/tmp/uploads_storage' },
-                       gitlab_ci: { builds_directory: '/tmp/builds' },
-                       git_data_dirs: {
-                         "some_storage" => {
-                           "path" => "/tmp/git-data"
-                         }
-                       },
-                       manage_storage_directories: { enable: false })
-      end
-
-      ChefSpec::SoloRunner.new.converge('gitlab::default')
-    end
-
-    it 'does not create the git-data directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/git-data')
-    end
-
-    it 'does not create the repositories directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/git-data/repositories')
-    end
-
-    it 'does not create the shared directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared')
-    end
-
-    it 'does not create the artifacts directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/artifacts')
-    end
-
-    it 'does not create the external-diffs directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/external-diffs')
-    end
-
-    it 'does not create the lfs storage directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/lfs-objects')
-    end
-
-    it 'does not create the packages storage directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/packages')
-    end
-
-    it 'does not create the dependency_proxy storage directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/dependency_proxy')
-    end
-
-    it 'does not create the terraform_state storage directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/terraform_state')
-    end
-
-    it 'does not create the ci_secure_files storage directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/ci_secure_files')
-    end
-
-    it 'does not create the GitLab pages directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/pages')
-    end
-
-    it 'does not create the uploads directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/uploads')
-    end
-
-    it 'does not create the ci builds directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/builds')
-    end
-
-    it 'does not create the uploads storage directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/uploads_storage')
-    end
-  end
-
-  context 'when manage-storage-directories is enabled' do
-    cached(:chef_run) do
-      RSpec::Mocks.with_temporary_scope do
-        stub_gitlab_rb(gitlab_rails: { shared_path: '/tmp/shared',
-                                       uploads_directory: '/tmp/uploads',
-                                       uploads_storage_path: '/tmp/uploads_storage' },
-                       gitlab_ci: { builds_directory: '/tmp/builds' },
-                       git_data_dirs: {
-                         "some_storage" => {
-                           "path" => "/tmp/git-data"
-                         }
-                       })
-      end
-
-      ChefSpec::SoloRunner.converge('gitlab::default')
-    end
-
-    it 'creates the git-data directory' do
-      expect(chef_run).to create_storage_directory('/tmp/git-data').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the repositories directory' do
-      expect(chef_run).to create_storage_directory('/tmp/git-data/repositories').with(owner: 'git', group: 'git', mode: '2770')
-    end
-
-    it 'creates the shared directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared').with(owner: 'git', group: 'gitlab-www', mode: '0751')
-    end
-
-    it 'creates the artifacts directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared/artifacts').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the external-diffs directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared/external-diffs').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the lfs storage directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared/lfs-objects').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the packages directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared/packages').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the dependency_proxy directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared/dependency_proxy').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the terraform_state directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared/terraform_state').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the ci_secure_files directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared/ci_secure_files').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the encrypted_settings directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared/encrypted_settings').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the GitLab pages directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared/pages').with(owner: 'git', group: 'gitlab-www', mode: '0750')
-    end
-
-    it 'creates the shared tmp directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared/tmp').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the shared cache directory' do
-      expect(chef_run).to create_storage_directory('/tmp/shared/cache').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the uploads directory' do
-      expect(chef_run).to create_storage_directory('/tmp/uploads').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the ci builds directory' do
-      expect(chef_run).to create_storage_directory('/tmp/builds').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the uploads storage directory' do
-      expect(chef_run).to create_storage_directory('/tmp/uploads_storage').with(owner: 'git', group: 'git', mode: '0700')
-    end
-  end
-
-  context 'when uploads storage directory is not specified' do
-    cached(:chef_run) do
-      ChefSpec::SoloRunner.converge('gitlab::default')
-    end
-
-    it 'does not create the uploads storage directory' do
-      expect(chef_run).not_to create_storage_directory('/opt/gitlab/embedded/service/gitlab-rails/public')
-    end
-  end
-
-  context 'with redis settings' do
-    let(:config_file) { '/var/opt/gitlab/gitlab-rails/etc/resque.yml' }
-    let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') }
-
-    context 'and default configuration' do
-      it 'creates the config file with the required redis settings' do
-        expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root').with_variables(
-          hash_including(
-            redis_url: URI('unix:/var/opt/gitlab/redis/redis.socket'),
-            redis_sentinels: [],
-            redis_enable_client: true
-          )
-        )
-
-        expect(chef_run).to render_file(config_file).with_content { |content|
-          expect(content).to match(%r(url: unix:/var/opt/gitlab/redis/redis.socket$))
-          expect(content).not_to match(/id:/)
-        }
-      end
-
-      it 'creates cable.yml with the same settings' do
-        expect(chef_run).to create_templatesymlink('Create a cable.yml and create a symlink to Rails root').with_variables(
-          hash_including(
-            redis_url: URI('unix:/var/opt/gitlab/redis/redis.socket'),
-            redis_sentinels: [],
-            redis_enable_client: true
-          )
-        )
-
-        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/cable.yml').with_content { |content|
-          expect(content).to match(%r(url: unix:/var/opt/gitlab/redis/redis.socket$))
-        }
-      end
-
-      it 'does not render the separate instance configurations' do
-        redis_instances.each do |instance|
-          expect(chef_run).not_to render_file("#{config_dir}redis.#{instance}.yml")
-        end
-      end
-
-      it 'deletes the separate instance config files' do
-        redis_instances.each do |instance|
-          expect(chef_run).to delete_file("/opt/gitlab/embedded/service/gitlab-rails/config/redis.#{instance}.yml")
-          expect(chef_run).to delete_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml")
-        end
-      end
-    end
-
-    context 'and custom configuration' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            redis_host: 'redis.example.com',
-            redis_port: 8888,
-            redis_database: 2,
-            redis_password: 'mypass',
-            redis_enable_client: false
-          }
-        )
-      end
-
-      it 'creates the config file with custom host, port, password and database' do
-        expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root').with_variables(
-          hash_including(
-            redis_url: URI('redis://:mypass@redis.example.com:8888/2'),
-            redis_sentinels: [],
-            redis_enable_client: false
-          )
-        )
-
-        expect(chef_run).to render_file(config_file).with_content { |content|
-          expect(content).to match(%r(url: redis://:mypass@redis.example.com:8888/2))
-          expect(content).to match(/id:$/)
-        }
-      end
-
-      it 'creates cable.yml with custom host, port, password and database' do
-        expect(chef_run).to create_templatesymlink('Create a cable.yml and create a symlink to Rails root').with_variables(
-          hash_including(
-            redis_url: URI('redis://:mypass@redis.example.com:8888/2'),
-            redis_sentinels: [],
-            redis_enable_client: false
-          )
-        )
-
-        expect(chef_run).to render_file(config_file).with_content { |content|
-          expect(content).to match(%r(url: redis://:mypass@redis.example.com:8888/2))
-          expect(content).to match(/id:$/)
-        }
-      end
-    end
-
-    context 'with multiple instances' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            redis_enable_client: false,
-            redis_cache_instance: "redis://:fakepass@fake.redis.cache.com:8888/2",
-            redis_cache_sentinels: [
-              { host: 'cache', port: '1234' },
-              { host: 'cache', port: '3456' }
-            ],
-            redis_queues_instance: "redis://:fakepass@fake.redis.queues.com:8888/2",
-            redis_queues_sentinels: [
-              { host: 'queues', port: '1234' },
-              { host: 'queues', port: '3456' }
-            ],
-            redis_shared_state_instance: "redis://:fakepass@fake.redis.shared_state.com:8888/2",
-            redis_shared_state_sentinels: [
-              { host: 'shared_state', port: '1234' },
-              { host: 'shared_state', port: '3456' }
-            ],
-            redis_trace_chunks_instance: "redis://:fakepass@fake.redis.trace_chunks.com:8888/2",
-            redis_trace_chunks_sentinels: [
-              { host: 'trace_chunks', port: '1234' },
-              { host: 'trace_chunks', port: '3456' }
-            ],
-            redis_rate_limiting_instance: "redis://:fakepass@fake.redis.rate_limiting.com:8888/2",
-            redis_rate_limiting_sentinels: [
-              { host: 'rate_limiting', port: '1234' },
-              { host: 'rate_limiting', port: '3456' }
-            ],
-            redis_sessions_instance: "redis://:fakepass@fake.redis.sessions.com:8888/2",
-            redis_sessions_sentinels: [
-              { host: 'sessions', port: '1234' },
-              { host: 'sessions', port: '3456' }
-            ],
-            redis_actioncable_instance: "redis://:fakepass@fake.redis.actioncable.com:8888/2",
-            redis_actioncable_sentinels: [
-              { host: 'actioncable', port: '1234' },
-              { host: 'actioncable', port: '3456' }
-            ]
-          }
-        )
-      end
-
-      it 'render separate config files' do
-        redis_instances.each do |instance|
-          expect(chef_run).to create_templatesymlink("Create a redis.#{instance}.yml and create a symlink to Rails root").with_variables(
-            redis_url: "redis://:fakepass@fake.redis.#{instance}.com:8888/2",
-            redis_sentinels: [{ "host" => instance, "port" => "1234" }, { "host" => instance, "port" => "3456" }],
-            redis_enable_client: false
-          )
-          expect(chef_run).not_to delete_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml")
-        end
-      end
-
-      it 'still renders the default configuration file' do
-        expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root')
-      end
-
-      it 'creates cable.yml with custom settings' do
-        expect(chef_run).to create_templatesymlink('Create a cable.yml and create a symlink to Rails root').with_variables(
-          hash_including(
-            redis_url: "redis://:fakepass@fake.redis.actioncable.com:8888/2",
-            redis_sentinels: [{ 'host' => 'actioncable', 'port' => '1234' }, { 'host' => 'actioncable', 'port' => '3456' }],
-            redis_enable_client: false
-          )
-        )
-      end
-    end
-  end
-
-  describe 'gitlab.yml' do
-    gitlab_yml_path = '/var/opt/gitlab/gitlab-rails/etc/gitlab.yml'
-    let(:gitlab_yml) { chef_run.template(gitlab_yml_path) }
-    let(:gitlab_yml_templatesymlink) { chef_run.templatesymlink('Create a gitlab.yml and create a symlink to Rails root') }
-
-    # NOTE: Test if we pass proper notifications to other resources
-    describe 'rails cache management' do
-      before do
-        stub_default_not_listening?(false)
-      end
-
-      context 'with default values' do
-        it 'should notify rails cache clear resource' do
-          expect(gitlab_yml_templatesymlink).to notify('execute[clear the gitlab-rails cache]')
-        end
-      end
-
-      context 'with rake_cache_clear set to false' do
-        before do
-          stub_gitlab_rb(gitlab_rails: { rake_cache_clear: false })
-        end
-
-        it 'should notify rails cache clear resource' do
-          expect(gitlab_yml_templatesymlink).to notify(
-            'execute[clear the gitlab-rails cache]')
-        end
-
-        it 'should not run cache clear' do
-          expect(chef_run).not_to run_execute(
-            'clear the gitlab-rails cache')
-        end
-      end
-    end
-  end
-
-  context 'with environment variables' do
-    context 'by default' do
-      it 'creates necessary env variable files' do
-        expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables(default_vars)
-      end
-
-      context 'when a custom env variable is specified' do
-        before do
-          stub_gitlab_rb(gitlab_rails: { env: { 'IAM' => 'CUSTOMVAR' } })
-        end
-
-        it 'creates necessary env variable files' do
-          expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables(
-            default_vars.merge(
-              {
-                'IAM' => 'CUSTOMVAR'
-              }
-            )
-          )
-        end
-      end
-    end
-
-    context 'when relative URL is enabled' do
-      before do
-        stub_gitlab_rb(gitlab_rails: { gitlab_relative_url: '/gitlab' })
-      end
-
-      it 'creates necessary env variable files' do
-        expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables(
-          default_vars.merge(
-            {
-              'RAILS_RELATIVE_URL_ROOT' => '/gitlab'
-            }
-          )
-        )
-      end
-    end
-
-    context 'when relative URL is specified in external_url' do
-      before do
-        stub_gitlab_rb(external_url: 'http://localhost/gitlab')
-      end
-
-      it 'creates necessary env variable files' do
-        expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables(
-          default_vars.merge(
-            {
-              'RAILS_RELATIVE_URL_ROOT' => '/gitlab'
-            }
-          )
-        )
-      end
-    end
-
-    context 'when jemalloc is disabled' do
-      before do
-        stub_gitlab_rb(gitlab_rails: { enable_jemalloc: false })
-      end
-
-      it 'creates necessary env variable files' do
-        vars = default_vars.dup
-        vars.delete("LD_PRELOAD")
-        expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables(vars)
-      end
-    end
-  end
-
-  describe "with symlinked templates" do
-    let(:chef_run) { ChefSpec::SoloRunner.new.converge('gitlab::default') }
-
-    before do
-      %w(
-        alertmanager
-        gitlab-exporter
-        gitlab-pages
-        gitlab-kas
-        gitlab-workhorse
-        logrotate
-        nginx
-        node-exporter
-        postgres-exporter
-        postgresql
-        prometheus
-        redis
-        redis-exporter
-        sidekiq
-        puma
-        gitaly
-      ).map { |svc| stub_should_notify?(svc, true) }
-    end
-
-    describe 'database.yml' do
-      database_yml_path = '/var/opt/gitlab/gitlab-rails/etc/database.yml'
-      let(:database_yml) { chef_run.template(database_yml_path) }
-      let(:database_yml_content) { ChefSpec::Renderer.new(chef_run, database_yml).content }
-      let(:generated_yml_content) { YAML.safe_load(database_yml_content) }
-
-      let(:config_file) { database_yml_path }
-      let(:templatesymlink) { chef_run.templatesymlink('Create a database.yml and create a symlink to Rails root') }
-
-      context 'by default' do
-        cached(:chef_run) do
-          ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default')
-        end
-
-        it 'creates the template' do
-          expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-            hash_including(
-              'db_host' => '/var/opt/gitlab/postgresql',
-              'db_database' => 'gitlabhq_production',
-              'db_load_balancing' => { 'hosts' => [] },
-              'db_prepared_statements' => false,
-              'db_sslcompression' => 0,
-              'db_sslcert' => nil,
-              'db_sslkey' => nil,
-              'db_application_name' => nil
-            )
-          )
-        end
-
-        it 'template triggers notifications' do
-          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
-          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
-          expect(templatesymlink).not_to notify('runit_service[gitlab-workhorse]').to(:restart).delayed
-          expect(templatesymlink).not_to notify('runit_service[nginx]').to(:restart).delayed
-        end
-
-        it 'renders expected YAML' do
-          expect(generated_yml_content.dig('production', 'main', 'adapter')).to eq('postgresql')
-          expect(generated_yml_content.dig('production', 'main', 'host')).to eq('/var/opt/gitlab/postgresql')
-          expect(generated_yml_content.dig('production', 'main', 'port')).to eq(5432)
-          expect(generated_yml_content.dig('production', 'main', 'application_name')).to eq(nil)
-        end
-      end
-
-      context 'with specific database settings' do
-        context 'with an application name set' do
-          where(:appname, :expected) do
-            ''     | ''
-            'test' | 'test'
-          end
-
-          with_them do
-            cached(:chef_run) do
-              ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default')
-            end
-
-            before do
-              stub_gitlab_rb(
-                'gitlab_rails' => { 'db_application_name' => appname }
-              )
-            end
-
-            it 'renders expected YAML' do
-              expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-                hash_including(
-                  'db_application_name' => appname
-                )
-              )
-
-              expect(generated_yml_content.dig('production', 'main').keys).to include(*%w(adapter host port application_name))
-              expect(generated_yml_content.dig('production', 'main', 'application_name')).to eq(expected)
-            end
-          end
-        end
-
-        context 'when multiple postgresql listen_address is used' do
-          before do
-            stub_gitlab_rb(postgresql: { listen_address: "127.0.0.1,1.1.1.1" })
-          end
-
-          it 'creates the postgres configuration file with multi listen_address and database.yml file with one host' do
-            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-              hash_including(
-                'db_host' => '127.0.0.1'
-              )
-            )
-          end
-        end
-
-        context 'when no postgresql listen_address is used' do
-          it 'creates the postgres configuration file with empty listen_address and database.yml file with default one' do
-            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-              hash_including(
-                'db_host' => '/var/opt/gitlab/postgresql'
-              )
-            )
-          end
-        end
-
-        context 'when one postgresql listen_address is used' do
-          cached(:chef_run) do
-            RSpec::Mocks.with_temporary_scope do
-              stub_gitlab_rb(postgresql: { listen_address: "127.0.0.1" })
-            end
-
-            ChefSpec::SoloRunner.new.converge('gitlab::default')
-          end
-
-          it 'creates the postgres configuration file with one listen_address and database.yml file with one host' do
-            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-              hash_including(
-                'db_host' => '127.0.0.1'
-              )
-            )
-          end
-
-          it 'template triggers notifications' do
-            expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
-            expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
-            expect(templatesymlink).not_to notify('runit_service[gitlab-workhorse]').to(:restart).delayed
-            expect(templatesymlink).not_to notify('runit_service[nginx]').to(:restart).delayed
-          end
-        end
-
-        context 'when load balancers are specified' do
-          before do
-            stub_gitlab_rb(gitlab_rails: { db_load_balancing: { 'hosts' => ['primary.example.com', 'secondary.example.com'] } })
-          end
-
-          it 'uses provided value in database.yml' do
-            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-              hash_including(
-                'db_load_balancing' => { 'hosts' => ['primary.example.com', 'secondary.example.com'] }
-              )
-            )
-          end
-        end
-
-        context 'when prepared_statements are disabled' do
-          before do
-            stub_gitlab_rb(gitlab_rails: { db_prepared_statements: false })
-          end
-
-          it 'uses provided value in database.yml' do
-            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-              hash_including(
-                'db_prepared_statements' => false,
-                'db_statements_limit' => 1000
-              )
-            )
-          end
-        end
-
-        context 'when limit for prepared_statements are specified' do
-          before do
-            stub_gitlab_rb(gitlab_rails: { db_statements_limit: 12345 })
-          end
-
-          it 'uses provided value in database.yml' do
-            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-              hash_including(
-                'db_prepared_statements' => false,
-                'db_statements_limit' => 12345
-              )
-            )
-          end
-        end
-
-        context 'when SSL compression is enabled' do
-          before do
-            stub_gitlab_rb(gitlab_rails: { db_sslcompression: 1 })
-          end
-
-          it 'uses provided value in database.yml' do
-            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-              hash_including(
-                'db_sslcompression' => 1
-              )
-            )
-          end
-        end
-
-        context 'when SSL certificate and key for DB is specified' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                db_sslcert: '/etc/certs/db.cer',
-                db_sslkey: '/etc/certs/db.key'
-              }
-            )
-          end
-
-          it 'uses specified value in database.yml' do
-            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-              hash_including(
-                'db_sslcert' => '/etc/certs/db.cer',
-                'db_sslkey' => '/etc/certs/db.key'
-              )
-            )
-          end
-        end
-      end
-
-      describe 'client side statement_timeout' do
-        let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') }
-
-        context 'default values' do
-          it 'does not set a default value' do
-            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-              hash_including(
-                'db_statement_timeout' => nil
-              )
-            )
-
-            expect(chef_run).to render_file(config_file).with_content { |content|
-              expect(content).to match(%r(statement_timeout: $))
-            }
-          end
-        end
-
-        context 'custom value' do
-          before do
-            stub_gitlab_rb(
-              'postgresql' => { 'statement_timeout' => '65000' },
-              'gitlab_rails' => { 'db_statement_timeout' => '70000' }
-            )
-          end
-
-          it 'uses specified client side statement_timeout value' do
-            expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-              hash_including(
-                'db_statement_timeout' => '70000'
-              )
-            )
-
-            expect(chef_run).to render_file(config_file).with_content { |content|
-              expect(content).to match(%r(statement_timeout: 70000$))
-            }
-          end
-        end
-      end
-
-      context 'adjusting database adapter connection parameters' do
-        let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') }
-
-        using RSpec::Parameterized::TableSyntax
-
-        where(:rb_param, :yaml_param, :default_value, :custom_value) do
-          'db_connect_timeout' | 'connect_timeout' | nil | 5
-          'db_keepalives' | 'keepalives' | nil | 1
-          'db_keepalives_idle' | 'keepalives_idle' | nil | 5
-          'db_keepalives_interval' | 'keepalives_interval' | nil | 3
-          'db_keepalives_count' | 'keepalives_count' | nil | 3
-          'db_tcp_user_timeout' | 'tcp_user_timeout' | nil | 13000
-        end
-
-        with_them do
-          context 'default values' do
-            it 'does not set a default value' do
-              expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-                hash_including(
-                  rb_param => default_value
-                )
-              )
-
-              expect(chef_run).to render_file(config_file).with_content { |content|
-                expect(content).to match(%r(#{yaml_param}: $))
-              }
-            end
-          end
-
-          context 'custom connection parameter value' do
-            before do
-              stub_gitlab_rb(
-                'gitlab_rails' => { rb_param => custom_value }
-              )
-            end
-
-            it 'uses specified connection parameter value' do
-              expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables(
-                hash_including(
-                  rb_param => custom_value
-                )
-              )
-
-              expect(chef_run).to render_file(config_file).with_content { |content|
-                expect(content).to match(%r(#{yaml_param}: #{custom_value}$))
-              }
-            end
-          end
-        end
-      end
-    end
-
-    describe 'gitlab_workhorse_secret' do
-      let(:templatesymlink) { chef_run.templatesymlink('Create a gitlab_workhorse_secret and create a symlink to Rails root') }
-
-      context 'by default' do
-        cached(:chef_run) do
-          ChefSpec::SoloRunner.new.converge('gitlab::default')
-        end
-
-        it 'creates the template' do
-          expect(chef_run).to create_templatesymlink("Create a gitlab_workhorse_secret and create a symlink to Rails root").with(
-            owner: 'root',
-            group: 'root',
-            mode: '0644'
-          )
-        end
-
-        it 'template triggers notifications' do
-          expect(templatesymlink).to notify('runit_service[gitlab-workhorse]').to(:restart).delayed
-          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
-          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
-        end
-      end
-
-      context 'with specific gitlab_workhorse_secret' do
-        cached(:chef_run) do
-          RSpec::Mocks.with_temporary_scope do
-            stub_gitlab_rb(gitlab_workhorse: { secret_token: 'abc123-gitlab-workhorse' })
-          end
-
-          ChefSpec::SoloRunner.new.converge('gitlab::default')
-        end
-
-        it 'renders the correct node attribute' do
-          expect(chef_run).to create_templatesymlink("Create a gitlab_workhorse_secret and create a symlink to Rails root").with_variables(
-            secret_token: 'abc123-gitlab-workhorse'
-          )
-        end
-
-        it 'uses the correct owner and permissions' do
-          expect(chef_run).to create_templatesymlink('Create a gitlab_workhorse_secret and create a symlink to Rails root').with(
-            owner: 'root',
-            group: 'root',
-            mode: '0644'
-          )
-        end
-
-        it 'template triggers notifications' do
-          expect(templatesymlink).to notify('runit_service[gitlab-workhorse]').to(:restart).delayed
-          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
-          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
-        end
-      end
-    end
-
-    describe 'gitlab_pages_secret' do
-      let(:templatesymlink) { chef_run.templatesymlink('Create a gitlab_pages_secret and create a symlink to Rails root') }
-
-      context 'with pages disabled' do
-        let(:api_secret_key) { SecureRandom.base64(32) }
-
-        cached(:chef_run) do
-          RSpec::Mocks.with_temporary_scope do
-            stub_gitlab_rb(
-              pages_enabled: false,
-              gitlab_pages: { api_secret_key: api_secret_key, enable: false },
-              pages_external_url: 'http://pages.example.com'
-            )
-          end
-
-          ChefSpec::SoloRunner.new.converge('gitlab::default')
-        end
-
-        it 'creates the template' do
-          expect(chef_run).to create_templatesymlink("Create a gitlab_pages_secret and create a symlink to Rails root").with(
-            owner: 'root',
-            group: 'root',
-            mode: '0644'
-          )
-        end
-      end
-
-      context 'by default' do
-        cached(:chef_run) do
-          RSpec::Mocks.with_temporary_scope do
-            stub_gitlab_rb(
-              external_url: 'http://gitlab.example.com',
-              pages_external_url: 'http://pages.example.com'
-            )
-          end
-
-          ChefSpec::SoloRunner.new.converge('gitlab::default')
-        end
-
-        it 'creates the template' do
-          expect(chef_run).to create_templatesymlink("Create a gitlab_pages_secret and create a symlink to Rails root").with(
-            owner: 'root',
-            group: 'root',
-            mode: '0644'
-          )
-        end
-
-        it 'template triggers notifications' do
-          expect(templatesymlink).to notify('runit_service[gitlab-pages]').to(:restart).delayed
-          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
-          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
-        end
-      end
-
-      context 'with specific gitlab_pages_secret' do
-        let(:api_secret_key) { SecureRandom.base64(32) }
-
-        cached(:chef_run) do
-          RSpec::Mocks.with_temporary_scope do
-            stub_gitlab_rb(
-              gitlab_pages: { api_secret_key: api_secret_key },
-              external_url: 'http://gitlab.example.com',
-              pages_external_url: 'http://pages.example.com'
-            )
-          end
-
-          ChefSpec::SoloRunner.new.converge('gitlab::default')
-        end
-
-        it 'renders the correct node attribute' do
-          expect(chef_run).to create_templatesymlink("Create a gitlab_pages_secret and create a symlink to Rails root").with_variables(
-            secret_token: api_secret_key
-          )
-        end
-
-        it 'uses the correct owner and permissions' do
-          expect(chef_run).to create_templatesymlink('Create a gitlab_pages_secret and create a symlink to Rails root').with(
-            owner: 'root',
-            group: 'root',
-            mode: '0644'
-          )
-        end
-
-        it 'template triggers notifications' do
-          expect(templatesymlink).to notify('runit_service[gitlab-pages]').to(:restart).delayed
-          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
-          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
-        end
-      end
-    end
-
-    describe 'gitlab_kas_secret' do
-      let(:templatesymlink) { chef_run.templatesymlink('Create a gitlab_kas_secret and create a symlink to Rails root') }
-
-      shared_examples 'creates the KAS template' do
-        it 'creates the template' do
-          expect(chef_run).to create_templatesymlink('Create a gitlab_kas_secret and create a symlink to Rails root').with(
-            owner: 'root',
-            group: 'root',
-            mode: '0644'
-          )
-        end
-      end
-
-      context 'with KAS disabled' do
-        cached(:chef_run) do
-          RSpec::Mocks.with_temporary_scope do
-            stub_gitlab_rb(
-              gitlab_kas: { enable: false }
-            )
-          end
-
-          ChefSpec::SoloRunner.new.converge('gitlab::default')
-        end
-
-        it_behaves_like 'creates the KAS template'
-      end
-
-      context 'with KAS enabled' do
-        cached(:chef_run) do
-          ChefSpec::SoloRunner.new.converge('gitlab::default')
-        end
-
-        it_behaves_like 'creates the KAS template'
-
-        it 'template triggers notifications' do
-          expect(templatesymlink).to notify('runit_service[gitlab-kas]').to(:restart).delayed
-          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
-          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
-        end
-      end
-
-      context 'with specific gitlab_kas_secret' do
-        let(:api_secret_key) { SecureRandom.base64(32) }
-
-        cached(:chef_run) do
-          RSpec::Mocks.with_temporary_scope do
-            stub_gitlab_rb(
-              gitlab_kas: { api_secret_key: api_secret_key }
-            )
-          end
-
-          ChefSpec::SoloRunner.new.converge('gitlab::default')
-        end
-
-        it 'renders the correct node attribute' do
-          expect(chef_run).to create_templatesymlink('Create a gitlab_kas_secret and create a symlink to Rails root').with_variables(
-            secret_token: api_secret_key
-          )
-        end
-
-        it_behaves_like 'creates the KAS template'
-
-        it 'template triggers notifications' do
-          expect(templatesymlink).to notify('runit_service[gitlab-kas]').to(:restart).delayed
-          expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed
-          expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed
-        end
-      end
-    end
-  end
-
-  describe 'GitLab Registry files' do
-    describe 'gitlab-registry.key file' do
-      context 'Registry is disabled' do
-        it 'does not generate gitlab-registry.key file' do
-          expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key")
-        end
-      end
-
-      context 'Registry is enabled' do
-        context 'with default configuration' do
-          before do
-            stub_gitlab_rb(
-              gitlab_rails: {
-                registry_enabled: true
-              }
-            )
-          end
-
-          it 'generates key file in the default location' do
-            expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key").with_content(/\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m)
-          end
-        end
-
-        context 'with user specified configuration' do
-          context 'when location of key file is specified' do
-            before do
-              stub_gitlab_rb(
-                gitlab_rails: {
-                  registry_enabled: true,
-                  registry_key_path: '/fake/path'
-                }
-              )
-            end
-
-            it 'generates key file in the specified location' do
-              expect(chef_run).to render_file("/fake/path").with_content(/\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m)
-            end
-          end
-
-          context 'when key content is specified' do
-            before do
-              stub_gitlab_rb(
-                gitlab_rails: {
-                  registry_enabled: true
-                },
-                registry: {
-                  internal_key: 'foobar'
-                }
-              )
-            end
-
-            it 'generates key file with specified content' do
-              expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key").with_content('foobar')
-            end
-          end
-        end
-      end
-    end
-  end
-
-  context 'SMTP settings' do
-    context 'when connection pooling is not configured' do
-      it 'creates smtp_settings.rb with pooling disabled' do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            smtp_enable: true
-          }
-        )
-
-        expect(chef_run).to create_templatesymlink('Create a smtp_settings.rb and create a symlink to Rails root').with_variables(
-          hash_including(
-            'smtp_pool' => false
-          )
-        )
-      end
-    end
-
-    context 'when connection pooling is enabled' do
-      it 'creates smtp_settings.rb with pooling enabled' do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            smtp_enable: true,
-            smtp_pool: true
-          }
-        )
-
-        expect(chef_run).to create_templatesymlink('Create a smtp_settings.rb and create a symlink to Rails root').with_variables(
-          hash_including(
-            'smtp_pool' => true
-          )
-        )
-
-        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/smtp_settings.rb').with_content { |content|
-          expect(content).to include('ActionMailer::Base.delivery_method = :smtp_pool')
-        }
-      end
-    end
-  end
-
-  describe 'logrotate settings' do
-    context 'default values' do
-      it_behaves_like 'configured logrotate service', 'gitlab-pages', 'git', 'git'
-    end
-
-    context 'specified username and group' do
-      before do
-        stub_gitlab_rb(
-          user: {
-            username: 'foo',
-            group: 'bar'
-          }
-        )
-      end
-
-      it_behaves_like 'configured logrotate service', 'gitlab-pages', 'foo', 'bar'
-    end
-  end
-
-  describe 'cleaning up the legacy sidekiq log symlink' do
-    it 'removes the link if it existed' do
-      allow(File).to receive(:symlink?).with('/var/log/gitlab/gitlab-rails/sidekiq.log') { true }
-
-      expect(chef_run).to delete_link('/var/log/gitlab/gitlab-rails/sidekiq.log')
-    end
-
-    it 'does nothing if it did not exist' do
-      allow(File).to receive(:symlink?).with('/var/log/gitlab/gitlab-rails/sidekiq.log') { false }
-
-      expect(chef_run).not_to delete_link('/var/log/gitlab/gitlab-rails/sidekiq.log')
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-shell_spec.rb b/spec/chef/recipes/gitlab-shell_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXNoZWxsX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-shell_spec.rb
+++ /dev/null
@@ -1,265 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-shell' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe 'logrotate settings' do
-    context 'default values' do
-      it_behaves_like 'configured logrotate service', 'gitlab-shell', 'git', 'git'
-    end
-
-    context 'specified username and group' do
-      before do
-        stub_gitlab_rb(
-          user: {
-            username: 'foo',
-            group: 'bar'
-          }
-        )
-      end
-
-      it_behaves_like 'configured logrotate service', 'gitlab-shell', 'foo', 'bar'
-    end
-  end
-
-  it 'defaults the auth_file to be within the user\'s home directory' do
-    stub_gitlab_rb(user: { home: '/tmp/user' })
-    expect(chef_run.node['gitlab']['gitlab-shell']['auth_file']).to eq('/tmp/user/.ssh/authorized_keys')
-  end
-
-  it 'uses custom auth_files set in gitlab.rb' do
-    stub_gitlab_rb(user: { home: '/tmp/user' }, gitlab_shell: { auth_file: '/tmp/authorized_keys' })
-    expect(chef_run.node['gitlab']['gitlab-shell']['auth_file']).to eq('/tmp/authorized_keys')
-  end
-
-  it 'creates authorized_keys file if missing' do
-    stub_gitlab_rb(gitlab_shell: { auth_file: '/tmp/authorized_keys' })
-    expect(chef_run).to create_file_if_missing('/tmp/authorized_keys')
-  end
-
-  context 'with default settings' do
-    it 'create config file in default location with default values' do
-      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
-        hash_including(
-          log_file: '/var/log/gitlab/gitlab-shell/gitlab-shell.log',
-          log_format: "json",
-          migration: { enabled: true, features: [] },
-          gitlab_url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
-          gitlab_relative_path: '',
-          ssl_cert_dir: '/opt/gitlab/embedded/ssl/certs/'
-        )
-      )
-    end
-  end
-
-  context 'with a non-default directory' do
-    before do
-      stub_gitlab_rb(gitlab_shell: {
-                       dir: '/export/gitlab/gitlab-shell',
-                     })
-    end
-
-    it 'create config file in specified location with default values' do
-      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_link_to('/export/gitlab/gitlab-shell/config.yml')
-    end
-  end
-
-  context 'with a non-default log directory' do
-    before do
-      stub_gitlab_rb(gitlab_shell: {
-                       log_directory: '/tmp/log',
-                       git_trace_log_file: '/tmp/log/gitlab-shell-git-trace.log'
-                     })
-    end
-
-    it 'create config file with provided values' do
-      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
-        hash_including(
-          log_file: '/tmp/log/gitlab-shell.log',
-          git_trace_log_file: '/tmp/log/gitlab-shell-git-trace.log'
-        )
-      )
-    end
-  end
-
-  context 'when using the default auth_file location' do
-    before { stub_gitlab_rb(user: { home: '/tmp/user' }) }
-
-    it 'creates the ssh dir in the user\'s home directory' do
-      expect(chef_run).to create_storage_directory('/tmp/user/.ssh').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the config file with the auth_file within user\'s ssh directory' do
-      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
-        hash_including(
-          authorized_keys: '/tmp/user/.ssh/authorized_keys'
-        )
-      )
-    end
-  end
-
-  context 'when using a different location for auth_file' do
-    before { stub_gitlab_rb(user: { home: '/tmp/user' }, gitlab_shell: { auth_file: '/tmp/ssh/authorized_keys' }) }
-
-    it 'creates the ssh dir in the user\'s home directory' do
-      expect(chef_run).to create_storage_directory('/tmp/user/.ssh').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the auth_file\'s parent directory' do
-      expect(chef_run).to create_storage_directory('/tmp/ssh').with(owner: 'git', group: 'git', mode: '0700')
-    end
-
-    it 'creates the config file with the auth_file at the specified location' do
-      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
-        hash_including(
-          authorized_keys: '/tmp/ssh/authorized_keys'
-        )
-      )
-    end
-  end
-
-  context 'when manage-storage-directories is disabled' do
-    before { stub_gitlab_rb(user: { home: '/tmp/user' }, manage_storage_directories: { enable: false }) }
-
-    it 'doesn\'t create the ssh dir in the user\'s home directory' do
-      expect(chef_run).not_to run_ruby_block('directory resource: /tmp/user/.ssh')
-    end
-  end
-
-  context 'with custom settings' do
-    before do
-      stub_gitlab_rb(
-        gitlab_shell: {
-          log_format: 'json',
-          migration: {
-            enabled: true,
-            features: ['discover']
-          }
-        }
-      )
-    end
-
-    it 'creates the config file with custom values' do
-      expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
-        hash_including(
-          log_format: 'json',
-          migration: {
-            enabled: true,
-            features: ['discover']
-          }
-        )
-      )
-    end
-
-    context 'migration is disabled (set to false)' do
-      before do
-        stub_gitlab_rb(
-          gitlab_shell: {
-            migration: {
-              enabled: false,
-              features: []
-            }
-          }
-        )
-      end
-
-      it 'creates the config file with migration disabled' do
-        expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
-          hash_including(
-            migration: {
-              enabled: false,
-              features: []
-            }
-          )
-        )
-      end
-    end
-
-    context 'with a non-default workhorse unix socket' do
-      context 'without sockets_directory defined' do
-        before do
-          stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/fake/workhorse/socket' })
-        end
-
-        it 'create config file with provided values' do
-          expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
-            hash_including(
-              gitlab_url: 'http+unix://%2Ffake%2Fworkhorse%2Fsocket',
-              gitlab_relative_path: ''
-            )
-          )
-        end
-      end
-
-      context 'with sockets_directory defined' do
-        before do
-          stub_gitlab_rb(gitlab_workhorse: { 'sockets_directory': '/fake/workhorse/sockets/' })
-        end
-
-        it 'create config file with provided values' do
-          expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
-            hash_including(
-              gitlab_url: 'http+unix://%2Ffake%2Fworkhorse%2Fsockets%2Fsocket',
-              gitlab_relative_path: ''
-            )
-          )
-        end
-      end
-    end
-
-    context 'with a tcp workhorse listener' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'http://example.com/gitlab',
-          gitlab_workhorse: {
-            listen_network: 'tcp',
-            listen_addr: 'localhost:1234'
-          }
-        )
-      end
-
-      it 'create config file with provided values' do
-        expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
-          hash_including(
-            gitlab_url: 'http://localhost:1234/gitlab',
-            gitlab_relative_path: nil
-          )
-        )
-      end
-    end
-
-    context 'with relative path in external_url' do
-      before do
-        stub_gitlab_rb(external_url: 'http://example.com/gitlab')
-      end
-
-      it 'create config file with provided values' do
-        expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
-          hash_including(
-            gitlab_url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
-            gitlab_relative_path: '/gitlab'
-          )
-        )
-      end
-    end
-
-    context 'with internal_api_url specified' do
-      before do
-        stub_gitlab_rb(gitlab_rails: { internal_api_url: 'http://localhost:8080' })
-      end
-
-      it 'create config file with provided values' do
-        expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
-          hash_including(
-            gitlab_url: 'http://localhost:8080',
-            gitlab_relative_path: ''
-          )
-        )
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/gitlab-workhorse_spec.rb b/spec/chef/recipes/gitlab-workhorse_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvZ2l0bGFiLXdvcmtob3JzZV9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/gitlab-workhorse_spec.rb
+++ /dev/null
@@ -1,580 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-workhorse' do
-  let(:node_cpus) { 1 }
-  let(:chef_run) do
-    ChefSpec::SoloRunner.new(step_into: %w(runit_service)) do |node|
-      node.automatic['cpu']['total'] = node_cpus
-    end.converge('gitlab::default')
-  end
-  let(:default_vars) do
-    {
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
-      'HOME' => '/var/opt/gitlab',
-      'PATH' => '/opt/gitlab/bin:/opt/gitlab/embedded/bin:/bin:/usr/bin'
-    }
-  end
-  let(:config_file) { '/var/opt/gitlab/gitlab-workhorse/config.toml' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'by default' do
-    it 'creates a default VERSION file and restarts service' do
-      expect(chef_run).to create_version_file('Create version file for Workhorse').with(
-        version_file_path: '/var/opt/gitlab/gitlab-workhorse/VERSION',
-        version_check_cmd: '/opt/gitlab/embedded/bin/gitlab-workhorse --version'
-      )
-
-      expect(chef_run.version_file('Create version file for Workhorse')).to notify('runit_service[gitlab-workhorse]').to(:restart)
-    end
-
-    it 'includes both authSocket and authBackend arguments' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
-        expect(content).to match(%r(-authSocket /var/opt/gitlab/gitlab-rails/sockets/gitlab.socket))
-        expect(content).to match(%r(-authBackend http://localhost:8080))
-      }
-    end
-
-    it 'does not include alternate document root' do
-      expect(chef_run).to render_file(config_file).with_content { |content|
-        expect(content).not_to match(/alt_document_root/)
-      }
-    end
-
-    it 'does not include shutdown timeout' do
-      expect(chef_run).to render_file(config_file).with_content { |content|
-        expect(content).not_to match(/shutdown_timeout/)
-      }
-    end
-
-    it 'does not include object storage configs' do
-      expect(chef_run).to render_file(config_file).with_content { |content|
-        expect(content).not_to match(/object_storage/)
-      }
-    end
-
-    it 'does not propagate correlation ID' do
-      expect(chef_run).to render_file(config_file).with_content { |content|
-        expect(content).not_to match(/propagateCorrelationID/)
-      }
-    end
-  end
-
-  context 'user and group' do
-    context 'default values' do
-      it_behaves_like "enabled runit service", "gitlab-workhorse", "root", "root"
-      it_behaves_like 'configured logrotate service', 'gitlab-workhorse', 'git', 'git'
-    end
-
-    context 'custom values' do
-      before do
-        stub_gitlab_rb(
-          user: {
-            username: 'foo',
-            group: 'bar'
-          }
-        )
-      end
-
-      it_behaves_like "enabled runit service", "gitlab-workhorse", "root", "root"
-      it_behaves_like 'configured logrotate service', 'gitlab-workhorse', 'foo', 'bar'
-    end
-  end
-
-  context 'with environment variables' do
-    context 'by default' do
-      it 'creates necessary env variable files' do
-        expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-workhorse/env').with_variables(default_vars)
-      end
-
-      context 'when a custom env variable is specified' do
-        before do
-          stub_gitlab_rb(gitlab_workhorse: { env: { 'IAM' => 'CUSTOMVAR' } })
-        end
-
-        it 'creates necessary env variable files' do
-          expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-workhorse/env').with_variables(
-            default_vars.merge(
-              {
-                'IAM' => 'CUSTOMVAR'
-              }
-            )
-          )
-        end
-      end
-    end
-  end
-
-  context 'with alternate document root' do
-    before do
-      stub_gitlab_rb(gitlab_workhorse: { alt_document_root: '/tmp/test' })
-    end
-
-    it 'includes alternate document root setting' do
-      expect(chef_run).to render_file(config_file).with_content { |content|
-        expect(content).to match(%r(alt_document_root = "/tmp/test"))
-      }
-    end
-  end
-
-  context 'with shutdown timeout' do
-    before do
-      stub_gitlab_rb(gitlab_workhorse: { shutdown_timeout: '60s' })
-    end
-
-    it 'includes alternate document root setting' do
-      expect(chef_run).to render_file(config_file).with_content { |content|
-        expect(content).to match(%r(shutdown_timeout = "60s"))
-      }
-    end
-  end
-
-  context 'auth_socket and auth_backend' do
-    context 'with only auth_socket specified' do
-      context "auth_socket set to legacy '' value" do
-        before do
-          stub_gitlab_rb(gitlab_workhorse: { auth_socket: "''" })
-        end
-
-        it 'includes both authSocket and authBackend arguments' do
-          expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
-            expect(content).to match(%r(-authSocket /var/opt/gitlab/gitlab-rails/sockets/gitlab.socket))
-            expect(content).to match(%r(-authBackend http://localhost:8080))
-          }
-        end
-      end
-    end
-
-    context 'with only auth_backend specified' do
-      before do
-        stub_gitlab_rb(gitlab_workhorse: { auth_backend: 'https://test.example.com:8080' })
-      end
-
-      it 'omits authSocket argument' do
-        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
-          expect(content).not_to match(/\-authSocket/)
-          expect(content).to match(%r(-authBackend https://test.example.com:8080))
-        }
-      end
-    end
-
-    context "with nil auth_socket" do
-      before do
-        stub_gitlab_rb(gitlab_workhorse: { auth_socket: nil })
-      end
-
-      it 'includes both authSocket and authBackend arguments' do
-        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
-          expect(content).to match(%r(-authSocket /var/opt/gitlab/gitlab-rails/sockets/gitlab.socket))
-          expect(content).to match(%r(-authBackend http://localhost:8080))
-        }
-      end
-
-      context 'with auth_backend specified' do
-        before do
-          stub_gitlab_rb(gitlab_workhorse: { auth_socket: nil, auth_backend: 'https://test.example.com:8080' })
-        end
-
-        it 'omits authSocket argument' do
-          expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
-            expect(content).not_to match(/\-authSocket/)
-            expect(content).to match(%r(-authBackend https://test.example.com:8080))
-          }
-        end
-      end
-    end
-
-    context 'with auth_backend and auth_socket set' do
-      before do
-        stub_gitlab_rb(gitlab_workhorse: { auth_socket: '/tmp/test.socket', auth_backend: 'https://test.example.com:8080' })
-      end
-
-      it 'includes both authSocket and authBackend arguments' do
-        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
-          expect(content).to match(%r(-authSocket /tmp/test.socket))
-          expect(content).to match(%r(-authBackend https://test.example.com:8080))
-        }
-      end
-    end
-  end
-
-  context 'consolidated object store settings' do
-    include_context 'object storage config'
-
-    context 'with S3 config' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            object_store: {
-              enabled: true,
-              connection: aws_connection_hash,
-              objects: object_config
-            }
-          }
-        )
-      end
-
-      it 'includes S3 credentials' do
-        expect(chef_run).to render_file(config_file).with_content { |content|
-          expect(content).to match(/\[object_storage\]\n  provider = "AWS"\n/m)
-          expect(content).to match(/\[object_storage.s3\]\n  aws_access_key_id = "AKIAKIAKI"\n  aws_secret_access_key = "secret123"\n/m)
-        }
-      end
-    end
-
-    context 'with Azure config' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            object_store: {
-              enabled: true,
-              connection: azure_connection_hash,
-              objects: object_config
-            }
-          }
-        )
-      end
-
-      it 'includes Azure credentials' do
-        expect(chef_run).to render_file(config_file).with_content { |content|
-          expect(content).to match(/\[object_storage\]\n  provider = "AzureRM"\n/m)
-          expect(content).to match(/\[object_storage.azurerm\]\n  azure_storage_account_name = "testaccount"\n  azure_storage_access_key = "1234abcd"\n/m)
-        }
-      end
-    end
-  end
-
-  context 'without api rate limiting' do
-    it 'correctly renders out the workhorse service file' do
-      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiLimit/)
-      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiQueueDuration/)
-      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiQueueLimit/)
-    end
-  end
-
-  context 'with api rate limiting' do
-    before do
-      stub_gitlab_rb(gitlab_workhorse: { api_limit: 3, api_queue_limit: 6, api_queue_duration: '1m' })
-    end
-
-    it 'correctly renders out the workhorse service file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiLimit 3 \\/)
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiQueueDuration 1m \\/)
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiQueueLimit 6 \\/)
-    end
-  end
-
-  context 'without prometheus listen address' do
-    before do
-      stub_gitlab_rb(gitlab_workhorse: { prometheus_listen_addr: '' })
-    end
-
-    it 'correctly renders out the workhorse service file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run")
-      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-prometheusListenAddr/)
-    end
-  end
-
-  context 'with prometheus listen address' do
-    before do
-      stub_gitlab_rb(gitlab_workhorse: { prometheus_listen_addr: ':9229' })
-    end
-
-    it 'correctly renders out the workhorse service file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-prometheusListenAddr :9229 \\/)
-    end
-  end
-
-  context 'without api ci long polling duration defined' do
-    it 'correctly renders out the workhorse service file' do
-      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiCiLongPollingDuration/)
-    end
-  end
-
-  context 'with api ci long polling duration defined' do
-    before do
-      stub_gitlab_rb(gitlab_workhorse: { api_ci_long_polling_duration: "60s" })
-    end
-
-    it 'correctly renders out the workhorse service file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-apiCiLongPollingDuration 60s/)
-    end
-  end
-
-  context 'with propagate_correlation_id enabled' do
-    before do
-      stub_gitlab_rb(gitlab_workhorse: { propagate_correlation_id: true })
-    end
-
-    it 'correctly renders out the workhorse service file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-propagateCorrelationID/)
-    end
-
-    context 'with trusted_cidrs_for_propagation defined' do
-      before do
-        stub_gitlab_rb(gitlab_workhorse: {
-                         propagate_correlation_id: true,
-                         trusted_cidrs_for_propagation: %w(127.0.0.1/32 192.168.0.1/8)
-                       })
-      end
-
-      it 'correctly renders out the workhorse service file' do
-        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-propagateCorrelationID/)
-      end
-
-      it 'should generate an array in the config file' do
-        expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content { |content|
-          expect(content).to include(%(trusted_cidrs_for_propagation = ["127.0.0.1/32","192.168.0.1/8"]))
-          expect(content).not_to include(%(trusted_cidrs_for_x_forwarded_for))
-        }
-      end
-    end
-
-    context 'with trusted_cidrs_for_propagation and trusted_cidrs_for_x_forwarded_for defined' do
-      before do
-        stub_gitlab_rb(gitlab_workhorse: {
-                         propagate_correlation_id: true,
-                         trusted_cidrs_for_propagation: %w(127.0.0.1/32 192.168.0.1/8),
-                         trusted_cidrs_for_x_forwarded_for: %w(1.2.3.4/16 5.6.7.8/24)
-                       })
-      end
-
-      it 'correctly renders out the workhorse service file' do
-        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-propagateCorrelationID/)
-      end
-
-      it 'should generate arrays in the config file' do
-        expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content { |content|
-          expect(content).to include(%(trusted_cidrs_for_propagation = ["127.0.0.1/32","192.168.0.1/8"]))
-          expect(content).to include(%(trusted_cidrs_for_x_forwarded_for = ["1.2.3.4/16","5.6.7.8/24"]))
-        }
-      end
-    end
-  end
-
-  context 'with log format defined as json' do
-    before do
-      stub_gitlab_rb(gitlab_workhorse: { log_format: "json" })
-    end
-
-    it 'correctly renders out the workhorse service file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-logFormat json/)
-    end
-
-    it 'renders svlogd file which will not prepend timestamp' do
-      expect(chef_run).not_to render_file("/opt/gitlab/sv/gitlab-workhorse/log/run").with_content(/\-tt/)
-    end
-  end
-
-  context 'with log format defined as text' do
-    before do
-      stub_gitlab_rb(gitlab_workhorse: { log_format: "text" })
-    end
-
-    it 'correctly renders out the workhorse service file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-logFormat text/)
-    end
-
-    it 'renders svlogd file which will prepend timestamp' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/log/run").with_content(/\-tt/)
-    end
-  end
-
-  context 'with default value for working directory' do
-    it 'should generate config file in the correct directory' do
-      expect(chef_run).to render_file(config_file)
-    end
-  end
-
-  context 'with working directory specified' do
-    before do
-      stub_gitlab_rb(gitlab_workhorse: { dir: "/home/random/dir" })
-    end
-    it 'should generate config file in the correct directory' do
-      expect(chef_run).to render_file("/home/random/dir/config.toml")
-    end
-  end
-
-  context 'with default values for redis' do
-    it 'should generate config file' do
-      content_url = 'URL = "unix:/var/opt/gitlab/redis/redis.socket"'
-      expect(chef_run).to render_file(config_file).with_content(content_url)
-      expect(chef_run).not_to render_file(config_file).with_content(/Sentinel/)
-    end
-
-    it 'should pass config file to workhorse' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content(/\-config config.toml/)
-    end
-  end
-
-  context 'with host/port/password values for redis specified and socket disabled' do
-    before do
-      stub_gitlab_rb(
-        gitlab_rails: {
-          redis_host: "10.0.15.1",
-          redis_port: "1234",
-          redis_password: 'examplepassword'
-        }
-      )
-    end
-
-    it 'should generate config file with the specified values' do
-      content_url = 'URL = "redis://:examplepassword@10.0.15.1:1234/"'
-      content_password = 'Password = "examplepassword"'
-      content_sentinel = 'Sentinel'
-      content_sentinel_master = 'SentinelMaster'
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_url)
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_password)
-      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_sentinel)
-      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_sentinel_master)
-    end
-  end
-
-  context 'with socket for redis specified' do
-    before do
-      stub_gitlab_rb(gitlab_rails: { redis_socket: "/home/random/path.socket", redis_password: 'examplepassword' })
-    end
-
-    it 'should generate config file with the specified values' do
-      content_url = 'URL = "unix:/home/random/path.socket"'
-      content_password = 'Password = "examplepassword"'
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_url)
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_password)
-      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(/Sentinel/)
-      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(/SentinelMaster/)
-    end
-  end
-
-  context 'with Sentinels specified with default master' do
-    before do
-      stub_gitlab_rb(
-        gitlab_rails: {
-          redis_sentinels: [
-            { 'host' => '127.0.0.1', 'port' => 2637 },
-            { 'host' => '127.0.8.1', 'port' => 1234 }
-          ]
-        }
-      )
-    end
-
-    it 'should generate config file with the specified values' do
-      content = 'Sentinel = ["tcp://127.0.0.1:2637", "tcp://127.0.8.1:1234"]'
-      content_url = 'URL ='
-      content_sentinel_master = 'SentinelMaster = "gitlab-redis"'
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content)
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_sentinel_master)
-      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_url)
-    end
-  end
-
-  context 'with Sentinels and master specified' do
-    before do
-      stub_gitlab_rb(
-        gitlab_rails: {
-          redis_sentinels: [
-            { 'host' => '127.0.0.1', 'port' => 26379 },
-            { 'host' => '127.0.8.1', 'port' => 12345 }
-          ]
-        },
-        redis: {
-          master_name: 'examplemaster',
-          master_password: 'examplepassword'
-        }
-      )
-    end
-
-    it 'should generate config file with the specified values' do
-      content = 'Sentinel = ["tcp://127.0.0.1:26379", "tcp://127.0.8.1:12345"]'
-      content_sentinel_master = 'SentinelMaster = "examplemaster"'
-      content_sentinel_password = 'Password = "examplepassword"'
-      content_url = 'URL ='
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content)
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_sentinel_master)
-      expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_sentinel_password)
-      expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_url)
-    end
-  end
-
-  context 'image scaler' do
-    context 'with default values' do
-      it 'sets the default maximum file size' do
-        expect(chef_run).to render_file(config_file).with_content { |content|
-          expect(content).to match(/\[image_resizer\]\n  max_scaler_procs = \d+\n  max_filesize = 250000/m)
-        }
-      end
-
-      context 'when reported CPU cores are at least 4' do
-        let(:node_cpus) { 4 }
-
-        it 'sets default max procs to half the number of cores' do
-          expect(chef_run).to render_file(config_file).with_content { |content|
-            expect(content).to match(/\[image_resizer\]\n  max_scaler_procs = 2/m)
-          }
-        end
-      end
-
-      context 'when reported CPU cores are less than 4' do
-        let(:node_cpus) { 3 }
-
-        it 'pins default max procs to 2' do
-          expect(chef_run).to render_file(config_file).with_content { |content|
-            expect(content).to match(/\[image_resizer\]\n  max_scaler_procs = 2/m)
-          }
-        end
-      end
-    end
-
-    context 'with custom values' do
-      before do
-        stub_gitlab_rb(
-          gitlab_workhorse: {
-            image_scaler_max_procs: 5,
-            image_scaler_max_filesize: 1024
-          }
-        )
-      end
-
-      it 'should generate config file with the specified values' do
-        expect(chef_run).to render_file(config_file).with_content { |content|
-          expect(content).to match(/\[image_resizer\]\n  max_scaler_procs = 5\n  max_filesize = 1024/m)
-        }
-      end
-    end
-  end
-
-  context 'with workhorse keywatcher enabled' do
-    before do
-      stub_gitlab_rb(
-        gitlab_workhorse: {
-          workhorse_keywatcher: true,
-        }
-      )
-    end
-
-    it 'should generate redis block in the configuration file' do
-      expect(chef_run).to render_file(config_file).with_content { |content|
-        expect(content).to match(/\[redis\]/m)
-      }
-    end
-  end
-
-  context 'with workhorse keywatcher disabled' do
-    before do
-      stub_gitlab_rb(
-        gitlab_workhorse: {
-          workhorse_keywatcher: false,
-        }
-      )
-    end
-
-    it 'should not generate redis block in the configuration file' do
-      expect(chef_run).to render_file(config_file).with_content { |content|
-        expect(content).not_to match(/\[redis\]/m)
-      }
-    end
-  end
-
-  include_examples "consul service discovery", "gitlab_workhorse", "workhorse"
-end
diff --git a/spec/chef/recipes/letsencrypt_spec.rb b/spec/chef/recipes/letsencrypt_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvbGV0c2VuY3J5cHRfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/letsencrypt_spec.rb
+++ /dev/null
@@ -1,227 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'enabling letsencrypt' do
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    stub_gitlab_rb(external_url: 'https://fakehost.example.com')
-  end
-
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-  context 'unspecified uses LetsEncrypt.should_auto_enable?' do
-    it 'true' do
-      allow(LetsEncrypt).to receive(:should_auto_enable?).and_return(true)
-
-      expect(chef_run).to include_recipe('letsencrypt::enable')
-    end
-
-    it 'false' do
-      allow(LetsEncrypt).to receive(:should_auto_enable?).and_return(false)
-
-      expect(chef_run).to include_recipe('letsencrypt::disable')
-      expect(chef_run).not_to include_recipe('letsencrypt::enable')
-    end
-  end
-
-  context 'specified' do
-    it 'true' do
-      stub_gitlab_rb(letsencrypt: { enable: true })
-
-      expect(chef_run).to include_recipe('letsencrypt::enable')
-    end
-
-    it 'false' do
-      stub_gitlab_rb(letsencrypt: { enable: false })
-
-      expect(chef_run).not_to include_recipe('letsencrypt::enable')
-    end
-  end
-end
-
-RSpec.describe 'letsencrypt::enable' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-  let(:node) { chef_run.node }
-
-  let(:https_redirect_block) { %r!server { ## HTTPS redirect server(.*)} ## end HTTPS redirect server!m }
-  let(:https_block) { %r!server { ## HTTPS server(.*)} ## end HTTPS server!m }
-  let(:acme_challenge_block) { %r!\s*location /.well-known/acme-challenge/ {! }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow(LetsEncrypt).to receive(:should_auto_enable?).and_return(false)
-    allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
-    allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('nginx').and_return(true)
-  end
-
-  context 'default' do
-    it 'does not run' do
-      expect(chef_run).not_to include_recipe('letsencrypt::enable')
-    end
-  end
-
-  context 'enabled' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'https://fakehost.example.com',
-        mattermost_external_url: 'https://fakemost.example.com',
-        registry_external_url: 'https://fakereg.example.com',
-        letsencrypt: {
-          enable: true
-        }
-      )
-    end
-
-    it 'is included' do
-      expect(chef_run).to include_recipe('letsencrypt::enable')
-    end
-
-    context 'when updating nginx configurations' do
-      using RSpec::Parameterized::TableSyntax
-
-      where(:redirect_config_key, :nginx_config_file) do
-        'nginx' | '/var/opt/gitlab/nginx/conf/gitlab-http.conf'
-        'mattermost-nginx' | '/var/opt/gitlab/nginx/conf/gitlab-mattermost-http.conf'
-        'registry-nginx' | '/var/opt/gitlab/nginx/conf/gitlab-registry.conf'
-      end
-
-      with_them do
-        it 'redirects http to https' do
-          expect(node['gitlab'][redirect_config_key]['redirect_http_to_https']).to be_truthy
-        end
-
-        it 'includes the well known acme challenge location block' do
-          expect(chef_run).to render_file(nginx_config_file).with_content { |content|
-            https_redirect_server = content.match(https_redirect_block)
-            https_server = content.match(https_block)
-
-            expect(https_redirect_server).not_to be_nil
-            expect(https_server).not_to be_nil
-
-            expect(https_redirect_server.to_s).to match(acme_challenge_block)
-            expect(https_server.to_s).to match(acme_challenge_block)
-          }
-        end
-      end
-    end
-
-    it 'uses http authorization by default' do
-      expect(chef_run).to include_recipe('letsencrypt::http_authorization')
-    end
-
-    it 'creates a self signed certificate' do
-      expect(chef_run).to create_acme_selfsigned('fakehost.example.com').with(
-        key: '/etc/gitlab/ssl/fakehost.example.com.key',
-        crt: '/etc/gitlab/ssl/fakehost.example.com.crt'
-      )
-    end
-
-    it 'creates a letsencrypt certificate' do
-      expect(chef_run).to create_letsencrypt_certificate('fakehost.example.com').with(
-        key: '/etc/gitlab/ssl/fakehost.example.com.key',
-        crt: '/etc/gitlab/ssl/fakehost.example.com.crt'
-      )
-    end
-
-    it 'warns the user' do
-      prod_cert = chef_run.letsencrypt_certificate('fakehost.example.com')
-      expect(prod_cert).to notify('ruby_block[display_le_message]').to(:run)
-    end
-
-    context 'auto_renew' do
-      context 'default' do
-        it 'enables crond' do
-          expect(chef_run).to include_recipe('crond::enable')
-        end
-
-        it 'adds a crond_job' do
-          expect(chef_run).to create_crond_job('letsencrypt-renew').with(
-            user: "root",
-            hour: 0,
-            minute: 31,
-            day_of_month: "*/4",
-            command: "/opt/gitlab/bin/gitlab-ctl renew-le-certs"
-          )
-        end
-
-        it 'does not log a warning' do
-          expect(LoggingHelper).not_to receive(:warning).with("Let's Encrypt is enabled, but external_url is using http")
-          chef_run.ruby_block('display_le_message').block.call
-        end
-      end
-
-      context 'false' do
-        before do
-          stub_gitlab_rb(letsencrypt: { enable: true, auto_renew: false })
-          allow_any_instance_of(PgHelper).to receive(:is_standby?).and_return false
-        end
-
-        it 'removes the letsencrypt-renew cronjob' do
-          expect(chef_run).to delete_crond_job('letsencrypt-renew')
-        end
-
-        it 'warns that we do not setup automatic renewal' do
-          chef_run.ruby_block('display_le_message').block.call
-
-          expect_logged_warning(/does not setup/)
-        end
-      end
-    end
-
-    context 'external_url uses http' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'http://plainhost.example.com',
-          letsencrypt: {
-            enable: true
-          }
-        )
-      end
-
-      it 'logs a warning' do
-        expect(chef_run).to run_ruby_block('http external-url')
-      end
-    end
-  end
-end
-
-# This should work standalone for renewal purposes
-RSpec.describe 'letsencrypt::renew' do
-  let(:chef_run) do
-    ChefSpec::SoloRunner.converge('gitlab::letsencrypt_renew')
-  end
-
-  before do
-    allow_any_instance_of(OmnibusHelper).to receive(:service_up?).and_return(false)
-    allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('nginx').and_return(true)
-  end
-
-  context 'letsencrypt enabled' do
-    before do
-      allow(Gitlab).to receive(:[]).and_call_original
-      stub_gitlab_rb(
-        external_url: 'https://standalone.fakehost.com',
-        letsencrypt: {
-          enable: true
-        }
-      )
-    end
-
-    it 'executes letsencrypt_certificate' do
-      expect(chef_run).to create_letsencrypt_certificate('standalone.fakehost.com')
-    end
-  end
-
-  context 'letsencrypt auto-enabled' do
-    before do
-      allow(Gitlab).to receive(:[]).and_call_original
-      allow(OpenSSL::X509::Certificate).to receive(:not_after).and_return(Time.now - 1)
-      stub_gitlab_rb(
-        external_url: 'https://standalone.fakehost.com'
-      )
-    end
-
-    it 'executes letsencrypt_certificate' do
-      expect(chef_run).to create_letsencrypt_certificate('standalone.fakehost.com')
-    end
-  end
-end
diff --git a/spec/chef/recipes/mailroom_spec.rb b/spec/chef/recipes/mailroom_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvbWFpbHJvb21fc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/mailroom_spec.rb
+++ /dev/null
@@ -1,291 +0,0 @@
-#
-# Copyright:: Copyright (c) 2018 GitLab Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef_helper'
-
-RSpec.describe 'gitlab::mailroom' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service templatesymlink)).converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  shared_examples 'renders Microsoft Graph config' do
-    let(:gitlab_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/gitlab.yml') }
-    let(:gitlab_yml_file_content) { ChefSpec::Renderer.new(chef_run, gitlab_yml_template).content }
-    let(:gitlab_yml) { YAML.safe_load(gitlab_yml_file_content, [], [], true, symbolize_names: true) }
-    let(:inbox_options) do
-      {
-        tenant_id: 'MY-TENANT-ID',
-        client_id: '12345',
-        client_secret: 'MY-CLIENT-SECRET'
-      }
-    end
-
-    before do
-      config_sections.each do |config_section|
-        stub_gitlab_rb(
-          gitlab_rails: {
-            "#{config_section}_enabled".to_sym => true,
-            "#{config_section}_inbox_method".to_sym => 'microsoft_graph',
-            "#{config_section}_inbox_options".to_sym => inbox_options
-          }
-        )
-      end
-    end
-
-    it 'renders gitlab.yml with the right data' do
-      config_sections.each do |config_section|
-        expect(gitlab_yml[:production][config_section][:inbox_method]).to eq('microsoft_graph')
-        expect(gitlab_yml[:production][config_section][:inbox_options]).to eq(inbox_options)
-      end
-    end
-  end
-
-  shared_examples 'configured sidekiq delivery method' do
-    let(:gitlab_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/gitlab.yml') }
-    let(:gitlab_yml_file_content) { ChefSpec::Renderer.new(chef_run, gitlab_yml_template).content }
-    let(:gitlab_yml) { YAML.safe_load(gitlab_yml_file_content, [], [], true, symbolize_names: true) }
-
-    before do
-      stub_gitlab_rb(
-        gitlab_rails: config_sections.each_with_object({}) do |config_section, memo|
-          memo.merge!(
-            "#{config_section}_enabled".to_sym => true,
-            "#{config_section}_delivery_method".to_sym => 'sidekiq'
-          )
-        end
-      )
-    end
-
-    it 'renders gitlab.yml with the right data' do
-      config_sections.each do |config_section|
-        expect(gitlab_yml[:production][config_section][:delivery_method]).to eq('sidekiq')
-      end
-    end
-  end
-
-  shared_examples 'configured webhook delivery method' do
-    let(:gitlab_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/gitlab.yml') }
-    let(:gitlab_yml_file_content) { ChefSpec::Renderer.new(chef_run, gitlab_yml_template).content }
-    let(:gitlab_yml) { YAML.safe_load(gitlab_yml_file_content, [], [], true, symbolize_names: true) }
-
-    before do
-      stub_gitlab_rb(
-        external_url: 'http://localhost/gitlab/', # Mind the last "/" character
-        gitlab_rails: config_sections.each_with_object({}) do |config_section, memo|
-          memo.merge!(
-            "#{config_section}_enabled".to_sym => true,
-            "#{config_section}_delivery_method".to_sym => 'webhook'
-          )
-        end
-      )
-    end
-
-    it 'renders gitlab.yml with the right data' do
-      config_sections.each do |config_section|
-        expect(gitlab_yml[:production][config_section][:delivery_method]).to eq('webhook')
-        expect(gitlab_yml[:production][config_section][:secret_file]).to eq(".gitlab_#{config_section}_secret")
-        expect(gitlab_yml[:production][config_section][:gitlab_url]).to eq("http://localhost/gitlab")
-      end
-    end
-
-    it 'triggers notifications' do
-      config_sections.each do |config_section|
-        templatesymlink = chef_run.templatesymlink("Create a gitlab_#{config_section}_secret and create a symlink to Rails root")
-        expect(templatesymlink).to notify('runit_service[mailroom]').to(:restart).delayed
-      end
-    end
-
-    context 'auth token is not specified' do
-      before do
-        allow(SecretsHelper).to receive(:generate_base64).with(32).and_return("a" * 32)
-      end
-
-      it 'renders the correct node attribute with auto-generated auth token' do
-        config_sections.each do |config_section|
-          expect(chef_run).to create_templatesymlink("Create a gitlab_#{config_section}_secret and create a symlink to Rails root").with(
-            owner: 'root',
-            group: 'root',
-            mode: '0644'
-          ).with_variables(
-            a_hash_including(
-              secret_token: "a" * 32
-            )
-          )
-        end
-      end
-    end
-
-    context 'auth token is set' do
-      let(:auth_token) { SecureRandom.base64(32) }
-
-      before do
-        stub_gitlab_rb(
-          external_url: 'http://localhost/gitlab/',
-          gitlab_rails: config_sections.each_with_object({}) do |config_section, memo|
-            memo.merge!(
-              "#{config_section}_enabled".to_sym => true,
-              "#{config_section}_delivery_method".to_sym => 'webhook',
-              "#{config_section}_auth_token".to_sym => "#{auth_token}-#{config_section}"
-            )
-          end
-        )
-      end
-
-      it 'renders the correct node attribute' do
-        config_sections.each do |config_section|
-          expect(chef_run).to create_templatesymlink("Create a gitlab_#{config_section}_secret and create a symlink to Rails root").with(
-            owner: 'root',
-            group: 'root',
-            mode: '0644'
-          ).with_variables(
-            a_hash_including(
-              secret_token: "#{auth_token}-#{config_section}"
-            )
-          )
-        end
-      end
-    end
-  end
-
-  describe 'when disabled' do
-    it_behaves_like 'disabled runit service', 'mailroom'
-  end
-
-  describe 'when enabled' do
-    context 'when only service_desk_email enabled' do
-      let(:config_sections) { %i[service_desk_email] }
-
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            service_desk_email_enabled: true
-          }
-        )
-      end
-
-      it_behaves_like 'enabled runit service', 'mailroom', 'root', 'root'
-      it_behaves_like 'configured logrotate service', 'mailroom', 'git', 'git'
-      it_behaves_like 'configured sidekiq delivery method'
-    end
-
-    context 'when both service_desk_email and incoming_email enabled' do
-      let(:config_sections) { %i[incoming_email service_desk_email] }
-
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            incoming_email_enabled: true,
-            service_desk_email_enabled: true
-          }
-        )
-      end
-
-      it_behaves_like 'enabled runit service', 'mailroom', 'root', 'root'
-      it_behaves_like 'configured logrotate service', 'mailroom', 'git', 'git'
-      it_behaves_like 'configured sidekiq delivery method'
-    end
-
-    context 'default values' do
-      let(:config_sections) { %i[incoming_email] }
-
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            incoming_email_enabled: true
-          }
-        )
-      end
-
-      it_behaves_like 'enabled runit service', 'mailroom', 'root', 'root'
-      it_behaves_like 'configured logrotate service', 'mailroom', 'git', 'git'
-
-      it 'uses --log-exit-as plain' do
-        expect(chef_run).to render_file("/opt/gitlab/sv/mailroom/run").with_content(/\-\-log\-exit\-as plain/)
-      end
-    end
-
-    context 'custom values' do
-      let(:config_sections) { %i[incoming_email] }
-
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            incoming_email_enabled: true
-          },
-          user: {
-            username: 'foo',
-            group: 'bar'
-          }
-        )
-      end
-
-      it_behaves_like 'enabled runit service', 'mailroom', 'root', 'root'
-      it_behaves_like 'configured logrotate service', 'mailroom', 'foo', 'bar'
-    end
-
-    context 'incoming email with Microsoft Graph' do
-      let(:config_sections) { %i[incoming_email] }
-
-      it_behaves_like 'renders Microsoft Graph config'
-    end
-
-    context 'Service Desk email with Microsoft Graph' do
-      let(:config_sections) { %i[service_desk_email] }
-
-      it_behaves_like 'renders Microsoft Graph config'
-    end
-
-    context 'Incoming Email webhook delivery method' do
-      let(:config_sections) { %i[incoming_email] }
-
-      it_behaves_like 'configured webhook delivery method'
-    end
-
-    context 'Service Desk Email webhook delivery method' do
-      let(:config_sections) { %i[incoming_email] }
-
-      it_behaves_like 'configured webhook delivery method'
-    end
-
-    context 'Both Incoming Email and Service Desk Email webhook delivery method' do
-      let(:config_sections) { %i[incoming_email service_desk_email] }
-
-      it_behaves_like 'configured webhook delivery method'
-    end
-  end
-
-  context 'with specified exit_log_format' do
-    before do
-      stub_gitlab_rb(
-        gitlab_rails: {
-          incoming_email_enabled: true
-        },
-        mailroom: {
-          exit_log_format: "json"
-        }
-      )
-    end
-
-    it 'correctly passes the --log-exit-as ' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/mailroom/run").with_content(/\-\-log\-exit\-as json/)
-    end
-
-    it_behaves_like 'enabled runit service', 'mailroom', 'root', 'root'
-    it_behaves_like 'configured logrotate service', 'mailroom', 'git', 'git'
-  end
-end
diff --git a/spec/chef/recipes/mattermost_spec.rb b/spec/chef/recipes/mattermost_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvbWF0dGVybW9zdF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/mattermost_spec.rb
+++ /dev/null
@@ -1,322 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::mattermost' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  let(:default_vars) do
-    {
-      'MM_FILESETTINGS_DIRECTORY' => '/var/opt/gitlab/mattermost/data',
-      'MM_GITLABSETTINGS_AUTHENDPOINT' => 'http://gitlab.example.com/oauth/authorize',
-      'MM_GITLABSETTINGS_ENABLE' => 'false',
-      'MM_GITLABSETTINGS_ID' => '',
-      'MM_GITLABSETTINGS_SCOPE' => '',
-      'MM_GITLABSETTINGS_SECRET' => '',
-      'MM_GITLABSETTINGS_TOKENENDPOINT' => 'http://gitlab.example.com/oauth/token',
-      'MM_GITLABSETTINGS_USERAPIENDPOINT' => 'http://gitlab.example.com/api/v4/user',
-      'MM_INSTALL_TYPE' => 'gitlab_omnibus',
-      'MM_LOGSETTINGS_FILELOCATION' => '/var/log/gitlab/mattermost',
-      'MM_PLUGINSETTINGS_CLIENTDIRECTORY' => '/var/opt/gitlab/mattermost/client-plugins',
-      'MM_PLUGINSETTINGS_DIRECTORY' => '/var/opt/gitlab/mattermost/plugins',
-      'MM_SERVICESETTINGS_ALLOWEDUNTRUSTEDINTERNALCONNECTIONS' => ' gitlab.example.com',
-      'MM_SERVICESETTINGS_ENABLEAPITEAMDELETION' => 'true',
-      'MM_SERVICESETTINGS_LISTENADDRESS' => '127.0.0.1:8065',
-      'MM_SERVICESETTINGS_SITEURL' => 'http://mattermost.example.com',
-      'MM_SQLSETTINGS_ATRESTENCRYPTKEY' => 'aabbccddeeff11223344556677889900',
-      'MM_SQLSETTINGS_DATASOURCE' => 'user=gitlab_mattermost host=/var/opt/gitlab/postgresql port=5432 dbname=mattermost_production',
-      'MM_SQLSETTINGS_DRIVERNAME' => 'postgres',
-      'MM_TEAMSETTINGS_SITENAME' => 'GitLab Mattermost',
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    stub_gitlab_rb(external_url: 'http://gitlab.example.com', mattermost_external_url: 'http://mattermost.example.com')
-    allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
-    allow_any_instance_of(PgHelper).to receive(:database_exists?).and_return(true)
-    allow(SecretsHelper).to receive(:generate_hex).with(64).and_return('aaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000')
-    allow(SecretsHelper).to receive(:generate_hex).with(16).and_return('aabbccddeeff11223344556677889900')
-  end
-
-  context 'by default' do
-    it 'creates a default VERSION file and restarts service' do
-      expect(chef_run).to create_version_file('Create version file for Mattermost').with(
-        version_file_path: '/var/opt/gitlab/mattermost/VERSION',
-        version_check_cmd: 'cat /opt/gitlab/embedded/service/mattermost/VERSION'
-      )
-
-      expect(chef_run.version_file('Create version file for Mattermost')).to notify('runit_service[mattermost]').to(:hup)
-    end
-  end
-
-  context 'service user and group' do
-    context 'default values' do
-      it_behaves_like "enabled runit service", "mattermost", "root", "root"
-    end
-
-    context 'custom user and group' do
-      before do
-        stub_gitlab_rb(
-          external_url: 'http://gitlab.example.com',
-          mattermost_external_url: 'http://mattermost.example.com',
-          mattermost: {
-            username: 'foo',
-            group: 'bar'
-          }
-        )
-      end
-
-      it_behaves_like "enabled runit service", "mattermost", "root", "root"
-    end
-  end
-
-  context 'SiteUrl setting' do
-    context 'default value' do
-      it 'creates necessary env variable files' do
-        expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
-          variables: default_vars
-        )
-      end
-    end
-
-    context 'when explicitly set' do
-      before do
-        stub_gitlab_rb(mattermost: {
-                         service_site_url: 'http://mattermost.gitlab.example'
-                       })
-      end
-
-      it 'creates necessary env variable files' do
-        expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
-          variables: default_vars.merge({ 'MM_SERVICESETTINGS_SITEURL' => 'http://mattermost.gitlab.example' })
-        )
-      end
-    end
-  end
-
-  it 'authorizes mattermost with gitlab' do
-    allow(MattermostHelper).to receive(:authorize_with_gitlab)
-
-    expect(chef_run).to run_ruby_block('authorize mattermost with gitlab')
-      .at_converge_time
-    expect(MattermostHelper).to receive(:authorize_with_gitlab)
-      .with 'http://gitlab.example.com'
-
-    chef_run.ruby_block('authorize mattermost with gitlab').block.call
-  end
-
-  it 'populates mattermost configuration options to node attributes' do
-    stub_gitlab_rb(mattermost: { enable: true, gitlab_id: 'old' })
-    allow(MattermostHelper).to receive(:authorize_with_gitlab) do |url|
-      Gitlab['mattermost']['gitlab_id'] = 'new'
-    end
-
-    expect(chef_run).to run_ruby_block('populate mattermost configuration options')
-      .at_converge_time
-
-    chef_run.ruby_block('authorize mattermost with gitlab').block.call
-    chef_run.ruby_block('populate mattermost configuration options').block.call
-
-    expect(chef_run.node['mattermost']['gitlab_id']).to eq 'new'
-  end
-
-  context 'populate env variables based on provided gitlab settings' do
-    before do
-      stub_gitlab_rb(mattermost: {
-                       enable: true,
-                       gitlab_enable: true,
-                       gitlab_id: 'gitlab_id',
-                       gitlab_secret: 'gitlab_secret',
-                       gitlab_scope: 'scope',
-                     })
-    end
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
-        variables: default_vars.merge(
-          {
-            'MM_GITLABSETTINGS_ENABLE' => 'true',
-            'MM_GITLABSETTINGS_SECRET' => 'gitlab_secret',
-            'MM_GITLABSETTINGS_ID' => 'gitlab_id',
-            'MM_GITLABSETTINGS_SCOPE' => 'scope',
-            'MM_GITLABSETTINGS_AUTHENDPOINT' => 'http://gitlab.example.com/oauth/authorize',
-            'MM_GITLABSETTINGS_TOKENENDPOINT' => 'http://gitlab.example.com/oauth/token',
-            'MM_GITLABSETTINGS_USERAPIENDPOINT' => 'http://gitlab.example.com/api/v4/user',
-          }
-        )
-      )
-    end
-  end
-
-  context 'allows overrides to the mattermost settings regarding GitLab endpoints' do
-    before do
-      stub_gitlab_rb(mattermost: {
-                       enable: true,
-                       gitlab_enable: true,
-                       gitlab_auth_endpoint: 'https://test-endpoint.example.com/test/auth',
-                       gitlab_token_endpoint: 'https://test-endpoint.example.com/test/token',
-                       gitlab_user_api_endpoint: 'https://test-endpoint.example.com/test/user/api'
-                     })
-    end
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
-        variables: default_vars.merge(
-          {
-            'MM_GITLABSETTINGS_ENABLE' => 'true',
-            'MM_GITLABSETTINGS_AUTHENDPOINT' => 'https://test-endpoint.example.com/test/auth',
-            'MM_GITLABSETTINGS_TOKENENDPOINT' => 'https://test-endpoint.example.com/test/token',
-            'MM_GITLABSETTINGS_USERAPIENDPOINT' => 'https://test-endpoint.example.com/test/user/api'
-          }
-        )
-      )
-    end
-  end
-
-  context 'gitlab is added to untrusted internal connections list' do
-    context 'when no allowed internal connections are provided by gitlab.rb' do
-      it 'creates necessary env variable files' do
-        expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(variables: default_vars)
-      end
-    end
-
-    context 'when some allowed internal connections are provided by gitlab.rb' do
-      before do
-        stub_gitlab_rb(mattermost: { enable: true, service_allowed_untrusted_internal_connections: 'localhost' })
-      end
-
-      it 'creates necessary env variable files' do
-        expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
-          variables: default_vars.merge(
-            {
-              'MM_SERVICESETTINGS_ALLOWEDUNTRUSTEDINTERNALCONNECTIONS' => 'localhost gitlab.example.com'
-            }
-          )
-        )
-      end
-    end
-  end
-
-  shared_examples 'no gitlab authorization performed' do
-    it 'does not authorize mattermost with gitlab' do
-      expect(chef_run).not_to run_ruby_block('authorize mattermost with gitlab')
-    end
-  end
-
-  context 'when gitlab authentication parameters are specified explicitly' do
-    before { stub_gitlab_rb(mattermost: { enable: true, gitlab_enable: true }) }
-
-    it_behaves_like 'no gitlab authorization performed'
-  end
-
-  context 'when gitlab-rails is disabled' do
-    before { stub_gitlab_rb(gitlab_rails: { enable: false }) }
-
-    it_behaves_like 'no gitlab authorization performed'
-
-    it 'does not add gitlab automatically to the list of allowed internal addresses' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
-        variables: default_vars.merge(
-          {
-            'MM_SERVICESETTINGS_ALLOWEDUNTRUSTEDINTERNALCONNECTIONS' => nil
-          }
-        )
-      )
-    end
-  end
-
-  context 'when database is not running' do
-    before { allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(false) }
-
-    it_behaves_like 'no gitlab authorization performed'
-  end
-
-  context 'when mattermost database does not exist' do
-    before { allow_any_instance_of(PgHelper).to receive(:database_exists?).and_return(false) }
-
-    it_behaves_like 'no gitlab authorization performed'
-  end
-
-  context 'when a custom env variable is specified' do
-    before do
-      stub_gitlab_rb(mattermost: { env: { 'IAM' => 'CUSTOMVAR' } })
-    end
-
-    it 'creates necessary env variable files' do
-      expect(chef_run).to create_env_dir('/opt/gitlab/etc/mattermost/env').with(
-        variables: default_vars.merge(
-          {
-            'IAM' => 'CUSTOMVAR'
-          }
-        )
-      )
-    end
-  end
-
-  describe 'letsencrypt' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'https://gitlab.example.com',
-        mattermost_external_url: 'https://mattermost.example.com'
-      )
-
-      allow(File).to receive(:exist?).and_call_original
-    end
-
-    describe 'HTTP to HTTPS redirection' do
-      context 'by default' do
-        it 'is enabled' do
-          expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/gitlab-mattermost-http.conf').with_content("return 301 https://mattermost.example.com:443$request_uri;")
-        end
-      end
-
-      context 'if disabled in gitlab.rb' do
-        before do
-          stub_gitlab_rb(
-            external_url: 'https://gitlab.example.com',
-            mattermost_external_url: 'https://mattermost.example.com',
-            mattermost_nginx: {
-              redirect_http_to_https: false
-            }
-          )
-        end
-
-        it 'is disabled' do
-          expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/gitlab-mattermost-http.conf')
-          expect(chef_run).not_to render_file('/var/opt/gitlab/nginx/conf/gitlab-mattermost.conf').with_content("return 301 https://mattermost.example.com:443$request_uri;")
-        end
-      end
-    end
-
-    context 'default certificate file is missing' do
-      before do
-        allow(File).to receive(:exist?).with('/etc/gitlab/ssl/mattermost.example.com.crt').and_return(false)
-      end
-
-      it 'adds itself to letsencrypt alt_names' do
-        expect(chef_run.node['letsencrypt']['alt_names']).to eql(['mattermost.example.com'])
-      end
-
-      it 'is reflected in the acme_selfsigned' do
-        expect(chef_run).to create_acme_selfsigned('gitlab.example.com').with(
-          alt_names: ['mattermost.example.com']
-        )
-      end
-    end
-
-    context 'default certificate file is present' do
-      before do
-        allow(File).to receive(:exist?).with('/etc/gitlab/ssl/mattermost.example.com.crt').and_return(true)
-      end
-
-      it 'does not alter letsencrypt alt_names' do
-        expect(chef_run.node['letsencrypt']['alt_names']).to eql([])
-      end
-
-      it 'is reflected in the acme_selfsigned' do
-        expect(chef_run).to create_acme_selfsigned('gitlab.example.com').with(
-          alt_names: []
-        )
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/nginx_spec.rb b/spec/chef/recipes/nginx_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvbmdpbnhfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/nginx_spec.rb
+++ /dev/null
@@ -1,698 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::nginx' do
-  let(:chef_runner) do
-    ChefSpec::SoloRunner.new(step_into: %w(runit_service)) do |node|
-      node.normal['gitlab']['nginx']['enable'] = true
-      node.normal['package']['install-dir'] = '/opt/gitlab'
-    end
-  end
-
-  let(:chef_run) do
-    chef_runner.converge('gitlab::config', 'gitlab::nginx')
-  end
-
-  let(:gitlab_http_config) { '/var/opt/gitlab/nginx/conf/gitlab-http.conf' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow(Gitlab).to receive(:[]).with('node') { chef_runner.node }
-
-    # generate a random number to use as error code
-    @code = rand(1000)
-    @nginx_errors = {
-      @code => {
-        'title' => 'TEST TITLE',
-        'header' => 'TEST HEADER',
-        'message' => 'TEST MESSAGE'
-      }
-    }
-  end
-
-  it_behaves_like 'enabled runit service', 'nginx', 'root', 'root'
-
-  it 'creates a custom error_page entry when a custom error is defined' do
-    allow(Gitlab).to receive(:[]).with('nginx').and_return({ 'custom_error_pages' => @nginx_errors })
-
-    expect(chef_run).to render_file(gitlab_http_config).with_content { |content|
-      expect(content).to include("error_page #{@code} /#{@code}-custom.html;")
-    }
-  end
-
-  it 'renders an error template when a custom error is defined' do
-    chef_runner.node.normal['gitlab']['nginx']['custom_error_pages'] = @nginx_errors
-    expect(chef_run).to render_file("/opt/gitlab/embedded/service/gitlab-rails/public/#{@code}-custom.html").with_content { |content|
-      expect(content).to include("TEST MESSAGE")
-    }
-  end
-
-  it 'creates a standard error_page entry when no custom error is defined' do
-    chef_runner.node.normal['gitlab']['nginx'].delete('custom_error_pages')
-    expect(chef_run).to render_file(gitlab_http_config).with_content { |content|
-      expect(content).to include("error_page 404 /404.html;")
-    }
-  end
-
-  it 'enables the proxy_intercept_errors option when custom_error_pages is defined' do
-    chef_runner.node.normal['gitlab']['nginx']['custom_error_pages'] = @nginx_errors
-    expect(chef_run).to render_file(gitlab_http_config).with_content { |content|
-      expect(content).to include("proxy_intercept_errors on")
-    }
-  end
-
-  it 'uses the default proxy_intercept_errors option when custom_error_pages is not defined' do
-    chef_runner.node.normal['gitlab']['nginx'].delete('custom_error_pages')
-    expect(chef_run).to render_file(gitlab_http_config).with_content { |content|
-      expect(content).not_to include("proxy_intercept_errors")
-    }
-  end
-end
-
-RSpec.describe 'nginx' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  subject { chef_run }
-
-  let(:gitlab_http_config) { '/var/opt/gitlab/nginx/conf/gitlab-http.conf' }
-  let(:nginx_status_config) { /include \/var\/opt\/gitlab\/nginx\/conf\/nginx-status\.conf;/ }
-
-  let(:basic_nginx_headers) do
-    {
-      "Host" => "$http_host",
-      "X-Real-IP" => "$remote_addr",
-      "X-Forwarded-Proto" => "http",
-      "X-Forwarded-For" => "$proxy_add_x_forwarded_for"
-    }
-  end
-
-  let(:http_conf) do
-    {
-      "gitlab" => "/var/opt/gitlab/nginx/conf/gitlab-http.conf",
-      "mattermost" => "/var/opt/gitlab/nginx/conf/gitlab-mattermost-http.conf",
-      "registry" => "/var/opt/gitlab/nginx/conf/gitlab-registry.conf",
-      "pages" => "/var/opt/gitlab/nginx/conf/gitlab-pages.conf",
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when http external urls are being used' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'http://localhost',
-        mattermost_external_url: 'http://mattermost.localhost',
-        registry_external_url: 'http://registry.localhost',
-        pages_external_url: 'http://pages.localhost'
-      )
-    end
-
-    it 'properly sets the default nginx proxy headers' do
-      expect(chef_run.node['gitlab']['nginx']['proxy_set_headers']).to eql(nginx_headers({
-                                                                                           "Host" => "$http_host_with_default",
-                                                                                           "Upgrade" => "$http_upgrade",
-                                                                                           "Connection" => "$connection_upgrade"
-                                                                                         }))
-      expect(chef_run.node['gitlab']['registry-nginx']['proxy_set_headers']).to eql(basic_nginx_headers)
-      expect(chef_run.node['gitlab']['mattermost-nginx']['proxy_set_headers']).to eql(nginx_headers({
-                                                                                                      "X-Frame-Options" => "SAMEORIGIN",
-                                                                                                      "Upgrade" => "$http_upgrade",
-                                                                                                      "Connection" => "$connection_upgrade"
-                                                                                                    }))
-      expect(chef_run.node['gitlab']['pages-nginx']['proxy_set_headers']).to eql(basic_nginx_headers)
-    end
-
-    it 'supports overriding default nginx headers' do
-      expect_headers = nginx_headers({ "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp" })
-      stub_gitlab_rb(
-        "nginx" => { proxy_set_headers: { "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp" } },
-        "mattermost_nginx" => { proxy_set_headers: { "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp" } },
-        "registry_nginx" => { proxy_set_headers: { "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp" } }
-      )
-
-      expect(chef_run.node['gitlab']['nginx']['proxy_set_headers']).to include(expect_headers)
-      expect(chef_run.node['gitlab']['mattermost-nginx']['proxy_set_headers']).to include(expect_headers)
-      expect(chef_run.node['gitlab']['registry-nginx']['proxy_set_headers']).to include(expect_headers)
-    end
-  end
-
-  context 'when https external urls are being used' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'https://localhost',
-        mattermost_external_url: 'https://mattermost.localhost',
-        registry_external_url: 'https://registry.localhost',
-        pages_external_url: 'https://pages.localhost'
-      )
-    end
-
-    it 'properly sets the default nginx proxy ssl forward headers' do
-      expect(chef_run.node['gitlab']['nginx']['proxy_set_headers']).to eql(nginx_headers({
-                                                                                           "Host" => "$http_host_with_default",
-                                                                                           "X-Forwarded-Proto" => "https",
-                                                                                           "X-Forwarded-Ssl" => "on",
-                                                                                           "Upgrade" => "$http_upgrade",
-                                                                                           "Connection" => "$connection_upgrade"
-                                                                                         }))
-
-      expect(chef_run.node['gitlab']['registry-nginx']['proxy_set_headers']).to eql(nginx_headers({
-                                                                                                    "X-Forwarded-Proto" => "https",
-                                                                                                    "X-Forwarded-Ssl" => "on"
-                                                                                                  }))
-
-      expect(chef_run.node['gitlab']['mattermost-nginx']['proxy_set_headers']).to eql(nginx_headers({
-                                                                                                      "X-Forwarded-Proto" => "https",
-                                                                                                      "X-Forwarded-Ssl" => "on",
-                                                                                                      "X-Frame-Options" => "SAMEORIGIN",
-                                                                                                      "Upgrade" => "$http_upgrade",
-                                                                                                      "Connection" => "$connection_upgrade"
-                                                                                                    }))
-
-      expect(chef_run.node['gitlab']['pages-nginx']['proxy_set_headers']).to eql(nginx_headers({
-                                                                                                 "X-Forwarded-Proto" => "https",
-                                                                                                 "X-Forwarded-Ssl" => "on"
-                                                                                               }))
-    end
-
-    it 'supports overriding default nginx headers' do
-      expect_headers = nginx_headers({ "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp", "X-Forwarded-Ssl" => "on", 'Connection' => 'close' })
-      set_headers = { "Host" => "nohost.example.com", "X-Forwarded-Proto" => "ftp", 'Connection' => 'close' }
-      stub_gitlab_rb(
-        "nginx" => { proxy_set_headers: set_headers },
-        "mattermost_nginx" => { proxy_set_headers: set_headers },
-        "registry_nginx" => { proxy_set_headers: set_headers },
-        "pages_nginx" => { proxy_set_headers: set_headers }
-      )
-
-      expect(chef_run.node['gitlab']['nginx']['proxy_set_headers']).to include(expect_headers)
-      expect(chef_run.node['gitlab']['mattermost-nginx']['proxy_set_headers']).to include(expect_headers)
-      expect(chef_run.node['gitlab']['registry-nginx']['proxy_set_headers']).to include(expect_headers)
-      expect(chef_run.node['gitlab']['pages-nginx']['proxy_set_headers']).to include(expect_headers)
-    end
-
-    it 'disables Connection header' do
-      expect_headers = nginx_headers({ "Host" => "nohost.example.com", "X-Forwarded-Proto" => "https", "X-Forwarded-Ssl" => "on" })
-      set_headers = { "Host" => "nohost.example.com", "Connection" => nil }
-      stub_gitlab_rb(
-        "nginx" => { proxy_set_headers: set_headers },
-        "mattermost_nginx" => { proxy_set_headers: set_headers },
-        "registry_nginx" => { proxy_set_headers: set_headers },
-        "pages_nginx" => { proxy_set_headers: set_headers }
-      )
-
-      expect(chef_run.node['gitlab']['nginx']['proxy_set_headers']).to include(expect_headers)
-      expect(chef_run.node['gitlab']['mattermost-nginx']['proxy_set_headers']).to include(expect_headers)
-      expect(chef_run.node['gitlab']['registry-nginx']['proxy_set_headers']).to include(expect_headers)
-      expect(chef_run.node['gitlab']['pages-nginx']['proxy_set_headers']).to include(expect_headers)
-    end
-
-    it 'does not set ssl_client_certificate by default' do
-      http_conf.each_value do |conf|
-        expect(chef_run).to render_file(conf).with_content { |content|
-          expect(content).not_to include("ssl_client_certificate")
-        }
-      end
-    end
-
-    it 'does not set ssl_verify_client by default' do
-      http_conf.each_value do |conf|
-        expect(chef_run).to render_file(conf).with_content { |content|
-          expect(content).not_to include("ssl_verify_client")
-        }
-      end
-    end
-
-    it 'does not set ssl_verify_depth by default' do
-      http_conf.each_value do |conf|
-        expect(chef_run).to render_file(conf).with_content { |content|
-          expect(content).not_to include("ssl_verify_depth")
-        }
-      end
-    end
-
-    it 'sets the default ssl_verify_depth when ssl_verify_client is defined' do
-      verify_client = { "ssl_verify_client" => "on" }
-      stub_gitlab_rb(
-        "nginx" => verify_client,
-        "mattermost_nginx" => verify_client,
-        "registry_nginx" => verify_client,
-        "pages_nginx" => verify_client
-      )
-      chef_run.converge('gitlab::default')
-      http_conf.each_value do |conf|
-        expect(chef_run).to render_file(conf).with_content { |content|
-          expect(content).to include("ssl_verify_depth 1")
-        }
-      end
-    end
-
-    it 'applies nginx verify client settings to gitlab-http' do
-      stub_gitlab_rb("nginx" => {
-                       "ssl_client_certificate" => "/etc/gitlab/ssl/gitlab-http-ca.crt",
-                       "ssl_verify_client" => "on",
-                       "ssl_verify_depth" => "2",
-                     })
-      chef_run.converge('gitlab::default')
-      expect(chef_run).to render_file(http_conf['gitlab']).with_content { |content|
-        expect(content).to include("ssl_client_certificate /etc/gitlab/ssl/gitlab-http-ca.crt")
-        expect(content).to include("ssl_verify_client on")
-        expect(content).to include("ssl_verify_depth 2")
-      }
-    end
-
-    it 'applies nginx request_buffering path regex' do
-      expect(chef_run).to render_file(http_conf['gitlab']).with_content { |content|
-        expect(content).to include("location ~ (/api/v\\d/jobs/\\d+/artifacts$|\\.git/git-receive-pack$|\\.git/gitlab-lfs/objects|\\.git/info/lfs/objects/batch$)")
-      }
-    end
-
-    it 'applies mattermost_nginx verify client settings to gitlab-mattermost-http' do
-      stub_gitlab_rb("mattermost_nginx" => {
-                       "ssl_client_certificate" => "/etc/gitlab/ssl/gitlab-mattermost-http-ca.crt",
-                       "ssl_verify_client" => "on",
-                       "ssl_verify_depth" => "3",
-                     })
-      chef_run.converge('gitlab::default')
-      expect(chef_run).to render_file(http_conf['mattermost']).with_content { |content|
-        expect(content).to include("ssl_client_certificate /etc/gitlab/ssl/gitlab-mattermost-http-ca.crt")
-        expect(content).to include("ssl_verify_client on")
-        expect(content).to include("ssl_verify_depth 3")
-      }
-    end
-
-    it 'applies registry_nginx verify client settings to gitlab-registry' do
-      stub_gitlab_rb("registry_nginx" => {
-                       "ssl_client_certificate" => "/etc/gitlab/ssl/gitlab-registry-ca.crt",
-                       "ssl_verify_client" => "off",
-                       "ssl_verify_depth" => "5",
-                     })
-      chef_run.converge('gitlab::default')
-      expect(chef_run).to render_file(http_conf['registry']).with_content { |content|
-        expect(content).to include("ssl_client_certificate /etc/gitlab/ssl/gitlab-registry-ca.crt")
-        expect(content).to include("ssl_verify_client off")
-        expect(content).to include("ssl_verify_depth 5")
-      }
-    end
-
-    it 'applies pages_nginx verify client settings to gitlab-pages' do
-      stub_gitlab_rb("pages_nginx" => {
-                       "ssl_client_certificate" => "/etc/gitlab/ssl/gitlab-pages-ca.crt",
-                       "ssl_verify_client" => "on",
-                       "ssl_verify_depth" => "7",
-                     })
-      chef_run.converge('gitlab::default')
-      expect(chef_run).to render_file(http_conf['pages']).with_content { |content|
-        expect(content).to include("ssl_client_certificate /etc/gitlab/ssl/gitlab-pages-ca.crt")
-        expect(content).to include("ssl_verify_client on")
-        expect(content).to include("ssl_verify_depth 7")
-      }
-    end
-  end
-
-  context 'when is enabled' do
-    it 'enables nginx status by default' do
-      expect(chef_run.node['gitlab']['nginx']['status']).to eql({
-                                                                  "enable" => true,
-                                                                  "listen_addresses" => ["*"],
-                                                                  "fqdn" => "localhost",
-                                                                  "port" => 8060,
-                                                                  "vts_enable" => true,
-                                                                  "options" => {
-                                                                    "server_tokens" => "off",
-                                                                    "access_log" => "off",
-                                                                    "allow" => "127.0.0.1",
-                                                                    "deny" => "all"
-                                                                  }
-                                                                })
-      expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/nginx.conf').with_content(nginx_status_config)
-    end
-
-    it "supports overrading nginx status default configuration" do
-      custom_nginx_status_config = {
-        "enable" => true,
-        "listen_addresses" => ["127.0.0.1"],
-        "fqdn" => "dev.example.com",
-        "port" => 9999,
-        "vts_enable" => true,
-        "options" => {
-          "server_tokens" => "off",
-          "access_log" => "on",
-          "allow" => "127.0.0.1",
-          "deny" => "all"
-        }
-      }
-
-      stub_gitlab_rb("nginx" => {
-                       "status" => custom_nginx_status_config
-                     })
-
-      chef_run.converge('gitlab::default')
-
-      expect(chef_run.node['gitlab']['nginx']['status']).to eql(custom_nginx_status_config)
-    end
-
-    it "will not load the nginx status config if nginx status is disabled" do
-      stub_gitlab_rb("nginx" => { "status" => { "enable" => false } })
-      expect(chef_run).not_to render_file('/var/opt/gitlab/nginx/conf/nginx.conf').with_content(nginx_status_config)
-    end
-
-    it 'defaults to redirect_http_to_https off' do
-      expect(chef_run.node['gitlab']['nginx']['redirect_http_to_https']).to be false
-      expect(chef_run).to render_file(gitlab_http_config).with_content { |content|
-        expect(content).not_to include('return 301 https://fauxhai.local:80$request_uri;')
-      }
-    end
-
-    it 'enables redirect when redirect_http_to_https is true' do
-      stub_gitlab_rb(nginx: { listen_https: true, redirect_http_to_https: true })
-      expect(chef_run.node['gitlab']['nginx']['redirect_http_to_https']).to be true
-      expect(chef_run).to render_file(gitlab_http_config).with_content('return 301 https://fauxhai.local:80$request_uri;')
-    end
-
-    context 'when smartcard authentication is enabled' do
-      let(:gitlab_smartcard_http_config) { '/var/opt/gitlab/nginx/conf/gitlab-smartcard-http.conf' }
-
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            smartcard_enabled: true
-          },
-          nginx: { listen_https: true }
-        )
-      end
-
-      it 'listens on a separate port' do
-        expect(chef_run).to render_file(gitlab_smartcard_http_config).with_content { |content|
-          expect(content).to include('server_name fauxhai.local;')
-          expect(content).to include('listen *:3444 ssl http2;')
-        }
-      end
-
-      it 'requires client side certificate' do
-        expect(chef_run).to render_file(gitlab_smartcard_http_config).with_content { |content|
-          expect(content).to include('ssl_client_certificate /etc/gitlab/ssl/CA.pem')
-          expect(content).to include('ssl_verify_client on')
-          expect(content).to include('ssl_verify_depth 2')
-        }
-      end
-
-      it 'forwards client side certificate in header' do
-        expect(chef_run).to render_file(gitlab_smartcard_http_config).with_content('proxy_set_header X-SSL-Client-Certificate')
-      end
-
-      context 'when smartcard_client_certificate_required_host is set' do
-        before do
-          stub_gitlab_rb(
-            gitlab_rails: {
-              smartcard_enabled: true,
-              smartcard_client_certificate_required_host: 'smartcard.fauxhai.local'
-            },
-            nginx: { listen_https: true }
-          )
-        end
-
-        it 'sets smartcard nginx server name' do
-          expect(chef_run).to render_file(gitlab_smartcard_http_config).with_content { |content|
-            expect(content).to include('server_name smartcard.fauxhai.local;')
-            expect(content).to include('listen *:3444 ssl http2;')
-          }
-        end
-      end
-    end
-
-    context 'when smartcard authentication is disabled' do
-      let(:gitlab_smartcard_http_config) { '/var/opt/gitlab/nginx/conf/gitlab-smartcard-http.conf' }
-
-      before do
-        stub_gitlab_rb(gitlab_rails: { smartcard_enabled: false })
-      end
-
-      it 'should not add the gitlab smartcard config' do
-        expect(chef_run).not_to render_file(gitlab_smartcard_http_config)
-      end
-    end
-  end
-
-  context 'when is disabled' do
-    it 'should not add the nginx status config' do
-      stub_gitlab_rb("nginx" => { "enable" => false })
-      expect(chef_run).not_to render_file('/var/opt/gitlab/nginx/conf/nginx.conf').with_content(nginx_status_config)
-    end
-  end
-
-  context 'when grafana is enabled' do
-    before do
-      stub_gitlab_rb(
-        grafana: { enable: true }
-      )
-    end
-
-    it 'applies nginx grafana proxy' do
-      expect(chef_run).to render_file(http_conf['gitlab']).with_content { |content|
-        expect(content).to include('location /-/grafana/ {')
-        expect(content).to include('proxy_pass http://localhost:3000/;')
-      }
-    end
-  end
-
-  context 'when KAS is enabled' do
-    before do
-      stub_gitlab_rb(
-        gitlab_kas: { enable: true }
-      )
-    end
-
-    it 'applies nginx KAS proxy' do
-      expect(chef_run).to render_file(http_conf['gitlab']).with_content { |content|
-        expect(content).to include('location = /-/kubernetes-agent/ {')
-        expect(content).to include('proxy_pass http://localhost:8150/;')
-
-        expect(content).to include('location /-/kubernetes-agent/k8s-proxy/ {')
-        expect(content).to include('proxy_pass http://localhost:8154/;')
-      }
-    end
-  end
-
-  context 'when hsts is disabled' do
-    before do
-      stub_gitlab_rb(nginx: { hsts_max_age: 0 })
-    end
-    it { is_expected.not_to render_file(gitlab_http_config).with_content(/add_header Strict-Transport-Security/) }
-  end
-
-  it { is_expected.to render_file(gitlab_http_config).with_content(/add_header Strict-Transport-Security "max-age=63072000";/) }
-
-  context 'when referrer_policy is disabled' do
-    before do
-      stub_gitlab_rb(nginx: { referrer_policy: false })
-    end
-
-    it { is_expected.not_to render_file(gitlab_http_config).with_content(/add_header Referrer-Policy/) }
-  end
-
-  context 'when referrer_policy is set to origin' do
-    before do
-      stub_gitlab_rb(nginx: { referrer_policy: 'origin' })
-    end
-
-    it { is_expected.to render_file(gitlab_http_config).with_content(/add_header Referrer-Policy origin;/) }
-  end
-
-  it { is_expected.to render_file(gitlab_http_config).with_content(/add_header Referrer-Policy strict-origin-when-cross-origin;/) }
-
-  context 'when gzip is disabled' do
-    before do
-      stub_gitlab_rb(nginx: { gzip_enabled: false })
-    end
-    it { is_expected.to render_file(gitlab_http_config).with_content(/gzip off;/) }
-  end
-
-  it { is_expected.to render_file(gitlab_http_config).with_content(/gzip on;/) }
-
-  context 'when include_subdomains is enabled' do
-    before do
-      stub_gitlab_rb(nginx: { hsts_include_subdomains: true })
-    end
-
-    it { is_expected.to render_file(gitlab_http_config).with_content(/add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";/) }
-  end
-
-  context 'when max-age is set to 10' do
-    before do
-      stub_gitlab_rb(nginx: { hsts_max_age: 10 })
-    end
-
-    it { is_expected.to render_file(gitlab_http_config).with_content(/"max-age=10[^"]*"/) }
-  end
-
-  context 'when error log level is set to debug' do
-    before do
-      stub_gitlab_rb(nginx: { error_log_level: 'debug' })
-    end
-    it { is_expected.to render_file(gitlab_http_config).with_content(/error_log   \/var\/log\/gitlab\/nginx\/gitlab_error.log debug;/) }
-  end
-
-  it { is_expected.to render_file(gitlab_http_config).with_content(/error_log   \/var\/log\/gitlab\/nginx\/gitlab_error.log error;/) }
-
-  context 'when NGINX RealIP module is configured' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'https://localhost',
-        mattermost_external_url: 'https://mattermost.localhost',
-        registry_external_url: 'https://registry.localhost',
-        pages_external_url: 'https://pages.localhost'
-      )
-    end
-
-    context 'when real_ip_header is configured' do
-      before do
-        stub_gitlab_rb(
-          nginx: { real_ip_header: 'X-FAKE' },
-          mattermost_nginx: { real_ip_header: 'X-FAKE' },
-          registry_nginx: { real_ip_header: 'X-FAKE' },
-          pages_nginx: { real_ip_header: 'X-FAKE' }
-        )
-      end
-
-      it 'populates all config with real_ip_header' do
-        http_conf.each_value do |conf|
-          expect(chef_run).to render_file(conf).with_content(/real_ip_header X-FAKE/)
-        end
-      end
-    end
-
-    context 'when real_ip_recursive is configured' do
-      before do
-        stub_gitlab_rb(
-          nginx: { real_ip_recursive: 'On' },
-          mattermost_nginx: { real_ip_recursive: 'On' },
-          registry_nginx: { real_ip_recursive: 'On' },
-          pages_nginx: { real_ip_recursive: 'On' }
-        )
-      end
-
-      it 'populates all config with real_up_recursive' do
-        http_conf.each_value do |conf|
-          expect(chef_run).to render_file(conf).with_content(/real_ip_recursive On/)
-        end
-      end
-    end
-
-    context 'when real_ip_trusted_addresses is configured' do
-      before do
-        stub_gitlab_rb(
-          nginx: { real_ip_trusted_addresses: %w(one two three) },
-          mattermost_nginx: { real_ip_trusted_addresses: %w(one two three) },
-          registry_nginx: { real_ip_trusted_addresses: %w(one two three) },
-          pages_nginx: { real_ip_trusted_addresses: %w(one two three) }
-        )
-      end
-
-      it 'populates all config with all items for real_ip_trusted_addresses' do
-        http_conf.each_value do |conf|
-          expect(chef_run).to render_file(conf).with_content { |content|
-            expect(content).to match(/set_real_ip_from one/)
-            expect(content).to match(/set_real_ip_from two/)
-            expect(content).to match(/set_real_ip_from three/)
-          }
-        end
-      end
-    end
-
-    context 'when proxy_protocol is enabled' do
-      before do
-        stub_gitlab_rb(
-          nginx: { proxy_protocol: true },
-          mattermost_nginx: { proxy_protocol: true },
-          registry_nginx: { proxy_protocol: true },
-          pages_nginx: { proxy_protocol: true }
-        )
-      end
-
-      it 'applies nginx proxy_protocol settings' do
-        http_conf.each_value do |conf|
-          expect(chef_run).to render_file(conf).with_content { |content|
-            expect(content).to match(/listen .*:\d+ proxy_protocol/)
-            expect(content).to include('real_ip_header proxy_protocol;')
-            expect(content).to include('proxy_set_header X-Real-IP $proxy_protocol_addr;')
-            expect(content).to include('proxy_set_header X-Forwarded-For $proxy_protocol_addr;')
-          }
-        end
-      end
-    end
-
-    it 'does not set proxy_protocol settings by default' do
-      http_conf.each_value do |conf|
-        expect(chef_run).to render_file(conf).with_content { |content|
-          expect(content).not_to match(/listen .*:\d+ proxy_protocol/)
-          expect(content).not_to include('real_ip_header proxy_protocol;')
-          expect(content).not_to include('proxy_set_header X-Real-IP $proxy_protocol_addr;')
-          expect(content).not_to include('proxy_set_header X-Forwarded-For $proxy_protocol_addr;')
-        }
-      end
-    end
-  end
-
-  context 'for proxy_custom_buffer_size' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'https://localhost',
-        mattermost_external_url: 'https://mattermost.localhost',
-        pages_external_url: 'https://pages.localhost'
-      )
-    end
-
-    context 'when proxy_custom_buffer_size is set' do
-      before do
-        stub_gitlab_rb(
-          nginx: { proxy_custom_buffer_size: '42k' },
-          mattermost_nginx: { proxy_custom_buffer_size: '42k' },
-          pages_nginx: { proxy_custom_buffer_size: '42k' }
-        )
-      end
-
-      it 'applies nginx proxy_custom_buffer_size settings' do
-        ['gitlab', 'mattermost', 'pages'].each do |conf|
-          expect(chef_run).to render_file(http_conf[conf]).with_content { |content|
-            expect(content).to include('proxy_buffers 8 42k;')
-            expect(content).to include('proxy_buffer_size 42k;')
-          }
-        end
-      end
-    end
-
-    it 'does not set proxy_custom_buffer_size by default' do
-      ['gitlab', 'mattermost', 'pages'].each do |conf|
-        expect(chef_run).to render_file(http_conf[conf]).with_content { |content|
-          expect(content).not_to include('proxy_buffers 8 42k;')
-          expect(content).not_to include('proxy_buffer_size 42k;')
-        }
-      end
-    end
-  end
-
-  include_examples "consul service discovery", "nginx", "nginx"
-
-  describe 'logrotate settings' do
-    context 'default values' do
-      it_behaves_like 'configured logrotate service', 'nginx', 'root', 'root'
-    end
-
-    context 'specified username and group' do
-      before do
-        stub_gitlab_rb(
-          web_server: {
-            username: 'foo',
-            group: 'bar'
-          }
-        )
-      end
-
-      it_behaves_like 'configured logrotate service', 'nginx', 'root', 'root'
-    end
-  end
-
-  def nginx_headers(additional_headers)
-    basic_nginx_headers.merge(additional_headers)
-  end
-end
diff --git a/spec/chef/recipes/postgresql_spec.rb b/spec/chef/recipes/postgresql_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvcG9zdGdyZXNxbF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/postgresql_spec.rb
+++ /dev/null
@@ -1,880 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'postgresql' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service postgresql_config database_objects)).converge('gitlab::default') }
-  let(:postgresql_data_dir) { '/var/opt/gitlab/postgresql/data' }
-  let(:postgresql_ssl_cert) { File.join(postgresql_data_dir, 'server.crt') }
-  let(:postgresql_ssl_key) { File.join(postgresql_data_dir, 'server.key') }
-  let(:postgresql_conf) { File.join(postgresql_data_dir, 'postgresql.conf') }
-  let(:runtime_conf) { '/var/opt/gitlab/postgresql/data/runtime.conf' }
-  let(:pg_hba_conf) { '/var/opt/gitlab/postgresql/data/pg_hba.conf' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('best_version'))
-    allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('best_version'))
-    allow_any_instance_of(PgHelper).to receive(:running_version).and_return(PGVersion.new('best_version'))
-  end
-
-  it 'includes the postgresql::bin recipe' do
-    expect(chef_run).to include_recipe('postgresql::bin')
-  end
-
-  it 'includes the postgresql::user recipe' do
-    expect(chef_run).to include_recipe('postgresql::user')
-  end
-
-  it 'includes postgresql::sysctl recipe' do
-    expect(chef_run).to include_recipe('postgresql::sysctl')
-  end
-
-  it 'does not warn the user that a restart is needed by default' do
-    allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
-    expect(chef_run).not_to run_ruby_block('warn pending postgresql restart')
-  end
-
-  it 'includes runtime.conf in postgresql.conf' do
-    expect(chef_run).to render_file(postgresql_conf)
-      .with_content(/include 'runtime.conf'/)
-  end
-
-  context 'with default settings' do
-    it_behaves_like 'enabled runit service', 'postgresql', 'root', 'root', 'gitlab-psql', 'gitlab-psql'
-
-    context 'when rendering postgresql.conf' do
-      it 'correctly sets the shared_preload_libraries default setting' do
-        expect(chef_run.node['postgresql']['shared_preload_libraries'])
-          .to be_nil
-
-        expect(chef_run).to render_file(postgresql_conf)
-          .with_content(/shared_preload_libraries = ''/)
-      end
-
-      it 'disables archive mode' do
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/archive_mode = off/)
-      end
-
-      it 'enables SSL by default' do
-        expect(chef_run.node['postgresql']['ssl'])
-          .to eq('on')
-
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/ssl = on/)
-      end
-
-      it 'sets the default SSL cipher list' do
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(%r{ssl_ciphers = 'HIGH:MEDIUM:\+3DES:!aNULL:!SSLv3:!TLSv1'})
-      end
-
-      it 'sets the default locations of SSL certificates' do
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/ssl_cert_file = 'server.crt'/)
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/ssl_key_file = 'server.key'/)
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(%r{ssl_ca_file = '/opt/gitlab/embedded/ssl/certs/cacert.pem'})
-      end
-
-      it 'leaves synchronous_standby_names empty' do
-        expect(chef_run.node['postgresql']['synchronous_standby_names'])
-          .to eq('')
-
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/synchronous_standby_names = ''/)
-      end
-
-      it 'disables wal_log_hints setting' do
-        expect(chef_run.node['postgresql']['wal_log_hints']).to eq('off')
-
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/wal_log_hints = off/)
-      end
-
-      it 'does not set dynamic_shared_memory_type by default' do
-        expect(chef_run).not_to render_file(
-          postgresql_conf
-        ).with_content(/^dynamic_shared_memory_type = /)
-      end
-
-      it 'sets the max_locks_per_transaction setting' do
-        expect(chef_run.node['postgresql']['max_locks_per_transaction'])
-          .to eq(128)
-
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/max_locks_per_transaction = 128/)
-      end
-
-      it 'does not include gitlab-geo.conf' do
-        expect(chef_run).to render_file(postgresql_conf)
-          .with_content { |content|
-            expect(content).not_to match(/include_if_exists 'gitlab-geo.conf'/)
-          }
-      end
-    end
-
-    it 'generates a self-signed SSL certificate and key' do
-      stub_gitlab_rb(postgresql: { ssl_cert_file: 'certfile', ssl_key_file: 'keyfile' })
-
-      absolute_cert_path = File.join(postgresql_data_dir, 'certfile')
-      absolute_key_path = File.join(postgresql_data_dir, 'keyfile')
-
-      expect(chef_run).to create_file(absolute_cert_path).with(
-        user: 'gitlab-psql',
-        group: 'gitlab-psql',
-        mode: 0400
-      )
-
-      expect(chef_run).to create_file(absolute_key_path).with(
-        user: 'gitlab-psql',
-        group: 'gitlab-psql',
-        mode: 0400
-      )
-
-      expect(chef_run).to render_file(absolute_cert_path)
-        .with_content(/-----BEGIN CERTIFICATE-----/)
-      expect(chef_run).to render_file(absolute_key_path)
-        .with_content(/-----BEGIN RSA PRIVATE KEY-----/)
-    end
-
-    context 'when rendering runtime.conf' do
-      it 'correctly sets the log_line_prefix default setting' do
-        expect(chef_run.node['postgresql']['log_line_prefix'])
-          .to be_nil
-
-        expect(chef_run).to render_file(runtime_conf)
-          .with_content(/log_line_prefix = ''/)
-      end
-
-      it 'does not include log_statement by default' do
-        expect(chef_run).not_to render_file(runtime_conf)
-          .with_content(/log_statement = /)
-      end
-
-      it 'sets max_standby settings' do
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/max_standby_archive_delay = 30s/)
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/max_standby_streaming_delay = 30s/)
-      end
-
-      it 'sets archive settings' do
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/archive_command = ''/)
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/archive_timeout = 0/)
-      end
-
-      it 'sets logging directory' do
-        expect(chef_run.node['postgresql']['log_directory'])
-          .to eq('/var/log/gitlab/postgresql')
-
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(%r(^log_directory = '/var/log/gitlab/postgresql'))
-      end
-
-      it 'disables hot_standby_feedback' do
-        expect(chef_run.node['postgresql']['hot_standby_feedback'])
-          .to eq('off')
-
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/hot_standby_feedback = off/)
-      end
-
-      it 'sets the random_page_cost setting' do
-        expect(chef_run.node['postgresql']['random_page_cost'])
-          .to eq(2.0)
-
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/random_page_cost = 2\.0/)
-      end
-
-      it 'sets the log_temp_files setting' do
-        expect(chef_run.node['postgresql']['log_temp_files'])
-          .to eq(-1)
-
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/log_temp_files = -1/)
-      end
-
-      it 'disables the log_checkpoints setting' do
-        expect(chef_run.node['postgresql']['log_checkpoints'])
-          .to eq('off')
-
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/log_checkpoints = off/)
-      end
-
-      it 'sets idle_in_transaction_session_timeout' do
-        expect(chef_run.node['postgresql']['idle_in_transaction_session_timeout'])
-          .to eq('60000')
-
-        expect(chef_run).to render_file(runtime_conf)
-          .with_content(/idle_in_transaction_session_timeout = 60000/)
-      end
-
-      it 'sets effective_io_concurrency' do
-        expect(chef_run.node['postgresql']['effective_io_concurrency'])
-          .to eq(1)
-
-        expect(chef_run).to render_file(runtime_conf)
-          .with_content(/effective_io_concurrency = 1/)
-      end
-
-      it 'sets max_worker_processes' do
-        expect(chef_run.node['postgresql']['max_worker_processes'])
-          .to eq(8)
-
-        expect(chef_run).to render_file(runtime_conf)
-          .with_content(/max_worker_processes = 8/)
-      end
-
-      it 'sets max_parallel_workers_per_gather' do
-        expect(chef_run.node['postgresql']['max_parallel_workers_per_gather'])
-          .to eq(0)
-
-        expect(chef_run).to render_file(runtime_conf)
-          .with_content(/max_parallel_workers_per_gather = 0/)
-      end
-
-      it 'sets log_lock_waits' do
-        expect(chef_run.node['postgresql']['log_lock_waits'])
-          .to eq(1)
-
-        expect(chef_run).to render_file(runtime_conf)
-          .with_content(/log_lock_waits = 1/)
-      end
-
-      it 'sets deadlock_timeout' do
-        expect(chef_run.node['postgresql']['deadlock_timeout'])
-          .to eq('5s')
-
-        expect(chef_run).to render_file(runtime_conf)
-          .with_content(/deadlock_timeout = '5s'/)
-      end
-
-      it 'disables track_io_timing' do
-        expect(chef_run.node['postgresql']['track_io_timing'])
-          .to eq('off')
-
-        expect(chef_run).to render_file(runtime_conf)
-          .with_content(/track_io_timing = 'off'/)
-      end
-
-      it 'sets default_statistics_target' do
-        expect(chef_run.node['postgresql']['default_statistics_target'])
-          .to eq(1000)
-
-        expect(chef_run).to render_file(runtime_conf)
-          .with_content(/default_statistics_target = 1000/)
-      end
-
-      it 'enables the synchronous_commit setting' do
-        expect(chef_run.node['postgresql']['synchronous_commit'])
-          .to eq('on')
-
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/synchronous_commit = on/)
-      end
-    end
-
-    context 'when rendering pg_hba.conf' do
-      it 'creates a standard pg_hba.conf' do
-        expect(chef_run).to render_file(pg_hba_conf)
-          .with_content('local   all         all                               peer map=gitlab')
-      end
-
-      it 'cert authentication is disabled by default' do
-        expect(chef_run).to render_file(pg_hba_conf).with_content { |content|
-          expect(content).to_not match(/cert$/)
-        }
-      end
-    end
-  end
-
-  context 'with user specified settings' do
-    before do
-      stub_gitlab_rb(postgresql: {
-                       shared_preload_libraries: 'pg_stat_statements',
-                       archive_mode: 'on',
-                       username: 'foo',
-                       group: 'bar',
-                       ssl: 'off',
-                       ssl_crl_file: 'revoke.crl',
-                       ssl_ciphers: 'ALL',
-                       log_destination: 'csvlog',
-                       logging_collector: 'on',
-                       log_filename: 'test.log',
-                       log_file_mode: '0600',
-                       log_truncate_on_rotation: 'on',
-                       log_rotation_age: '1d',
-                       log_rotation_size: '10MB',
-                       dynamic_shared_memory_type: 'none',
-                       wal_log_hints: 'on'
-                     },
-                     geo_secondary_role: {
-                       enable: true
-                     })
-    end
-
-    it_behaves_like 'enabled runit service', 'postgresql', 'root', 'root', 'foo', 'bar'
-
-    context 'when rendering postgresql.conf' do
-      it 'correctly sets the shared_preload_libraries setting' do
-        expect(chef_run.node['postgresql']['shared_preload_libraries'])
-          .to eql('pg_stat_statements')
-
-        expect(chef_run).to render_file(postgresql_conf)
-          .with_content(/shared_preload_libraries = 'pg_stat_statements'/)
-      end
-
-      it 'enables archive mode' do
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/archive_mode = on/)
-      end
-
-      it 'disables SSL' do
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/ssl = off/)
-
-        expect(chef_run).not_to render_file(postgresql_ssl_cert)
-        expect(chef_run).not_to render_file(postgresql_ssl_key)
-      end
-
-      it 'sets the certificate revocation list' do
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/ssl_crl_file = 'revoke.crl'/)
-      end
-
-      it 'sets the SSL cipher list' do
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/ssl_ciphers = 'ALL'/)
-      end
-
-      it 'includes gitlab-geo.conf in postgresql.conf' do
-        expect(chef_run).to render_file(postgresql_conf)
-          .with_content(/include_if_exists 'gitlab-geo.conf'/)
-      end
-
-      it 'sets user specified logging parameters' do
-        expect(chef_run).to render_file(postgresql_conf).with_content { |content|
-          expect(content).to match(/logging_collector = on/)
-        }
-
-        expect(chef_run).to render_file(runtime_conf).with_content { |content|
-          expect(content).to match(/log_destination = 'csvlog'/)
-          expect(content).to match(/log_filename = 'test.log'/)
-          expect(content).to match(/log_file_mode = 0600/)
-          expect(content).to match(/log_truncate_on_rotation = on/)
-          expect(content).to match(/log_rotation_age = 1d/)
-          expect(content).to match(/log_rotation_size = 10MB/)
-        }
-      end
-
-      it 'sets the dynamic_shared_memory_type' do
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/^dynamic_shared_memory_type = none/)
-      end
-
-      it 'enables wal_log_hints' do
-        expect(chef_run).to render_file(
-          postgresql_conf
-        ).with_content(/^wal_log_hints = on/)
-      end
-    end
-
-    context 'when rendering runtime.conf' do
-      before do
-        stub_gitlab_rb(postgresql: {
-                         log_line_prefix: '%a',
-                         log_statement: 'all',
-                         max_standby_archive_delay: '60s',
-                         max_standby_streaming_delay: '120s',
-                         archive_command: 'command',
-                         archive_timeout: '120',
-                       })
-      end
-
-      it 'correctly sets the log_line_prefix setting' do
-        expect(chef_run.node['postgresql']['log_line_prefix'])
-          .to eql('%a')
-
-        expect(chef_run).to render_file(runtime_conf)
-          .with_content(/log_line_prefix = '%a'/)
-      end
-
-      it 'correctly sets the log_statement setting' do
-        expect(chef_run.node['postgresql']['log_statement'])
-          .to eql('all')
-
-        expect(chef_run).to render_file(runtime_conf)
-          .with_content(/log_statement = 'all'/)
-      end
-
-      it 'sets max_standby settings' do
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/max_standby_archive_delay = 60s/)
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/max_standby_streaming_delay = 120s/)
-      end
-
-      it 'sets archive settings' do
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/archive_command = 'command'/)
-        expect(chef_run).to render_file(
-          runtime_conf
-        ).with_content(/archive_timeout = 120/)
-      end
-    end
-
-    context 'when rendering pg_hba.conf' do
-      before do
-        stub_gitlab_rb(
-          postgresql: {
-            hostssl: true,
-            trust_auth_cidr_addresses: ['127.0.0.1/32'],
-            custom_pg_hba_entries: {
-              foo: [
-                {
-                  type: 'host',
-                  database: 'foo',
-                  user: 'bar',
-                  cidr: '127.0.0.1/32',
-                  method: 'trust'
-                }
-              ]
-            },
-            cert_auth_addresses: {
-              '1.2.3.4/32' => {
-                database: 'fakedatabase',
-                user: 'fakeuser'
-              },
-              'fakehostname' => {
-                database: 'anotherfakedatabase',
-                user: 'anotherfakeuser'
-              },
-            }
-          }
-        )
-      end
-
-      it 'prefers hostssl when configured in pg_hba.conf' do
-        expect(chef_run).to render_file(pg_hba_conf)
-          .with_content('hostssl    all         all         127.0.0.1/32           trust')
-      end
-
-      it 'adds users custom entries to pg_hba.conf' do
-        expect(chef_run).to render_file(pg_hba_conf)
-          .with_content('host foo bar 127.0.0.1/32 trust')
-      end
-
-      it 'allows cert authentication to be enabled' do
-        expect(chef_run).to render_file(pg_hba_conf).with_content('hostssl fakedatabase fakeuser 1.2.3.4/32 cert')
-        expect(chef_run).to render_file(pg_hba_conf).with_content('hostssl anotherfakedatabase anotherfakeuser fakehostname cert')
-      end
-    end
-  end
-
-  context 'when postgresql.conf changes' do
-    before do
-      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).and_call_original
-      allow_any_instance_of(OmnibusHelper).to receive(:should_notify?).with('postgresql').and_return(true)
-      allow_any_instance_of(OmnibusHelper).to receive(:service_dir_enabled?).and_call_original
-      allow_any_instance_of(OmnibusHelper).to receive(:service_dir_enabled?).with('postgresql').and_return(true)
-    end
-
-    it 'notifies reload postgresql task' do
-      expect(chef_run).to create_postgresql_config('gitlab')
-      postgresql_config = chef_run.postgresql_config('gitlab')
-      expect(postgresql_config).to notify('execute[reload postgresql]').to(:run).immediately
-      expect(postgresql_config).to notify('execute[start postgresql]').to(:run).immediately
-    end
-  end
-
-  context 'when enabling extensions' do
-    it 'creates the pg_trgm extension when it is possible' do
-      allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('pg_trgm', 'gitlabhq_production').and_return(true)
-      expect(chef_run).to enable_postgresql_extension('pg_trgm')
-    end
-
-    it 'does not create the pg_trgm extension if it is not possible' do
-      allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('pg_trgm', 'gitlabhq_production').and_return(false)
-      expect(chef_run).not_to run_execute('enable pg_trgm extension')
-    end
-
-    context 'when on a secondary database node' do
-      before do
-        allow_any_instance_of(PgHelper).to receive(:is_standby?).and_return(true)
-        allow_any_instance_of(PgHelper).to receive(:replica?).and_return(true)
-      end
-
-      it 'should not activate pg_trgm' do
-        expect(chef_run).not_to run_execute('enable pg_trgm extension')
-      end
-    end
-
-    it 'creates the btree_gist extension when it is possible' do
-      allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('btree_gist', 'gitlabhq_production').and_return(true)
-      expect(chef_run).to enable_postgresql_extension('btree_gist')
-    end
-
-    it 'does not create the btree_gist extension if it is not possible' do
-      allow_any_instance_of(PgHelper).to receive(:extension_can_be_enabled?).with('btree_gist', 'gitlabhq_production').and_return(false)
-      expect(chef_run).not_to run_execute('enable btree_gist extension')
-    end
-  end
-
-  context 'when configuring postgresql_user resources' do
-    before do
-      stub_gitlab_rb(
-        {
-          postgresql: {
-            sql_user_password: 'fakepassword',
-            sql_replication_password: 'fakepassword'
-          }
-        }
-      )
-    end
-
-    context 'when on the primary database node' do
-      before do
-        allow_any_instance_of(PgHelper).to receive(:is_standby?).and_return(false)
-      end
-
-      it 'should set a password for sql_user when sql_user_password is set' do
-        expect(chef_run).to create_postgresql_user('gitlab').with(password: 'md5fakepassword')
-      end
-
-      it 'should create the gitlab_replicator user with replication permissions' do
-        expect(chef_run).to create_postgresql_user('gitlab_replicator').with(
-          options: %w(replication),
-          password: 'md5fakepassword'
-        )
-      end
-    end
-
-    context 'when on a secondary database node' do
-      before do
-        allow_any_instance_of(PgHelper).to receive(:is_standby?).and_return(true)
-        allow_any_instance_of(PgHelper).to receive(:replica?).and_return(true)
-      end
-
-      it 'should not create users' do
-        expect(chef_run).not_to create_postgresql_user('gitlab')
-        expect(chef_run).not_to create_postgresql_user('gitlab_replicator')
-      end
-    end
-  end
-end
-
-RSpec.describe 'postgresql 13' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service postgresql_config)).converge('gitlab::default') }
-  let(:postgresql_conf) { File.join(postgresql_data_dir, 'postgresql.conf') }
-  let(:runtime_conf) { '/var/opt/gitlab/postgresql/data/runtime.conf' }
-
-  before do
-    allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('13.0'))
-    allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('13.0'))
-  end
-
-  it 'configures wal_keep_size instead of wal_keep_segments' do
-    expect(chef_run).to render_file(runtime_conf).with_content { |content|
-      expect(content).to include("wal_keep_size")
-      expect(content).not_to include("wal_keep_segments")
-    }
-  end
-end
-
-RSpec.describe 'postgresql 12' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service postgresql_config)).converge('gitlab::default') }
-  let(:postgresql_conf) { File.join(postgresql_data_dir, 'postgresql.conf') }
-  let(:runtime_conf) { '/var/opt/gitlab/postgresql/data/runtime.conf' }
-
-  before do
-    allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('12.0'))
-    allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('12.0'))
-  end
-
-  it 'configures wal_keep_segments instead of wal_keep_size' do
-    expect(chef_run).to render_file(runtime_conf).with_content { |content|
-      expect(content).to include("wal_keep_segments")
-      expect(content).to_not include("wal_keep_size")
-    }
-  end
-end
-
-RSpec.describe 'postgres when version mismatches occur' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service postgresql_config)).converge('gitlab::default') }
-  let(:postgresql_conf) { File.join(postgresql_data_dir, 'postgresql.conf') }
-  let(:runtime_conf) { '/var/opt/gitlab/postgresql/data/runtime.conf' }
-
-  context 'when data and binary versions differ' do
-    before do
-      allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('expectation'))
-      allow_any_instance_of(PgHelper).to receive(:running_version).and_return(PGVersion.new('expectation'))
-      allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('reality'))
-      allow(File).to receive(:exists?).and_call_original
-      allow(File).to receive(:exists?).with("/var/opt/gitlab/postgresql/data/PG_VERSION").and_return(true)
-      allow(Dir).to receive(:glob).and_call_original
-      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/reality*").and_return(
-        ['/opt/gitlab/embedded/postgresql/reality']
-      )
-      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/reality/bin/*").and_return(
-        %w(
-          /opt/gitlab/embedded/postgresql/reality/bin/foo_one
-          /opt/gitlab/embedded/postgresql/reality/bin/foo_two
-          /opt/gitlab/embedded/postgresql/reality/bin/foo_three
-        )
-      )
-    end
-
-    it 'corrects symlinks to the correct location' do
-      allow(FileUtils).to receive(:ln_sf).and_return(true)
-      %w(foo_one foo_two foo_three).each do |pg_bin|
-        expect(FileUtils).to receive(:ln_sf).with(
-          "/opt/gitlab/embedded/postgresql/reality/bin/#{pg_bin}",
-          "/opt/gitlab/embedded/bin/#{pg_bin}"
-        )
-      end
-      chef_run.ruby_block('Link postgresql bin files to the correct version').block.call
-    end
-
-    it 'does not warn the user that a restart is needed by default' do
-      allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
-      expect(chef_run).not_to run_ruby_block('warn pending postgresql restart')
-    end
-  end
-
-  context 'when running version and installed version differ' do
-    before do
-      allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('expectation'))
-      allow_any_instance_of(PgHelper).to receive(:running_version).and_return(PGVersion.new('reality'))
-    end
-
-    it 'warns the user that a restart is needed' do
-      allow_any_instance_of(PgHelper).to receive(:is_running?).and_return(true)
-      expect(chef_run).to run_ruby_block('warn pending postgresql restart')
-    end
-
-    it 'does not warns the user that a restart is needed when postgres is stopped' do
-      expect(chef_run).not_to run_ruby_block('warn pending postgresql restart')
-    end
-  end
-
-  context 'when an older data version is present and no longer used' do
-    before do
-      allow(Gitlab).to receive(:[]).and_call_original
-      allow_any_instance_of(PgHelper).to receive(:version).and_return(PGVersion.new('new_shiny'))
-      allow_any_instance_of(PGVersion).to receive(:major).and_return('new_shiny')
-      allow_any_instance_of(PgHelper).to receive(:running_version).and_return(PGVersion.new('new_shiny'))
-      allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('ancient_history'))
-      allow(File).to receive(:exists?).and_call_original
-      allow(File).to receive(:exists?).with("/var/opt/gitlab/postgresql/data/PG_VERSION").and_return(true)
-      allow(Dir).to receive(:glob).and_call_original
-      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/ancient_history*").and_return([])
-      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/new_shiny*").and_return(
-        ['/opt/gitlab/embedded/postgresql/new_shiny']
-      )
-      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/new_shiny/bin/*").and_return(
-        %w(
-          /opt/gitlab/embedded/postgresql/new_shiny/bin/foo_one
-          /opt/gitlab/embedded/postgresql/new_shiny/bin/foo_two
-          /opt/gitlab/embedded/postgresql/new_shiny/bin/foo_three
-        )
-      )
-    end
-
-    it 'corrects symlinks to the correct location' do
-      allow(FileUtils).to receive(:ln_sf).and_return(true)
-      %w(foo_one foo_two foo_three).each do |pg_bin|
-        expect(FileUtils).to receive(:ln_sf).with(
-          "/opt/gitlab/embedded/postgresql/new_shiny/bin/#{pg_bin}",
-          "/opt/gitlab/embedded/bin/#{pg_bin}"
-        )
-      end
-      chef_run.ruby_block('Link postgresql bin files to the correct version').block.call
-    end
-  end
-
-  context 'when the expected postgres version is missing' do
-    before do
-      allow_any_instance_of(PgHelper).to receive(:database_version).and_return(PGVersion.new('how_it_started'))
-      allow(File).to receive(:exists?).and_call_original
-      allow(File).to receive(:exists?).with("/var/opt/gitlab/postgresql/data/PG_VERSION").and_return(true)
-      allow(Dir).to receive(:glob).and_call_original
-      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/how_it_started*").and_return([])
-      allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/how_it_is_going*").and_return([])
-    end
-
-    it 'throws an error' do
-      expect do
-        chef_run.ruby_block('Link postgresql bin files to the correct version').block.call
-      end.to raise_error(RuntimeError, /Could not find PostgreSQL binaries/)
-    end
-  end
-end
-
-RSpec.describe 'postgresql::bin' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-  let(:gitlab_psql_rc) do
-    <<-EOF
-psql_user='gitlab-psql'
-psql_group='gitlab-psql'
-psql_host='/var/opt/gitlab/postgresql'
-psql_port='5432'
-    EOF
-  end
-
-  before do
-    allow(Gitlab). to receive(:[]).and_call_original
-  end
-
-  context 'when bundled postgresql is disabled' do
-    before do
-      stub_gitlab_rb(
-        postgresql: {
-          enable: false
-        }
-      )
-
-      allow(File).to receive(:exist?).and_call_original
-      allow(File).to receive(:exist?).with('/var/opt/gitlab/postgresql/data/PG_VERSION').and_return(false)
-
-      allow_any_instance_of(PgHelper).to receive(:database_version).and_return(nil)
-      version = double("PgHelper", major: 10, minor: 9)
-      allow_any_instance_of(PgHelper).to receive(:version).and_return(version)
-    end
-
-    it 'still includes the postgresql::bin recipe' do
-      expect(chef_run).to include_recipe('postgresql::bin')
-    end
-
-    it 'includes postgresql::directory_locations' do
-      expect(chef_run).to include_recipe('postgresql::directory_locations')
-    end
-
-    it 'creates gitlab-psql-rc' do
-      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-psql-rc')
-        .with_content(gitlab_psql_rc)
-    end
-
-    # We do expect the ruby block to run, but nothing to be found
-    it "doesn't link any files by default" do
-      expect(FileUtils).to_not receive(:ln_sf)
-    end
-
-    context "with postgresql['version'] set" do
-      before do
-        stub_gitlab_rb(
-          postgresql: {
-            enable: false,
-            version: '999'
-          }
-        )
-        allow(Dir).to receive(:glob).and_call_original
-        allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/999*").and_return(
-          %w(
-            /opt/gitlab/embedded/postgresql/999
-          )
-        )
-        allow(Dir).to receive(:glob).with("/opt/gitlab/embedded/postgresql/999/bin/*").and_return(
-          %w(
-            /opt/gitlab/embedded/postgresql/999/bin/foo_one
-            /opt/gitlab/embedded/postgresql/999/bin/foo_two
-            /opt/gitlab/embedded/postgresql/999/bin/foo_three
-          )
-        )
-      end
-
-      it "doesn't print a warning with a valid postgresql version" do
-        expect(chef_run).to_not run_ruby_block('check_postgresql_version')
-      end
-
-      it 'links the specified version' do
-        allow(FileUtils).to receive(:ln_sf).and_return(true)
-        %w(foo_one foo_two foo_three).each do |pg_bin|
-          expect(FileUtils).to receive(:ln_sf).with(
-            "/opt/gitlab/embedded/postgresql/999/bin/#{pg_bin}",
-            "/opt/gitlab/embedded/bin/#{pg_bin}"
-          )
-        end
-        chef_run.ruby_block('Link postgresql bin files to the correct version').block.call
-      end
-    end
-
-    context "with an invalid version in postgresql['version']" do
-      before do
-        stub_gitlab_rb(
-          postgresql: {
-            enable: false,
-            version: '888'
-          }
-        )
-        allow(Dir).to receive(:glob).and_call_original
-        allow(Dir).to receive(:glob).with('/opt/gitlab/embedded/postgresql/888*').and_return([])
-      end
-
-      it 'should print a warning' do
-        expect(chef_run).to run_ruby_block('check_postgresql_version')
-      end
-    end
-  end
-end
-
-RSpec.describe 'default directories' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'postgresql directory' do
-    context 'with default settings' do
-      it 'creates postgresql directory' do
-        expect(chef_run).to create_directory('/var/opt/gitlab/postgresql').with(owner: 'gitlab-psql', mode: '0755', recursive: true)
-      end
-    end
-
-    context 'with custom settings' do
-      before do
-        stub_gitlab_rb(
-          postgresql: {
-            dir: '/mypgdir',
-            home: '/mypghomedir'
-          })
-      end
-
-      it 'creates postgresql directory with custom path' do
-        expect(chef_run).to create_directory('/mypgdir').with(owner: 'gitlab-psql', mode: '0755', recursive: true)
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/praefect_spec.rb b/spec/chef/recipes/praefect_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvcHJhZWZlY3Rfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/praefect_spec.rb
+++ /dev/null
@@ -1,303 +0,0 @@
-require 'chef_helper'
-RSpec.describe 'praefect' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service env_dir)).converge('gitlab::default') }
-  let(:prometheus_grpc_latency_buckets) do
-    '[0.001, 0.005, 0.025, 0.1, 0.5, 1.0, 10.0, 30.0, 60.0, 300.0, 1500.0]'
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when the defaults are used' do
-    it_behaves_like 'disabled runit service', 'praefect'
-  end
-
-  context 'when praefect is enabled' do
-    let(:config_path) { '/var/opt/gitlab/praefect/config.toml' }
-    let(:env_dir) { '/opt/gitlab/etc/praefect/env' }
-    let(:auth_transitioning) { false }
-
-    before do
-      stub_gitlab_rb(praefect: {
-                       enable: true,
-                       auth_transitioning: auth_transitioning,
-                     })
-    end
-
-    it 'creates expected directories with correct permissions' do
-      expect(chef_run).to create_directory('/var/opt/gitlab/praefect').with(user: 'git', mode: '0700')
-    end
-
-    it 'creates a default VERSION file and sends hup to service' do
-      expect(chef_run).to create_version_file('Create Praefect version file').with(
-        version_file_path: '/var/opt/gitlab/praefect/VERSION',
-        version_check_cmd: '/opt/gitlab/embedded/bin/praefect --version'
-      )
-
-      expect(chef_run.version_file('Create Praefect version file')).to notify('runit_service[praefect]').to(:hup)
-    end
-
-    it 'renders the config.toml' do
-      rendered = {
-        'auth' => { 'token' => '', 'transitioning' => false },
-        'listen_addr' => 'localhost:2305',
-        'logging' => { 'format' => 'json' },
-        'prometheus_listen_addr' => 'localhost:9652',
-        'prometheus_exclude_database_from_default_metrics' => true,
-        'sentry' => {},
-        'database' => {
-          'session_pooled' => {},
-        },
-        'reconciliation' => {},
-        'failover' => { 'enabled' => true }
-      }
-
-      expect(chef_run).to render_file(config_path).with_content { |content|
-        expect(Tomlrb.parse(content)).to eq(rendered)
-      }
-      expect(chef_run).not_to render_file(config_path)
-      .with_content(%r{\[prometheus\]\s+grpc_latency_buckets =})
-    end
-
-    it 'renders the env dir files' do
-      expect(chef_run).to render_file(File.join(env_dir, "GITALY_PID_FILE"))
-        .with_content('/var/opt/gitlab/praefect/praefect.pid')
-      expect(chef_run).to render_file(File.join(env_dir, "WRAPPER_JSON_LOGGING"))
-        .with_content('true')
-      expect(chef_run).to render_file(File.join(env_dir, "SSL_CERT_DIR"))
-        .with_content('/opt/gitlab/embedded/ssl/certs/')
-    end
-
-    it 'renders the service run file with wrapper' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/praefect/run')
-        .with_content('/opt/gitlab/embedded/bin/gitaly-wrapper /opt/gitlab/embedded/bin/praefect')
-        .with_content('exec chpst -e /opt/gitlab/etc/praefect/env')
-    end
-
-    context 'with custom settings' do
-      let(:dir) { nil }
-      let(:socket_path) { '/var/opt/gitlab/praefect/praefect.socket' }
-      let(:auth_token) { 'secrettoken123' }
-      let(:auth_transitioning) { false }
-      let(:sentry_dsn) { 'https://my_key:my_secret@sentry.io/test_project' }
-      let(:sentry_environment) { 'production' }
-      let(:listen_addr) { 'localhost:4444' }
-      let(:tls_listen_addr) { 'localhost:5555' }
-      let(:certificate_path) { '/path/to/cert.pem' }
-      let(:key_path) { '/path/to/key.pem' }
-      let(:prom_addr) { 'localhost:1234' }
-      let(:separate_database_metrics) { false }
-      let(:log_level) { 'debug' }
-      let(:log_format) { 'text' }
-      let(:primaries) { %w[praefect1 praefect2] }
-      let(:virtual_storages) do
-        {
-          'default' => {
-            'default_replication_factor' => 2,
-            'nodes' => {
-              'praefect1' => { address: 'tcp://node1.internal', token: "praefect1-token" },
-              'praefect2' => { address: 'tcp://node2.internal', token: "praefect2-token" },
-              'praefect3' => { address: 'tcp://node3.internal', token: "praefect3-token" },
-              'praefect4' => { address: 'tcp://node4.internal', token: "praefect4-token" }
-            },
-            'praefect5' => { address: 'tcp://node5.internal', token: "praefect5-token" }
-          }
-        }
-      end
-      let(:failover_enabled) { true }
-      let(:database_host) { 'pg.external' }
-      let(:database_port) { 2234 }
-      let(:database_user) { 'praefect-pg' }
-      let(:database_password) { 'praefect-pg-pass' }
-      let(:database_dbname) { 'praefect_production' }
-      let(:database_sslmode) { 'require' }
-      let(:database_sslcert) { '/path/to/client-cert' }
-      let(:database_sslkey) { '/path/to/client-key' }
-      let(:database_sslrootcert) { '/path/to/rootcert' }
-      let(:database_sslrootcert) { '/path/to/rootcert' }
-      let(:database_direct_host) { 'pg.internal' }
-      let(:database_direct_port) { 1234 }
-      let(:reconciliation_scheduling_interval) { '1m' }
-      let(:reconciliation_histogram_buckets) { '[1.0, 2.0]' }
-
-      before do
-        stub_gitlab_rb(praefect: {
-                         enable: true,
-                         dir: dir,
-                         socket_path: socket_path,
-                         auth_token: auth_token,
-                         auth_transitioning: auth_transitioning,
-                         sentry_dsn: sentry_dsn,
-                         sentry_environment: sentry_environment,
-                         listen_addr: listen_addr,
-                         tls_listen_addr: tls_listen_addr,
-                         certificate_path: certificate_path,
-                         key_path: key_path,
-                         prometheus_listen_addr: prom_addr,
-                         prometheus_grpc_latency_buckets: prometheus_grpc_latency_buckets,
-                         separate_database_metrics: separate_database_metrics,
-                         logging_level: log_level,
-                         logging_format: log_format,
-                         failover_enabled: failover_enabled,
-                         virtual_storages: virtual_storages,
-                         database_host: database_host,
-                         database_port: database_port,
-                         database_user: database_user,
-                         database_password: database_password,
-                         database_dbname: database_dbname,
-                         database_sslmode: database_sslmode,
-                         database_sslcert: database_sslcert,
-                         database_sslkey: database_sslkey,
-                         database_sslrootcert: database_sslrootcert,
-                         database_direct_host: database_direct_host,
-                         database_direct_port: database_direct_port,
-                         reconciliation_scheduling_interval: reconciliation_scheduling_interval,
-                         reconciliation_histogram_buckets: reconciliation_histogram_buckets,
-                       })
-      end
-
-      it 'renders the config.toml' do
-        expect(chef_run).to render_file(config_path).with_content { |content|
-          expect(Tomlrb.parse(content)).to eq(
-            {
-              'auth' => {
-                'token' => 'secrettoken123',
-                'transitioning' => false
-              },
-              'database' => {
-                'dbname' => 'praefect_production',
-                'host' => 'pg.external',
-                'password' => 'praefect-pg-pass',
-                'port' => 2234,
-                'sslcert' => '/path/to/client-cert',
-                'sslkey' => '/path/to/client-key',
-                'sslmode' => 'require',
-                'sslrootcert' => '/path/to/rootcert',
-                'user' => 'praefect-pg',
-                'session_pooled' => {
-                  'host' => 'pg.internal',
-                  'port' => 1234,
-                }
-              },
-              'failover' => {
-                'enabled' => true
-              },
-              'logging' => {
-                'format' => 'text',
-                'level' => 'debug'
-              },
-              'listen_addr' => 'localhost:4444',
-              'prometheus' => {
-                'grpc_latency_buckets' => [0.001, 0.005, 0.025, 0.1, 0.5, 1.0, 10.0, 30.0, 60.0, 300.0, 1500.0]
-              },
-              'reconciliation' => {
-                'histogram_buckets' => [1.0, 2.0],
-                'scheduling_interval' => '1m'
-              },
-              'sentry' => {
-                'sentry_dsn' => 'https://my_key:my_secret@sentry.io/test_project',
-                'sentry_environment' => 'production'
-              },
-              'prometheus_listen_addr' => 'localhost:1234',
-              'prometheus_exclude_database_from_default_metrics' => false,
-              'socket_path' => '/var/opt/gitlab/praefect/praefect.socket',
-              'tls' => {
-                'certificate_path' => '/path/to/cert.pem',
-                'key_path' => '/path/to/key.pem'
-              },
-              'tls_listen_addr' => 'localhost:5555',
-              'virtual_storage' => [
-                {
-                  'name' => 'default',
-                  'default_replication_factor' => 2,
-                  'node' => [
-                    {
-                      'address' => 'tcp://node1.internal',
-                      'storage' => 'praefect1',
-                      'token' => 'praefect1-token'
-                    },
-                    {
-                      'address' => 'tcp://node2.internal',
-                      'storage' => 'praefect2',
-                      'token' => 'praefect2-token'
-                    },
-                    {
-                      'address' => 'tcp://node3.internal',
-                      'storage' => 'praefect3',
-                      'token' => 'praefect3-token'
-                    },
-                    {
-                      'address' => 'tcp://node4.internal',
-                      'storage' => 'praefect4',
-                      'token' => 'praefect4-token'
-                    },
-                    {
-                      'address' => 'tcp://node5.internal',
-                      'storage' => 'praefect5',
-                      'token' => 'praefect5-token'
-                    }
-                  ]
-                }
-              ]
-            }
-          )
-        }
-      end
-
-      it 'renders the env dir files correctly' do
-        expect(chef_run).to render_file(File.join(env_dir, "WRAPPER_JSON_LOGGING"))
-          .with_content('false')
-      end
-
-      context 'with virtual_storages as an array' do
-        let(:virtual_storages) { [{ name: 'default', 'nodes' => [{ storage: 'praefect1', address: 'tcp://node1.internal', token: "praefect1-token" }] }] }
-
-        it 'raises an error' do
-          expect { chef_run }.to raise_error("Praefect virtual_storages must be a hash")
-        end
-      end
-
-      context 'with duplicate virtual storage node configured via fallback' do
-        let(:virtual_storages) { { 'default' => { 'node-1' => {}, 'nodes' => { 'node-1' => {} } } } }
-
-        it 'raises an error' do
-          allow(LoggingHelper).to receive(:deprecation)
-          expect(LoggingHelper).to receive(:deprecation).with(
-            <<~EOS
-              Configuring the Gitaly nodes directly in the virtual storage's root configuration object has
-              been deprecated in GitLab 13.12 and will no longer be supported in GitLab 15.0. Move the Gitaly
-              nodes under the 'nodes' key as described in step 6 of https://docs.gitlab.com/ee/administration/gitaly/praefect.html#praefect.
-            EOS
-          )
-
-          expect { chef_run }.to raise_error("Virtual storage 'default' contains duplicate configuration for node 'node-1'")
-        end
-      end
-
-      context 'with nodes within virtual_storages as an array' do
-        let(:virtual_storages) { { 'default' => [{ storage: 'praefect1', address: 'tcp://node1.internal', token: "praefect1-token" }] } }
-
-        it 'raises an error' do
-          expect { chef_run }.to raise_error("nodes of a Praefect virtual_storage must be a hash")
-        end
-      end
-    end
-
-    describe 'database migrations' do
-      it 'runs the migrations' do
-        expect(chef_run).to run_bash('migrate praefect database')
-      end
-
-      context 'with auto_migrate off' do
-        before { stub_gitlab_rb(praefect: { auto_migrate: false }) }
-
-        it 'skips running the migrations' do
-          expect(chef_run).not_to run_bash('migrate praefect database')
-        end
-      end
-    end
-
-    include_examples "consul service discovery", "praefect", "praefect"
-  end
-end
diff --git a/spec/chef/recipes/puma_spec.rb b/spec/chef/recipes/puma_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvcHVtYV9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/puma_spec.rb
+++ /dev/null
@@ -1,260 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::puma with Ubuntu 16.04' do
-  let(:chef_run) do
-    runner = ChefSpec::SoloRunner.new(
-      step_into: %w(runit_service puma_config),
-      path: 'spec/fixtures/fauxhai/ubuntu/16.04.json'
-    )
-    runner.converge('gitlab::default')
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    stub_default_should_notify?(true)
-    stub_should_notify?('puma', true)
-  end
-
-  context 'when puma is enabled' do
-    it_behaves_like 'enabled runit service', 'puma', 'root', 'root'
-
-    describe 'logrotate settings' do
-      context 'default values' do
-        it_behaves_like 'configured logrotate service', 'puma', 'git', 'git'
-      end
-
-      context 'specified username and group' do
-        before do
-          stub_gitlab_rb(
-            user: {
-              username: 'foo',
-              group: 'bar'
-            }
-          )
-        end
-
-        it_behaves_like 'configured logrotate service', 'puma', 'foo', 'bar'
-      end
-    end
-
-    it 'creates runtime directories' do
-      expect(chef_run).to create_directory('/var/log/gitlab/puma').with(
-        owner: 'git',
-        group: nil,
-        mode: '0700'
-      )
-      expect(chef_run).to create_directory('/opt/gitlab/var/puma').with(
-        owner: 'git',
-        group: nil,
-        mode: '0700'
-      )
-      expect(chef_run).to create_directory('/var/opt/gitlab/gitlab-rails/sockets').with(
-        owner: 'git',
-        group: 'gitlab-www',
-        mode: '0750'
-      )
-    end
-
-    it 'renders the runit configuration with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/puma/run')
-        .with_content { |content|
-          expect(content).not_to match(/export prometheus_run_dir=\'\'/)
-          expect(content).not_to match(/rm \/run\/gitlab\/puma/)
-          expect(content).to match(/-u git:git/)
-          expect(content).to match(/-U git:git/)
-          expect(content).to match(/mkdir -p \/run\/gitlab\/puma/)
-          expect(content).to match(/chmod 0700 \/run\/gitlab\/puma/)
-          expect(content).to match(/chown git \/run\/gitlab\/puma/)
-          expect(content).to match(/export prometheus_run_dir=\'\/run\/gitlab\/puma\'/)
-          expect(content).to match(%r(/opt/gitlab/embedded/bin/bundle exec puma -C /var/opt/gitlab/gitlab-rails/etc/puma.rb))
-        }
-    end
-
-    it 'renders the puma.rb file' do
-      expect(chef_run).to create_puma_config('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with(
-        tag: 'gitlab-puma-worker',
-        rackup: 'config.ru',
-        environment: 'production',
-        pid: '/opt/gitlab/var/puma/puma.pid',
-        state_path: '/opt/gitlab/var/puma/puma.state',
-        listen_socket: '/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket',
-        listen_tcp: '127.0.0.1:8080',
-        working_directory: '/var/opt/gitlab/gitlab-rails/working',
-        worker_processes: 2,
-        min_threads: 4,
-        max_threads: 4
-      )
-      expect(chef_run).to create_template('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with_content { |content|
-        expect(content).to match(/lowlevel_error_handler/)
-        expect(content).to include('Raven.capture_exception')
-      }
-    end
-
-    it 'creates sysctl files' do
-      expect(chef_run).to create_gitlab_sysctl('net.core.somaxconn').with_value(1024)
-    end
-  end
-
-  context 'with custom Puma settings' do
-    before do
-      stub_gitlab_rb(
-        puma: {
-          worker_timeout: 120,
-          worker_processes: 4,
-          min_threads: 5,
-          max_threads: 10,
-          listen: '10.0.0.1',
-          port: 9000,
-          socket: '/tmp/puma.socket',
-          state_path: '/tmp/puma.state',
-          per_worker_max_memory_mb: 1000
-        }
-      )
-    end
-
-    it 'renders the puma.rb file' do
-      expect(chef_run).to create_puma_config('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with(
-        state_path: '/tmp/puma.state',
-        listen_socket: '/tmp/puma.socket',
-        listen_tcp: '10.0.0.1:9000',
-        worker_processes: 4,
-        min_threads: 5,
-        max_threads: 10,
-        per_worker_max_memory_mb: 1000
-      )
-      expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
-        expect(content).to match(%r(-authSocket /tmp/puma.socket))
-      }
-      expect(Gitlab['gitlab_workhorse']['auth_socket']).to eq('/tmp/puma.socket')
-    end
-  end
-
-  context 'with custom user and group' do
-    before do
-      stub_gitlab_rb(
-        user: {
-          username: 'foo',
-          group: 'bar'
-        }
-      )
-    end
-
-    it_behaves_like 'enabled runit service', 'puma', 'root', 'root'
-  end
-
-  context 'with custom runtime_dir' do
-    before do
-      stub_gitlab_rb(
-        runtime_dir: '/tmp/test-dir'
-      )
-    end
-
-    it 'uses the user-specific runtime_dir' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/puma/run')
-        .with_content { |content|
-          expect(content).to match(%r(export prometheus_run_dir='/tmp/test-dir/gitlab/puma'))
-          expect(content).to match(%r(mkdir -p /tmp/test-dir/gitlab/puma))
-        }
-    end
-  end
-
-  context 'with ActionCable enabled' do
-    before do
-      stub_gitlab_rb(
-        actioncable: {
-          worker_pool_size: 7
-        }
-      )
-    end
-
-    it 'renders the runit configuration with ActionCable environment variables' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/puma/run')
-        .with_content { |content|
-          expect(content).to match(/ACTION_CABLE_WORKER_POOL_SIZE=7/)
-          expect(content).to match(%r(/opt/gitlab/embedded/bin/bundle exec puma -C /var/opt/gitlab/gitlab-rails/etc/puma.rb))
-        }
-    end
-  end
-
-  include_examples "consul service discovery", "puma", "rails"
-end
-
-RSpec.describe 'gitlab::puma Ubuntu 16.04 with no tmpfs' do
-  let(:chef_run) do
-    runner = ChefSpec::SoloRunner.new(
-      path: 'spec/fixtures/fauxhai/ubuntu/16.04-no-run-tmpfs.json',
-      step_into: %w(runit_service)
-    )
-    runner.converge('gitlab::default')
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when puma is enabled on a node with no /run or /dev/shm tmpfs' do
-    it_behaves_like 'enabled runit service', 'puma', 'root', 'root'
-
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/puma/run')
-        .with_content { |content|
-          expect(content).to match(/export prometheus_run_dir=\'\'/)
-          expect(content).not_to match(/mkdir -p \/run\/gitlab\/puma/)
-        }
-    end
-  end
-end
-
-RSpec.describe 'gitlab::puma Ubuntu 16.04 Docker' do
-  let(:chef_run) do
-    runner = ChefSpec::SoloRunner.new(
-      path: 'spec/fixtures/fauxhai/ubuntu/16.04-docker.json',
-      step_into: %w(runit_service)
-    )
-    runner.converge('gitlab::default')
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when puma is enabled on a node with a /dev/shm tmpfs' do
-    it_behaves_like 'enabled runit service', 'puma', 'root', 'root'
-
-    it 'populates the files with expected configuration' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/puma/run')
-        .with_content { |content|
-          expect(content).to match(/export prometheus_run_dir=\'\/dev\/shm\/gitlab\/puma\'/)
-          expect(content).to match(/mkdir -p \/dev\/shm\/gitlab\/puma/)
-        }
-    end
-  end
-end
-
-RSpec.describe 'gitlab::puma with more CPUs' do
-  let(:chef_run) do
-    runner = ChefSpec::SoloRunner.new(
-      step_into: %w(runit_service),
-      path: 'spec/fixtures/fauxhai/ubuntu/16.04-more-cpus.json'
-    )
-    runner.converge('gitlab::default')
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when puma is enabled' do
-    it 'renders the puma.rb file' do
-      expect(chef_run).to create_puma_config('/var/opt/gitlab/gitlab-rails/etc/puma.rb').with(
-        environment: 'production',
-        pid: '/opt/gitlab/var/puma/puma.pid',
-        state_path: '/opt/gitlab/var/puma/puma.state',
-        listen_socket: '/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket',
-        listen_tcp: '127.0.0.1:8080',
-        working_directory: '/var/opt/gitlab/gitlab-rails/working',
-        worker_processes: 16
-      )
-    end
-  end
-end
diff --git a/spec/chef/recipes/rails_cache_clear_spec.rb b/spec/chef/recipes/rails_cache_clear_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvcmFpbHNfY2FjaGVfY2xlYXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/rails_cache_clear_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'chef_helper'
-
-# NOTE: We do not try to verify if we pass proper notifications to
-# execute['clear the gitlab-rails cache'] resource that's done in other specs
-# We just test if we use proper command and that we can change default
-# attribute value with gilab.rb setting.
-
-RSpec.describe 'gitlab::rails-cache-clear' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'test clear cache execution' do
-    let(:clear_cache_exec) { chef_run.execute('clear the gitlab-rails cache') }
-    let(:gilab_yml_temp) do
-      chef_run.find_resource(:templatesymlink,
-                             'Create a gitlab.yml and create a symlink to Rails root')
-    end
-
-    it 'check rake_cache_clear default attribute value set to true' do
-      expect(chef_run.node['gitlab']['gitlab-rails']['rake_cache_clear'])
-        .to be(true)
-    end
-
-    it 'check rake_cache_clear attribute value set to true' do
-      stub_gitlab_rb(gitlab_rails: { rake_cache_clear: true })
-      expect(chef_run.node['gitlab']['gitlab-rails']['rake_cache_clear'])
-        .to be(true)
-    end
-
-    it 'check rake_cache_clear attribute value set to false' do
-      stub_gitlab_rb(gitlab_rails: { rake_cache_clear: false })
-      expect(chef_run.node['gitlab']['gitlab-rails']['rake_cache_clear'])
-        .to be(false)
-    end
-
-    it 'check command used to clear cache' do
-      expect(clear_cache_exec.command).to match(
-        '/opt/gitlab/bin/gitlab-rake cache:clear')
-      expect(clear_cache_exec).to do_nothing
-    end
-  end
-end
diff --git a/spec/chef/recipes/registry_spec.rb b/spec/chef/recipes/registry_spec.rb
deleted file mode 100755
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvcmVnaXN0cnlfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/registry_spec.rb
+++ /dev/null
@@ -1,510 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'registry recipe' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe 'letsencrypt' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'https://gitlab.example.com',
-        registry_external_url: 'https://registry.example.com'
-      )
-
-      allow(File).to receive(:exist?).and_call_original
-    end
-
-    describe 'HTTP to HTTPS redirection' do
-      context 'by default' do
-        it 'is enabled' do
-          expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/gitlab-registry.conf').with_content("return 301 https://registry.example.com$request_uri;")
-        end
-      end
-
-      context 'if disabled in gitlab.rb' do
-        before do
-          stub_gitlab_rb(
-            external_url: 'https://gitlab.example.com',
-            registry_external_url: 'https://registry.example.com',
-            registry_nginx: {
-              redirect_http_to_https: false
-            }
-          )
-        end
-
-        it 'is disabled' do
-          expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/gitlab-registry.conf')
-          expect(chef_run).not_to render_file('/var/opt/gitlab/nginx/conf/gitlab-registry.conf').with_content("return 301 https://registry.example.com$request_uri;")
-        end
-      end
-
-      context 'registry on gitlab domain with a different port ' do
-        before do
-          stub_gitlab_rb(
-            external_url: 'https://gitlab.example.com',
-            registry_external_url: 'https://gitlab.example.com:5005'
-          )
-        end
-
-        it 'is enabled and has correct redirect URL in nginx config' do
-          expect(chef_run).to render_file('/var/opt/gitlab/nginx/conf/gitlab-registry.conf').with_content("return 301 https://gitlab.example.com:5005$request_uri;")
-        end
-      end
-    end
-
-    context 'default certificate file is missing' do
-      before do
-        allow(File).to receive(:exist?).with('/etc/gitlab/ssl/registry.example.com.crt').and_return(false)
-      end
-
-      it 'adds itself to letsencrypt alt_names' do
-        expect(chef_run.node['letsencrypt']['alt_names']).to eql(['registry.example.com'])
-      end
-
-      it 'is reflected in the acme_selfsigned' do
-        expect(chef_run).to create_acme_selfsigned('gitlab.example.com').with(
-          alt_names: ['registry.example.com']
-        )
-      end
-    end
-
-    context 'default certificate file is present' do
-      before do
-        allow(File).to receive(:exist?).with('/etc/gitlab/ssl/registry.example.com.crt').and_return(true)
-      end
-
-      it 'does not alter letsencrypt alt_names' do
-        expect(chef_run.node['letsencrypt']['alt_names']).to eql([])
-      end
-
-      it 'is reflected in the acme_selfsigned' do
-        expect(chef_run).to create_acme_selfsigned('gitlab.example.com').with(
-          alt_names: []
-        )
-      end
-    end
-  end
-
-  context 'when registry is enabled' do
-    before { stub_gitlab_rb(registry_external_url: 'https://registry.example.com') }
-
-    it_behaves_like 'enabled registry service'
-
-    it_behaves_like 'renders a valid YAML file', '/var/opt/gitlab/registry/config.yml'
-
-    it 'creates a default VERSION file and restarts service' do
-      expect(chef_run).to create_version_file('Create version file for Registry').with(
-        version_file_path: '/var/opt/gitlab/registry/VERSION',
-        version_check_cmd: '/opt/gitlab/embedded/bin/registry --version'
-      )
-
-      expect(chef_run.version_file('Create version file for Registry')).to notify('runit_service[registry]').to(:restart)
-    end
-
-    context 'when registry storagedriver health check is disabled' do
-      before { stub_gitlab_rb(registry: { health_storagedriver_enabled: false }) }
-
-      it 'creates registry config with specified value' do
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/health:\s*storagedriver:\s*enabled:\s*false/)
-      end
-    end
-
-    context 'when registry validation is enabled' do
-      before { stub_gitlab_rb(registry: { validation_enabled: true }) }
-
-      it 'creates registry config with specified value' do
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/^validation:\s*disabled: false$/)
-      end
-    end
-
-    context 'when registry middleware is enabled' do
-      let(:middleware_config) do
-        { "storage" => [
-          { "name" => "googlecdn",
-            "options" => {
-              "baseurl" => "https://example.org",
-              "privatekey" => "/etc/gitlab/googlecdn.key",
-              "keyname" => "example-key"
-            } }
-        ] }
-      end
-
-      before { stub_gitlab_rb(registry: { middleware: middleware_config }) }
-
-      it 'creates registry config with middleware' do
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(%r(^middleware: {"storage":))
-      end
-    end
-
-    context 'when a log formatter is specified' do
-      before { stub_gitlab_rb(registry: { log_formatter: 'json' }) }
-
-      it 'creates the registry config with the specified value' do
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/log:\s*level: info\s*formatter:\s*json/)
-      end
-
-      it 'does not append timestamp in logs if logging format is json' do
-        expect(chef_run).to render_file('/opt/gitlab/sv/registry/log/run')
-          .with_content(/exec svlogd \/var\/log\/gitlab\/registry/)
-      end
-    end
-
-    context 'when schema1 compatibility is enabled' do
-      before { stub_gitlab_rb(registry: { compatibility_schema1_enabled: true }) }
-
-      it 'creates registry config with specified value' do
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/compatibility:\s*schema1:\s*enabled:\s*true/)
-      end
-    end
-  end
-
-  context 'when registry port is specified' do
-    before { stub_gitlab_rb(registry_external_url: 'https://registry.example.com', registry: { registry_http_addr: 'localhost:5001' }) }
-
-    it 'creates registry and rails configs with specified value' do
-      expect(chef_run).to create_templatesymlink('Create a gitlab.yml and create a symlink to Rails root').with_variables(hash_including('registry_api_url' => 'http://localhost:5001'))
-
-      expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-        .with_content(/addr: localhost:5001/)
-    end
-  end
-
-  context 'when a debug addr is specified' do
-    before { stub_gitlab_rb(registry_external_url: 'https://registry.example.com', registry: { debug_addr: 'localhost:5005' }) }
-
-    it 'creates the registry config with the specified debug value' do
-      expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-        .with_content(/debug:\n\s*addr: localhost:5005/)
-    end
-  end
-
-  context 'when user and group are specified' do
-    before { stub_gitlab_rb(registry_external_url: 'https://registry.example.com', registry: { username: 'registryuser', group: 'registrygroup' }) }
-    it 'make registry run file start registry under correct user' do
-      expect(chef_run).to render_file('/opt/gitlab/sv/registry/run')
-        .with_content(/-U registryuser:registrygroup/)
-      expect(chef_run).to render_file('/opt/gitlab/sv/registry/run')
-        .with_content(/-u registryuser:registrygroup/)
-    end
-  end
-end
-
-RSpec.describe 'registry' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  let(:default_vars) do
-    {
-      'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
-    }
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when registry is enabled' do
-    before { stub_gitlab_rb(registry_external_url: 'https://registry.example.com') }
-
-    it_behaves_like 'enabled registry service'
-
-    context 'when custom storage parameters are specified' do
-      before do
-        stub_gitlab_rb(
-          registry: {
-            storage: {
-              s3: { accesskey: 'awsaccesskey', secretkey: 'awssecretkey', bucketname: 'bucketname' }
-            }
-          }
-        )
-      end
-
-      it 'uses custom storage instead of the default rootdirectory' do
-        expect(chef_run.node['registry']['storage'])
-          .to include(s3: { accesskey: 'awsaccesskey', secretkey: 'awssecretkey', bucketname: 'bucketname' })
-        expect(chef_run.node['registry']['storage'])
-          .not_to include('rootdirectory' => '/var/opt/gitlab/gitlab-rails/shared/registry')
-      end
-
-      it 'uses the default cache and delete settings if not overridden' do
-        expect(chef_run.node['registry']['storage']['cache'])
-          .to eql('blobdescriptor' => 'inmemory')
-        expect(chef_run.node['registry']['storage']['delete'])
-          .to eql('enabled' => true)
-      end
-
-      it 'allows the cache and delete settings to be overridden' do
-        stub_gitlab_rb(registry: { storage: { cache: 'somewhere-else', delete: { enabled: false } } })
-        expect(chef_run.node['registry']['storage']['cache'])
-          .to eql('somewhere-else')
-        expect(chef_run.node['registry']['storage']['delete'])
-          .to eql('enabled' => false)
-      end
-    end
-
-    context 'when storage_delete_enabled is false' do
-      before { stub_gitlab_rb(registry: { storage_delete_enabled: false }) }
-
-      it 'sets the delete enabled field on the storage object' do
-        expect(chef_run.node['registry']['storage']['delete'])
-          .to eql('enabled' => false)
-      end
-    end
-
-    context 'when notification is configured for Geo replication' do
-      before do
-        stub_gitlab_rb(
-          registry: {
-            notifications: [
-              {
-                'name' => 'geo_event',
-                'url' => 'https://registry.example.com/notify',
-                'timeout' => '500ms',
-                'threshold' => 5,
-                'backoff' => '1s',
-                'headers' => {
-                  "Authorization" => ["mysecret"]
-                }
-              }
-            ]
-          }
-        )
-      end
-
-      it 'assigns registry_notification_secret variable automatically' do
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"Authorization":\["mysecret"\]/)
-        expect(chef_run.node['gitlab']['gitlab-rails']['registry_notification_secret'])
-          .to eql('mysecret')
-      end
-    end
-
-    context 'when registry notification endpoint is configured with the minimum required' do
-      before do
-        stub_gitlab_rb(
-          registry: {
-            notifications: [
-              name: 'test_endpoint',
-              url: 'https://registry.example.com/notify'
-            ]
-          }
-        )
-      end
-
-      it 'creates the registry config with the specified endpoint config' do
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"name":"test_endpoint"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"url":"https:\/\/registry.example.com\/notify"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"timeout":"500ms"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"threshold":5/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"backoff":"1s"/)
-      end
-    end
-
-    context 'when the default values are overridden' do
-      before do
-        stub_gitlab_rb(
-          registry: {
-            notifications: [
-              name: 'test_endpoint',
-              url: 'https://registry.example.com/notify'
-            ],
-            default_notifications_timeout: '5000ms',
-            default_notifications_threshold: 10,
-            default_notifications_backoff: '50s',
-            default_notifications_headers: {
-              "Authorization" => %w(AUTHORIZATION_EXAMPLE_TOKEN1 AUTHORIZATION_EXAMPLE_TOKEN2)
-            }
-          }
-        )
-      end
-
-      it 'creates the registry config overriding the values not set with the new defaults' do
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"name":"test_endpoint"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"url":"https:\/\/registry.example.com\/notify"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"timeout":"5000ms"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"threshold":10/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"backoff":"50s"/)
-      end
-    end
-
-    context 'when registry notification endpoint is configured with all the available variables' do
-      before do
-        stub_gitlab_rb(
-          registry: {
-            notifications: [
-              {
-                'name' => 'test_endpoint',
-                'url' => 'https://registry.example.com/notify',
-                'timeout' => '500ms',
-                'threshold' => 5,
-                'backoff' => '1s',
-                'headers' => {
-                  "Authorization" => ["AUTHORIZATION_EXAMPLE_TOKEN"]
-                }
-              }
-            ]
-          }
-        )
-      end
-
-      it 'creates the registry config with the specified endpoint config' do
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"name":"test_endpoint"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"url":"https:\/\/registry.example.com\/notify"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"timeout":"500ms"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"threshold":5/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"backoff":"1s"/)
-      end
-    end
-
-    context 'when 3 registry notification endpoints are configured' do
-      before do
-        stub_gitlab_rb(
-          registry: {
-            notifications: [
-              {
-                'name' => 'test_endpoint',
-                'url' => 'https://registry.example.com/notify'
-              },
-              {
-                'name' => 'test_endpoint2',
-                'url' => 'https://registry.example.com/notify2',
-                'timeout' => '100ms',
-                'threshold' => 2,
-                'backoff' => '4s',
-                'headers' => {
-                  "Authorization" => ["AUTHORIZATION_EXAMPLE_TOKEN"]
-                }
-              },
-              {
-                'name' => 'test_endpoint3',
-                'url' => 'https://registry.example.com/notify3'
-              }
-            ]
-          }
-        )
-      end
-
-      it 'creates the registry config with the specified endpoint config' do
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"name":"test_endpoint"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/\"url\":\"https:\/\/registry.example.com\/notify\"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"timeout":"500ms"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"threshold":5/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"backoff":"1s"/)
-        # Second endpoint
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"name":"test_endpoint2"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"url":"https:\/\/registry.example.com\/notify2"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"timeout":"100ms"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"threshold":2/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"backoff":"4s"/)
-        # Third endpoint
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"name":"test_endpoint3"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"url":"https:\/\/registry.example.com\/notify3"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"timeout":"500ms"/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"threshold":5/)
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content(/"backoff":"1s"/)
-      end
-    end
-
-    context 'when registry notification endpoint is not configured' do
-      it 'creates the registry config without the endpoint config' do
-        expect(chef_run).to render_file('/var/opt/gitlab/registry/config.yml')
-        expect(chef_run).not_to render_file('/var/opt/gitlab/registry/config.yml')
-          .with_content('notifications:')
-      end
-    end
-
-    context 'when registry has custom environment variables configured' do
-      before do
-        stub_gitlab_rb(registry: { env: { 'HTTP_PROXY' => 'my-proxy' } })
-      end
-
-      it 'creates necessary env variable files' do
-        expect(chef_run).to create_env_dir('/opt/gitlab/etc/registry/env').with_variables(
-          default_vars.merge(
-            {
-              'HTTP_PROXY' => 'my-proxy'
-            }
-          )
-        )
-      end
-    end
-  end
-end
-
-RSpec.describe 'auto enabling registry' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  let(:registry_config) { '/var/opt/gitlab/registry/config.yml' }
-  let(:nginx_config) { '/var/opt/gitlab/nginx/conf/gitlab-registry.conf' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    stub_gitlab_rb(
-      external_url: 'https://gitlab.example.com'
-    )
-  end
-
-  it_behaves_like 'enabled registry service'
-
-  it 'should listen on port 5050 with nginx' do
-    expect(chef_run).to render_file(nginx_config)
-      .with_content { |content|
-        expect(content).to include("listen *:5050 ssl;")
-        expect(content).to include("server_name gitlab.example.com;")
-      }
-  end
-
-  it "should use the default Let's Encrypt certificates" do
-    expect(chef_run).to render_file(nginx_config)
-      .with_content { |content|
-        expect(content).to include("ssl_certificate /etc/gitlab/ssl/gitlab.example.com.crt;")
-        expect(content).to include("ssl_certificate_key /etc/gitlab/ssl/gitlab.example.com.key;")
-      }
-  end
-
-  it 'should point gitlab-rails to the registry' do
-    expect(chef_run).to create_templatesymlink(
-      'Create a gitlab.yml and create a symlink to Rails root'
-    ).with_variables(
-      hash_including(
-        'registry_host' => 'gitlab.example.com',
-        'registry_port' => '5050'
-      )
-    )
-  end
-end
diff --git a/spec/chef/recipes/remote-syslog_spec.rb b/spec/chef/recipes/remote-syslog_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvcmVtb3RlLXN5c2xvZ19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/remote-syslog_spec.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::remote-syslog' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'creates service' do
-    before do
-      stub_gitlab_rb(
-        remote_syslog: {
-          enable: true
-        }
-      )
-    end
-    it_behaves_like "enabled runit service", "remote-syslog", "root", "root"
-  end
-
-  context 'when remote logger is enabled' do
-    before do
-      stub_gitlab_rb(
-        remote_syslog: {
-          enable: true
-        },
-        logging: {
-          udp_log_shipping_hostname: "example.com"
-        }
-      )
-    end
-
-    it 'creates the remote_syslog config' do
-      expect(chef_run.node['gitlab']['remote-syslog']['enable']).to eq true
-    end
-
-    it 'creates remote-syslog config file with correct log directories' do
-      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml')
-
-      # Checking if log_directory of both type of services - those accessible
-      # via node[service] and those via node['gitlab'][service] are populated.
-      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/redis\/\*.log/)
-      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/nginx\/\*.log/)
-      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/gitlab-rails\/\*.log/)
-      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/postgresql\/\*.log/)
-      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/sidekiq\/\*.log/)
-      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/gitlab-workhorse\/\*.log/)
-      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/gitlab-pages\/\*.log/)
-      expect(chef_run).to render_file('/var/opt/gitlab/remote-syslog/remote_syslog.yml').with_content(/- \/var\/log\/gitlab\/gitlab-kas\/\*.log/)
-    end
-
-    it 'creates the remote-syslog sv file without setting hostname' do
-      stub_gitlab_rb(
-        remote_syslog: {
-          enable: true
-        }
-      )
-      contents = <<~END
-      #!/bin/sh
-      exec 2>&1
-
-      exec /opt/gitlab/embedded/bin/remote_syslog --no-detach --debug-level DEBUG -c /var/opt/gitlab/remote-syslog/remote_syslog.yml
-      END
-      expect(chef_run).to render_file('/opt/gitlab/sv/remote-syslog/run').with_content(contents.chomp)
-    end
-
-    it 'creates the remote-syslog sv file with setting hostname' do
-      stub_gitlab_rb(
-        remote_syslog: {
-          enable: true
-        },
-        logging: {
-          udp_log_shipping_hostname: "example.com"
-        }
-      )
-      contents = <<~END
-      #!/bin/sh
-      exec 2>&1
-
-      exec /opt/gitlab/embedded/bin/remote_syslog --no-detach --debug-level DEBUG -c /var/opt/gitlab/remote-syslog/remote_syslog.yml --hostname example.com
-      END
-      expect(chef_run).to render_file('/opt/gitlab/sv/remote-syslog/run').with_content(contents.chomp)
-    end
-  end
-end
diff --git a/spec/chef/recipes/selinux_spec.rb b/spec/chef/recipes/selinux_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvc2VsaW51eF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/selinux_spec.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::gitlab-selinux' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink storage_directory)).converge('gitlab::default') }
-  let(:templatesymlink) { chef_run.templatesymlink('Create a config.yml and create a symlink to Rails root') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    stub_default_should_notify?(true)
-  end
-
-  context 'when NOT running on selinux' do
-    before do
-      allow_any_instance_of(ShellOutHelper).to receive(:success?).with('id -Z').and_return(false)
-    end
-
-    it 'should not run the semanage bash command' do
-      expect(templatesymlink).to_not notify('bash[Set proper security context on ssh files for selinux]').delayed
-    end
-  end
-
-  context 'when running on selinux' do
-    before do
-      allow_any_instance_of(ShellOutHelper).to receive(:success?).with('id -Z').and_return(true)
-      allow(File).to receive(:exist?).and_call_original
-      allow(File).to receive(:exist?).with('/var/opt/gitlab/.ssh').and_return(true)
-      allow(File).to receive(:exist?).with('/var/opt/gitlab/.ssh/authorized_keys').and_return(true)
-      allow(File).to receive(:exist?).with('/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret').and_return(true)
-      allow(File).to receive(:exist?).with('/var/opt/gitlab/gitlab-shell/config.yml').and_return(true)
-      allow(File).to receive(:exist?).with('/var/opt/gitlab/gitlab-workhorse/sockets').and_return(true)
-    end
-
-    let(:bash_block) { chef_run.bash('Set proper security context on ssh files for selinux') }
-
-    def semanage_fcontext(filename)
-      "semanage fcontext -a -t gitlab_shell_t '#{filename}'"
-    end
-
-    it 'should run the semanage bash command' do
-      expect(templatesymlink).to notify('bash[Set proper security context on ssh files for selinux]').delayed
-    end
-
-    it 'sets the security context of gitlab-shell files' do
-      lines = bash_block.code.split("\n")
-      files = %w(/var/opt/gitlab/.ssh(/.*)?
-                 /var/opt/gitlab/.ssh/authorized_keys
-                 /var/opt/gitlab/gitlab-shell/config.yml
-                 /var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret
-                 /var/opt/gitlab/gitlab-workhorse/sockets)
-      managed_files = files.map { |file| semanage_fcontext(file) }
-
-      expect(lines).to include(*managed_files)
-      expect(lines).to include("restorecon -R -v '/var/opt/gitlab/.ssh'")
-      expect(lines).to include("restorecon -v '/var/opt/gitlab/.ssh/authorized_keys'")
-      expect(lines).to include("restorecon -v '/var/opt/gitlab/gitlab-shell/config.yml'")
-      expect(lines).to include("restorecon -v '/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret'")
-      expect(lines).to include("restorecon -v '/var/opt/gitlab/gitlab-workhorse/sockets'")
-    end
-
-    context 'and the user configured a custom workhorse sockets directory' do
-      let(:user_sockets_directory) { '/how/do/you/do' }
-      before do
-        stub_gitlab_rb(
-          gitlab_workhorse: {
-            listen_network: 'unix',
-            sockets_directory: user_sockets_directory
-          }
-        )
-      end
-
-      it 'sets the security context of a custom workhorse sockets directory' do
-        allow(File).to receive(:exist?).with(user_sockets_directory).and_return(true)
-        lines = bash_block.code.split("\n")
-        files = [user_sockets_directory]
-        managed_files = files.map { |file| semanage_fcontext(file) }
-
-        expect(lines).to include(*managed_files)
-        expect(lines).to include("restorecon -v '#{user_sockets_directory}'")
-      end
-    end
-
-    context 'when gitlab-rails is disabled' do
-      before do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            enable: false
-          }
-        )
-      end
-
-      it 'should not attempt to set security context' do
-        expect(chef_run).not_to run_bash('Set proper security context on ssh files for selinux')
-      end
-    end
-  end
-end
diff --git a/spec/chef/recipes/sidekiq_spec.rb b/spec/chef/recipes/sidekiq_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvc2lkZWtpcV9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/sidekiq_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::sidekiq' do
-  let(:chef_run) do
-    runner = ChefSpec::SoloRunner.new(
-      step_into: %w(sidekiq_service runit_service),
-      path: 'spec/fixtures/fauxhai/ubuntu/16.04.json'
-    )
-    runner.converge('gitlab::default')
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'with default values' do
-    it 'correctly renders out the sidekiq service file' do
-      expect(chef_run).to render_file("/opt/gitlab/sv/sidekiq/run")
-        .with_content { |content|
-          expect(content).not_to match(/export prometheus_run_dir=\'\'/)
-          expect(content).to match(/mkdir -p \/run\/gitlab\/sidekiq/)
-          expect(content).to match(/rm \/run\/gitlab\/sidekiq/)
-          expect(content).to match(/chmod 0700 \/run\/gitlab\/sidekiq/)
-          expect(content).to match(/chown git \/run\/gitlab\/sidekiq/)
-          expect(content).to match(/export prometheus_run_dir=\'\/run\/gitlab\/sidekiq\'/)
-          expect(content).to match(%r{bin/sidekiq-cluster})
-          expect(content).to match(/-m 50/) # max_concurrency
-          expect(content).to match(/--timeout 25/) # shutdown timeout
-          expect(content).to match(/\*/) # all queues
-        }
-    end
-
-    it_behaves_like "enabled runit service", "sidekiq", "root", "root"
-  end
-
-  describe 'log_format' do
-    context 'by default' do
-      it 'does not pass timestamp flag to svlogd' do
-        expect(chef_run).not_to render_file("/opt/gitlab/sv/sidekiq/log/run").with_content(/-tt/)
-      end
-    end
-
-    context 'when user specifies text log format' do
-      before do
-        stub_gitlab_rb(
-          sidekiq: { log_format: 'text' }
-        )
-      end
-
-      it 'passes timestamp flag to svlogd' do
-        expect(chef_run).to render_file("/opt/gitlab/sv/sidekiq/log/run").with_content(/-tt/)
-      end
-    end
-  end
-
-  include_examples "consul service discovery", "sidekiq", "sidekiq"
-end
diff --git a/spec/chef/recipes/storage-check_spec.rb b/spec/chef/recipes/storage-check_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvc3RvcmFnZS1jaGVja19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/storage-check_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Copyright:: Copyright (c) 2017 GitLab Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef_helper'
-
-RSpec.describe 'gitlab::storage-check' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  describe 'when disabled' do
-    it_behaves_like 'disabled runit service', 'storage-check'
-  end
-
-  describe 'when enabled' do
-    context 'default values' do
-      before do
-        stub_gitlab_rb(
-          storage_check: { enable: true }
-        )
-      end
-
-      it_behaves_like 'enabled runit service', 'storage-check', 'root', 'root'
-    end
-
-    context 'custom values' do
-      before do
-        stub_gitlab_rb(
-          storage_check: {
-            enable: true
-          },
-          user: {
-            username: 'foo',
-            group: 'bar'
-          }
-        )
-      end
-
-      it_behaves_like 'enabled runit service', 'storage-check', 'root', 'root'
-    end
-  end
-end
diff --git a/spec/chef/recipes/users_spec.rb b/spec/chef/recipes/users_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlY2lwZXMvdXNlcnNfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/recipes/users_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'gitlab::users' do
-  let(:chef_run) { ChefSpec::SoloRunner.new.converge('gitlab::users') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'with default attributes' do
-    it 'creates gitlab user' do
-      account_params = {
-        username: 'git',
-        groupname: 'git',
-        home: '/var/opt/gitlab',
-        shell: '/bin/sh'
-      }
-
-      expect(chef_run).to create_directory('/var/opt/gitlab')
-      expect(chef_run).to create_account('GitLab user and group').with(account_params)
-    end
-
-    it 'creates .gitconfig' do
-      template_params = {
-        owner: 'git',
-        group: 'git',
-        mode: '0644'
-      }
-
-      expect(chef_run).to create_template('/var/opt/gitlab/.gitconfig').with(template_params)
-      expect(chef_run).to render_file('/var/opt/gitlab/.gitconfig').with_content { |content|
-        expect(content).to match(/name = GitLab/)
-        expect(content).to match(/email = gitlab@fauxhai.local/)
-        expect(content).to match(/fsyncObjectFiles = true/)
-      }
-    end
-
-    it 'creates .bundle directory' do
-      directory_params = {
-        owner: 'git',
-        group: 'git'
-      }
-
-      expect(chef_run).to create_directory('/var/opt/gitlab/.bundle').with(directory_params)
-    end
-  end
-end
diff --git a/spec/chef/redis/redis_spec.rb b/spec/chef/redis/redis_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3JlZGlzL3JlZGlzX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/chef/redis/redis_spec.rb
+++ /dev/null
@@ -1,379 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'redis' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(redis_service runit_service)).converge('gitlab::default') }
-  let(:redis_conf) { '/var/opt/gitlab/redis/redis.conf' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'by default' do
-    let(:gitlab_redis_cli_rc) do
-      <<-EOF
-redis_dir='/var/opt/gitlab/redis'
-redis_host='127.0.0.1'
-redis_port='0'
-redis_socket='/var/opt/gitlab/redis/redis.socket'
-      EOF
-    end
-
-    it 'enables the redis service' do
-      expect(chef_run).to create_redis_service('redis')
-    end
-
-    it 'creates redis config with default values' do
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content { |content|
-          expect(content).to match(/client-output-buffer-limit normal 0 0 0/)
-          expect(content).to match(/client-output-buffer-limit replica 256mb 64mb 60/)
-          expect(content).to match(/client-output-buffer-limit pubsub 32mb 8mb 60/)
-          expect(content).to match(/^hz 10/)
-          expect(content).to match(/^save 900 1/)
-          expect(content).to match(/^save 300 10/)
-          expect(content).to match(/^save 60 10000/)
-          expect(content).to match(/^maxmemory 0/)
-          expect(content).to match(/^maxmemory-policy noeviction/)
-          expect(content).to match(/^maxmemory-samples 5/)
-          expect(content).to match(/^tcp-backlog 511/)
-          expect(content).to match(/^rename-command KEYS ""$/)
-          expect(content).to match(/^lazyfree-lazy-eviction no$/)
-          expect(content).to match(/^lazyfree-lazy-expire no$/)
-          expect(content).to match(/^io-threads 1$/)
-          expect(content).to match(/^io-threads-do-reads no$/)
-          expect(content).to match(/^stop-writes-on-bgsave-error yes$/)
-          expect(content).not_to match(/^replicaof/)
-          expect(content).not_to match(/^tls-/)
-        }
-    end
-
-    it 'creates redis user and group' do
-      expect(chef_run).to create_account('user and group for redis').with(username: 'gitlab-redis', groupname: 'gitlab-redis')
-    end
-
-    it_behaves_like 'enabled runit service', 'redis', 'root', 'root'
-
-    it 'creates gitlab-redis-cli-rc' do
-      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
-        .with_content(gitlab_redis_cli_rc)
-    end
-
-    describe 'pending restart check' do
-      context 'when running version is same as installed version' do
-        before do
-          allow_any_instance_of(RedisHelper).to receive(:running_version).and_return('3.2.12')
-          allow_any_instance_of(RedisHelper).to receive(:installed_version).and_return('3.2.12')
-        end
-
-        it 'does not raise a warning' do
-          expect(chef_run).not_to run_ruby_block('warn pending redis restart')
-        end
-      end
-
-      context 'when running version is different than installed version' do
-        before do
-          allow_any_instance_of(RedisHelper).to receive(:running_version).and_return('3.2.12')
-          allow_any_instance_of(RedisHelper).to receive(:installed_version).and_return('5.0.9')
-        end
-
-        it 'raises a warning' do
-          expect(chef_run).to run_ruby_block('warn pending redis restart')
-        end
-      end
-    end
-  end
-
-  context 'with user specified values' do
-    before do
-      stub_gitlab_rb(
-        redis: {
-          client_output_buffer_limit_normal: "5 5 5",
-          client_output_buffer_limit_replica: "512mb 128mb 120",
-          client_output_buffer_limit_pubsub: "64mb 16mb 120",
-          save: ["10 15000"],
-          maxmemory: "32gb",
-          maxmemory_policy: "allkeys-url",
-          maxmemory_samples: 10,
-          tcp_backlog: 1024,
-          hz: 100,
-          username: 'foo',
-          group: 'bar',
-          rename_commands: {
-            "FAKE_COMMAND" => "RENAMED_FAKE_COMMAND",
-            "DISABLED_FAKE_COMMAND" => ""
-          }
-        }
-      )
-    end
-
-    it 'creates redis config with custom values' do
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/client-output-buffer-limit normal 5 5 5/)
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/client-output-buffer-limit replica 512mb 128mb 120/)
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/client-output-buffer-limit pubsub 64mb 16mb 120/)
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/^save 10 15000/)
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/^maxmemory 32gb/)
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/^maxmemory-policy allkeys-url/)
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/^maxmemory-samples 10/)
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/^tcp-backlog 1024/)
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/^hz 100/)
-    end
-
-    it 'does not include the default renamed keys in redis.conf' do
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content { |content|
-          expect(content).not_to match(/^rename-command KEYS ""$/)
-          expect(content).to match(/^rename-command FAKE_COMMAND "RENAMED_FAKE_COMMAND"$/)
-          expect(content).to match(/^rename-command DISABLED_FAKE_COMMAND ""$/)
-        }
-    end
-
-    it 'creates redis user and group' do
-      expect(chef_run).to create_account('user and group for redis').with(username: 'foo', groupname: 'bar')
-    end
-
-    it_behaves_like 'enabled runit service', 'redis', 'root', 'root'
-  end
-
-  context 'with snapshotting disabled' do
-    before do
-      stub_gitlab_rb(
-        redis: {
-          save: []
-        }
-      )
-    end
-    it 'creates redis config without save setting' do
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-      expect(chef_run).not_to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/^save/)
-    end
-  end
-
-  context 'with snapshotting cleared' do
-    before do
-      stub_gitlab_rb(
-        redis: {
-          save: [""]
-        }
-      )
-    end
-    it 'creates redis config without save setting' do
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/^save ""/)
-    end
-  end
-
-  context 'with a replica configured' do
-    let(:redis_host) { '1.2.3.4' }
-    let(:redis_port) { 6370 }
-    let(:master_ip) { '10.0.0.0' }
-    let(:master_port) { 6371 }
-
-    let(:gitlab_redis_cli_rc) do
-      <<-EOF
-redis_dir='/var/opt/gitlab/redis'
-redis_host='1.2.3.4'
-redis_port='6370'
-redis_socket=''
-      EOF
-    end
-
-    before do
-      stub_gitlab_rb(
-        redis: {
-          bind: redis_host,
-          port: redis_port,
-          master_ip: master_ip,
-          master_port: master_port,
-          master_password: 'password',
-          master: false
-        }
-      )
-    end
-
-    it 'includes replicaof' do
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/^replicaof #{master_ip} #{master_port}/)
-    end
-
-    it 'creates gitlab-redis-cli-rc' do
-      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
-        .with_content(gitlab_redis_cli_rc)
-    end
-  end
-
-  context 'in HA mode with Sentinels' do
-    let(:redis_host) { '1.2.3.4' }
-    let(:redis_port) { 6370 }
-    let(:master_ip) { '10.0.0.0' }
-    let(:master_port) { 6371 }
-
-    let(:gitlab_redis_cli_rc) do
-      <<-EOF
-redis_dir='/var/opt/gitlab/redis'
-redis_host='1.2.3.4'
-redis_port='6370'
-redis_socket=''
-      EOF
-    end
-
-    before do
-      stub_gitlab_rb(
-        redis: {
-          bind: redis_host,
-          port: redis_port,
-          ha: true,
-          master_ip: master_ip,
-          master_port: master_port,
-          master_password: 'password',
-          master: false
-        }
-      )
-    end
-
-    it 'omits replicaof' do
-      expect(chef_run).not_to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content(/^replicaof/)
-    end
-
-    it 'creates gitlab-redis-cli-rc' do
-      expect(chef_run).to render_file('/opt/gitlab/etc/gitlab-redis-cli-rc')
-        .with_content(gitlab_redis_cli_rc)
-    end
-  end
-
-  context 'with rename_commands disabled' do
-    before do
-      stub_gitlab_rb(
-        redis: {
-          rename_commands: {}
-        }
-      )
-    end
-
-    it 'should not rename any commands' do
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content { |content|
-          expect(content).not_to match(/^rename-command/)
-        }
-    end
-  end
-
-  context 'with lazy eviction enabled' do
-    before do
-      stub_gitlab_rb(
-        redis: {
-          lazyfree_lazy_eviction: true
-        }
-      )
-    end
-
-    it 'creates redis config with lazyfree-lazy-eviction yes' do
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content { |content|
-          expect(content).to match(/^lazyfree-lazy-eviction yes$/)
-          expect(content).to match(/^lazyfree-lazy-expire no$/)
-        }
-    end
-  end
-
-  context 'with lazy eviction enabled' do
-    before do
-      stub_gitlab_rb(
-        redis: {
-          io_threads: 4,
-          io_threads_do_reads: true
-        }
-      )
-    end
-
-    it 'creates redis config with lazyfree-lazy-eviction yes' do
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content { |content|
-          expect(content).to match(/^io-threads 4$/)
-          expect(content).to match(/^io-threads-do-reads yes$/)
-        }
-    end
-  end
-
-  context 'with stop writes on bgsave error disabled' do
-    before do
-      stub_gitlab_rb(
-        redis: {
-          stop_writes_on_bgsave_error: false
-        }
-      )
-    end
-
-    it 'creates redis config with stop-writes-on-bgsave-error no' do
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content { |content|
-          expect(content).to match(/^stop-writes-on-bgsave-error no$/)
-        }
-    end
-  end
-
-  context 'with tls settings specified' do
-    before do
-      stub_gitlab_rb(
-        redis: {
-          tls_port: 6380,
-          tls_cert_file: '/etc/gitlab/ssl/redis.crt',
-          tls_key_file: '/etc/gitlab/ssl/redis.key',
-          tls_dh_params_file: '/etc/gitlab/ssl/redis-dhparams',
-          tls_ca_cert_file: '/etc/gitlab/ssl/redis-ca.crt',
-          tls_ca_cert_dir: '/opt/gitlab/embedded/ssl/certs',
-          tls_auth_clients: 'no',
-          tls_replication: 'yes',
-          tls_cluster: 'yes',
-          tls_protocols: 'TLSv1.2 TLSv1.3',
-          tls_ciphers: 'DEFAULT:!MEDIUM',
-          tls_ciphersuites: 'TLS_CHACHA20_POLY1305_SHA256',
-          tls_prefer_server_ciphers: 'yes',
-          tls_session_caching: 'no',
-          tls_session_cache_size: 10000,
-          tls_session_cache_timeout: 120
-        }
-      )
-    end
-
-    it 'renders redis config with tls settings' do
-      expect(chef_run).to render_file('/var/opt/gitlab/redis/redis.conf')
-        .with_content { |content|
-          expect(content).to match(%r{^tls-port 6380$})
-          expect(content).to match(%r{^tls-cert-file /etc/gitlab/ssl/redis.crt$})
-          expect(content).to match(%r{^tls-key-file /etc/gitlab/ssl/redis.key$})
-          expect(content).to match(%r{^tls-dh-params-file /etc/gitlab/ssl/redis-dhparams$})
-          expect(content).to match(%r{^tls-ca-cert-file /etc/gitlab/ssl/redis-ca.crt$})
-          expect(content).to match(%r{^tls-ca-cert-dir /opt/gitlab/embedded/ssl/certs$})
-          expect(content).to match(%r{^tls-auth-clients no$})
-          expect(content).to match(%r{^tls-replication yes$})
-          expect(content).to match(%r{^tls-cluster yes$})
-          expect(content).to match(%r{^tls-protocols "TLSv1.2 TLSv1.3"$})
-          expect(content).to match(%r{^tls-ciphers DEFAULT:!MEDIUM$})
-          expect(content).to match(%r{^tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256$})
-          expect(content).to match(%r{^tls-prefer-server-ciphers yes$})
-          expect(content).to match(%r{^tls-session-caching no$})
-          expect(content).to match(%r{^tls-session-cache-size 10000$})
-          expect(content).to match(%r{^tls-session-cache-timeout 120$})
-        }
-    end
-  end
-
-  context 'with redis disabled' do
-    before do
-      stub_gitlab_rb(redis: { enable: false })
-    end
-
-    it_behaves_like 'disabled runit service', 'redis', 'root', 'root'
-  end
-end
diff --git a/spec/chef/secrets_spec.rb b/spec/chef/secrets_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3NlY3JldHNfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/chef/secrets_spec.rb
+++ /dev/null
@@ -1,375 +0,0 @@
-require 'chef_helper'
-require 'base64'
-
-RSpec.describe 'secrets' do
-  let(:chef_run) { ChefSpec::SoloRunner.new.converge('gitlab::default') }
-
-  HEX_KEY = /\h{128}/.freeze
-  RSA_KEY = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m.freeze
-
-  def stub_gitlab_secrets_json(secrets)
-    allow(File).to receive(:read).with('/etc/gitlab/gitlab-secrets.json').and_return(JSON.generate(secrets))
-  end
-
-  before do
-    allow(File).to receive(:directory?).and_call_original
-    allow(File).to receive(:exist?).and_call_original
-    allow(File).to receive(:read).and_call_original
-    allow(File).to receive(:open).and_call_original
-    allow(File).to receive(:open).with('/etc/gitlab/initial_root_password', 'w', 0600).and_yield(double(:file, write: true)).once
-  end
-
-  context 'when /etc/gitlab does not exist' do
-    it 'does not write secrets to the file' do
-      allow(File).to receive(:directory?).with('/etc/gitlab').and_return(false)
-      expect(File).not_to receive(:open).with('/etc/gitlab/gitlab-secrets.json', 'w')
-
-      chef_run
-    end
-  end
-
-  context 'when /etc/gitlab exists' do
-    let(:file) { double(:file) }
-    let(:new_secrets) { @new_secrets }
-    let(:gitlab_rb_ci_jwt_signing_key) { SecretsHelper.generate_rsa(4096).to_pem }
-
-    before do
-      allow(SecretsHelper).to receive(:system)
-      allow(File).to receive(:directory?).with('/etc/gitlab').and_return(true)
-      allow(File).to receive(:open).with('/etc/gitlab/gitlab-secrets.json', 'w', 0600).and_yield(file).once
-      allow(file).to receive(:puts) { |json| @new_secrets = JSON.parse(json) }
-      allow(file).to receive(:chmod).and_return(true)
-    end
-
-    context 'when there are no existing secrets' do
-      before do
-        allow(File).to receive(:exist?).with('/etc/gitlab/gitlab-secrets.json').and_return(false)
-
-        chef_run
-      end
-
-      it 'writes new secrets to the file, with different values for each' do
-        rails_keys = new_secrets['gitlab_rails']
-        hex_keys = rails_keys.values_at('db_key_base', 'otp_key_base', 'secret_key_base', 'encrypted_settings_key_base')
-        rsa_keys = rails_keys.values_at('openid_connect_signing_key', 'ci_jwt_signing_key')
-
-        expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a)
-        expect(hex_keys).to all(match(HEX_KEY))
-        expect(rsa_keys).to all(match(RSA_KEY))
-      end
-
-      it 'does not write legacy keys' do
-        expect(new_secrets).not_to have_key('gitlab_ci')
-        expect(new_secrets['gitlab_rails']).not_to have_key('jws_private_key')
-      end
-
-      it 'generates an appropriate secret for gitlab-workhorse' do
-        workhorse_secret = new_secrets['gitlab_workhorse']['secret_token']
-        expect(Base64.strict_decode64(workhorse_secret).length).to eq(32)
-      end
-
-      it 'generates an appropriate shared secret for gitlab-pages' do
-        pages_shared_secret = new_secrets['gitlab_pages']['api_secret_key']
-        expect(Base64.strict_decode64(pages_shared_secret).length).to eq(32)
-      end
-
-      it 'generates an appropriate shared secret for gitlab-kas' do
-        kas_shared_secret = new_secrets['gitlab_kas']['api_secret_key']
-        expect(Base64.strict_decode64(kas_shared_secret).length).to eq(32)
-      end
-    end
-
-    context 'gitlab.rb provided gitlab_pages.api_secret_key' do
-      before do
-        allow(Gitlab).to receive(:[]).and_call_original
-      end
-
-      it 'fails when provided gitlab_pages.shared_secret is not 32 bytes' do
-        stub_gitlab_rb(gitlab_pages: { api_secret_key: SecureRandom.base64(16) })
-
-        expect { chef_run }.to raise_error(RuntimeError, /gitlab_pages\['api_secret_key'\] should be exactly 32 bytes/)
-      end
-
-      it 'accepts provided gitlab_pages.api_secret_key when it is 32 bytes' do
-        api_secret_key = SecureRandom.base64(32)
-        stub_gitlab_rb(gitlab_pages: { api_secret_key: api_secret_key })
-
-        expect { chef_run }.not_to raise_error
-        expect(new_secrets['gitlab_pages']['api_secret_key']).to eq(api_secret_key)
-      end
-    end
-
-    context 'gitlab.rb provided gitlab_kas.api_secret_key' do
-      before do
-        allow(Gitlab).to receive(:[]).and_call_original
-      end
-
-      it 'fails when provided gitlab_kas.shared_secret is not 32 bytes' do
-        stub_gitlab_rb(gitlab_kas: { api_secret_key: SecureRandom.base64(16) })
-
-        expect { chef_run }.to raise_error(RuntimeError, /gitlab_kas\['api_secret_key'\] should be exactly 32 bytes/)
-      end
-
-      it 'accepts provided gitlab_kas.api_secret_key when it is 32 bytes' do
-        api_secret_key = SecureRandom.base64(32)
-        stub_gitlab_rb(gitlab_kas: { api_secret_key: api_secret_key })
-
-        expect { chef_run }.not_to raise_error
-        expect(new_secrets['gitlab_kas']['api_secret_key']).to eq(api_secret_key)
-      end
-    end
-
-    context 'when there are existing secrets in /etc/gitlab/gitlab-secrets.json' do
-      before do
-        allow(SecretsHelper).to receive(:system)
-        allow(File).to receive(:directory?).with('/etc/gitlab').and_return(true)
-        allow(File).to receive(:open).with('/etc/gitlab/gitlab-secrets.json', 'w').and_yield(file).once
-        allow(File).to receive(:exist?).with('/etc/gitlab/gitlab-secrets.json').and_return(true)
-      end
-
-      context 'when secrets are only partially present' do
-        before do
-          stub_gitlab_secrets_json(gitlab_ci: { db_key_base: 'json_ci_db_key_base' })
-          chef_run
-        end
-
-        it 'uses secrets from /etc/gitlab/gitlab-secrets.json where available' do
-          expect(new_secrets['gitlab_rails']['db_key_base']).to eq('json_ci_db_key_base')
-        end
-
-        it 'falls back further to generating new secrets' do
-          expect(new_secrets['gitlab_rails']['otp_key_base']).to match(HEX_KEY)
-        end
-      end
-
-      context 'when secrets exist under legacy keys' do
-        before do
-          stub_gitlab_secrets_json(
-            gitlab_ci: { db_key_base: 'json_ci_db_key_base', secret_token: 'json_ci_secret_token' },
-            gitlab_rails: {
-              secret_token: 'json_rails_secret_token',
-              jws_private_key: 'json_rails_jws_private_key'
-            }
-          )
-
-          chef_run
-        end
-
-        it 'moves gitlab_ci.db_key_base to gitlab_rails.db_key_base' do
-          expect(new_secrets['gitlab_rails']['db_key_base']).to eq('json_ci_db_key_base')
-        end
-
-        it 'moves gitlab_rails.secret_token to gitlab_rails.otp_key_base' do
-          expect(new_secrets['gitlab_rails']['otp_key_base']).to eq('json_rails_secret_token')
-        end
-
-        it 'moves gitlab_ci.db_key_base to gitlab_rails.secret_key_base' do
-          expect(new_secrets['gitlab_rails']['secret_key_base']).to eq('json_ci_db_key_base')
-        end
-
-        it 'moves gitlab_rails.jws_private_key to gitlab_rails.openid_connect_signing_key' do
-          expect(new_secrets['gitlab_rails']['openid_connect_signing_key']).to eq('json_rails_jws_private_key')
-        end
-
-        it 'ignores other, unused, secrets' do
-          expect(new_secrets.inspect).not_to include('json_ci_secret_token')
-        end
-      end
-    end
-
-    context 'when there are existing secrets in /etc/gitlab/gitlab.rb and /etc/gitlab/gitlab-secrets.json' do
-      before do
-        allow(Gitlab).to receive(:[]).and_call_original
-        allow(File).to receive(:exist?).with('/etc/gitlab/gitlab-secrets.json').and_return(true)
-      end
-
-      context 'when secrets are only partially present' do
-        before do
-          stub_gitlab_secrets_json(
-            gitlab_ci: { db_key_base: 'json_ci_db_key_base' },
-            gitlab_rails: {
-              secret_token: 'json_rails_secret_token',
-              jws_private_key: 'json_rails_jws_private_key'
-            }
-          )
-
-          stub_gitlab_rb(gitlab_ci: { db_key_base: 'rb_ci_db_key_base' })
-
-          chef_run
-        end
-
-        it 'uses secrets from /etc/gitlab/gitlab.rb when available' do
-          expect(new_secrets['gitlab_rails']['db_key_base']).to eq('rb_ci_db_key_base')
-        end
-
-        it 'falls back to secrets from /etc/gitlab/gitlab-secrets.json' do
-          expect(new_secrets['gitlab_rails']['otp_key_base']).to eq('json_rails_secret_token')
-        end
-
-        it 'falls back further to generating new secrets' do
-          expect(new_secrets['gitlab_shell']['secret_token']).to match(HEX_KEY)
-        end
-      end
-
-      context 'when secrets exist under legacy keys' do
-        before do
-          stub_gitlab_rb(gitlab_ci: { db_key_base: 'rb_ci_db_key_base',
-                                      secret_token: 'rb_ci_secret_token' })
-
-          stub_gitlab_secrets_json(
-            gitlab_rails: {
-              secret_token: 'json_rails_secret_token',
-              jws_private_key: 'json_rails_jws_private_key',
-              encrypted_settings_key_base: 'encrypted_settings_key_base',
-              ci_jwt_signing_key: gitlab_rb_ci_jwt_signing_key
-            }
-          )
-
-          chef_run
-        end
-
-        it 'moves gitlab_ci.db_key_base to gitlab_rails.db_key_base' do
-          expect(new_secrets['gitlab_rails']['db_key_base']).to eq('rb_ci_db_key_base')
-        end
-
-        it 'moves gitlab_rails.secret_token to gitlab_rails.otp_key_base' do
-          expect(new_secrets['gitlab_rails']['otp_key_base']).to eq('json_rails_secret_token')
-        end
-
-        it 'moves gitlab_ci.db_key_base to gitlab_rails.secret_key_base' do
-          expect(new_secrets['gitlab_rails']['secret_key_base']).to eq('rb_ci_db_key_base')
-        end
-
-        it 'moves gitlab_rails.jws_private_key to gitlab_rails.openid_connect_signing_key' do
-          expect(new_secrets['gitlab_rails']['openid_connect_signing_key']).to eq('json_rails_jws_private_key')
-        end
-
-        it 'ignores other, unused, secrets' do
-          expect(new_secrets.inspect).not_to include('rb_ci_secret_token')
-        end
-
-        it 'writes the correct data to secrets.yml' do
-          expect(chef_run).to create_templatesymlink('Create a secrets.yml and create a symlink to Rails root').with_variables(
-            'secrets' => {
-              'production' => {
-                'db_key_base' => 'rb_ci_db_key_base',
-                'secret_key_base' => 'rb_ci_db_key_base',
-                'otp_key_base' => 'json_rails_secret_token',
-                'encrypted_settings_key_base' => 'encrypted_settings_key_base',
-                'openid_connect_signing_key' => 'json_rails_jws_private_key',
-                'ci_jwt_signing_key' => gitlab_rb_ci_jwt_signing_key
-              }
-            }
-          )
-        end
-
-        it 'deletes the secret file' do
-          expect(chef_run).to delete_file('/var/opt/gitlab/gitlab-rails/etc/secret')
-          expect(chef_run).to delete_file('/opt/gitlab/embedded/service/gitlab-rails/.secret')
-        end
-      end
-
-      context 'when secrets are ambiguous and cannot be migrated automatically' do
-        before { stub_gitlab_secrets_json({}) }
-
-        it 'fails when gitlab_ci.db_key_base and gitlab_rails.db_key_base are different' do
-          stub_gitlab_rb(
-            gitlab_rails: { db_key_base: 'rb_rails_db_key_base' },
-            gitlab_ci: { db_key_base: 'rb_ci_db_key_base' }
-          )
-
-          expect(File).not_to receive(:open).with('/etc/gitlab/gitlab-secrets.json', 'w')
-          expect { chef_run }.to raise_error(RuntimeError, /db_key_base/)
-        end
-
-        it 'fails when the secret file does not match gitlab_rails.otp_key_base' do
-          secret_file = '/var/opt/gitlab/gitlab-rails/etc/secret'
-
-          stub_gitlab_rb(gitlab_rails: { otp_key_base: 'rb_rails_otp_key_base',
-                                         secret_key_base: 'rb_rails_secret_key_base' })
-
-          allow(File).to receive(:exist?).with(secret_file).and_return(true)
-          allow(File).to receive(:read).with(secret_file).and_return('secret_key_base')
-
-          expect { chef_run }.to raise_error(RuntimeError, /otp_key_base/)
-        end
-      end
-
-      context 'ci_jwt_signing_key' do
-        let(:secrets_json_ci_jwt_signing_key) { SecretsHelper.generate_rsa(4096).to_pem }
-
-        it 'uses the key from /etc/gitlab/gitlab.rb when available' do
-          stub_gitlab_secrets_json(
-            gitlab_rails: { ci_jwt_signing_key: secrets_json_ci_jwt_signing_key }
-          )
-
-          stub_gitlab_rb(
-            gitlab_rails: { ci_jwt_signing_key: gitlab_rb_ci_jwt_signing_key }
-          )
-
-          chef_run
-
-          expect(new_secrets['gitlab_rails']['ci_jwt_signing_key']).to eq(gitlab_rb_ci_jwt_signing_key)
-        end
-
-        it 'uses the key from /etc/gitlab/gitlab-secrets.json when available' do
-          stub_gitlab_secrets_json(
-            gitlab_rails: { ci_jwt_signing_key: secrets_json_ci_jwt_signing_key }
-          )
-
-          chef_run
-
-          expect(new_secrets['gitlab_rails']['ci_jwt_signing_key']).to eq(secrets_json_ci_jwt_signing_key)
-        end
-
-        it 'rejects invalid RSA keys' do
-          stub_gitlab_secrets_json({})
-          stub_gitlab_rb(
-            gitlab_rails: { ci_jwt_signing_key: 'invalid key' }
-          )
-
-          expect { chef_run }.to raise_error('ci_jwt_signing_key: The provided key is not valid RSA key')
-        end
-
-        it 'rejects RSA public keys' do
-          public_key = SecretsHelper.generate_rsa(4096).public_key.to_pem
-
-          stub_gitlab_secrets_json({})
-          stub_gitlab_rb(
-            gitlab_rails: { ci_jwt_signing_key: public_key }
-          )
-
-          expect { chef_run }.to raise_error('ci_jwt_signing_key: The provided key is not private RSA key')
-        end
-
-        it 'writes the correct data to secrets.yml' do
-          stub_gitlab_secrets_json({})
-
-          stub_gitlab_rb(
-            gitlab_rails: {
-              db_key_base: 'rb_ci_db_key_base',
-              secret_key_base: 'rb_ci_db_key_base',
-              otp_key_base: 'json_rails_secret_token',
-              encrypted_settings_key_base: 'encrypted_settings_key_base',
-              openid_connect_signing_key: 'json_rails_jws_private_key',
-              ci_jwt_signing_key: gitlab_rb_ci_jwt_signing_key
-            }
-          )
-
-          expect(chef_run).to create_templatesymlink('Create a secrets.yml and create a symlink to Rails root').with_variables(
-            'secrets' => {
-              'production' => {
-                'db_key_base' => 'rb_ci_db_key_base',
-                'secret_key_base' => 'rb_ci_db_key_base',
-                'otp_key_base' => 'json_rails_secret_token',
-                'encrypted_settings_key_base' => 'encrypted_settings_key_base',
-                'openid_connect_signing_key' => 'json_rails_jws_private_key',
-                'ci_jwt_signing_key' => gitlab_rb_ci_jwt_signing_key
-              }
-            }
-          )
-        end
-      end
-    end
-  end
-end
diff --git a/spec/chef/spamcheck/recipes/spamcheck_spec.rb b/spec/chef/spamcheck/recipes/spamcheck_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmL3NwYW1jaGVjay9yZWNpcGVzL3NwYW1jaGVja19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/chef/spamcheck/recipes/spamcheck_spec.rb
+++ /dev/null
@@ -1,274 +0,0 @@
-require 'chef_helper'
-require 'toml-rb'
-
-RSpec.describe 'spamcheck' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab-ee::default') }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'when spamcheck is disabled (default)' do
-    it 'includes spamcheck::disable recipe' do
-      expect(chef_run).to include_recipe('spamcheck::disable')
-    end
-  end
-
-  context 'when spamcheck is enabled' do
-    before do
-      stub_gitlab_rb(
-        spamcheck: {
-          enable: true
-        }
-      )
-    end
-
-    it 'includes spamcheck::enable recipe' do
-      expect(chef_run).to include_recipe('spamcheck::enable')
-    end
-  end
-
-  describe 'spamcheck::enable' do
-    context 'with default values' do
-      before do
-        stub_gitlab_rb(
-          spamcheck: {
-            enable: true
-          }
-        )
-      end
-
-      it 'creates necessary directories at default locations' do
-        %w[
-          /var/opt/gitlab/spamcheck
-          /var/opt/gitlab/spamcheck/sockets
-          /var/log/gitlab/spamcheck
-          /var/log/gitlab/spam-classifier
-        ].each do |dir|
-          expect(chef_run).to create_directory(dir).with(
-            owner: 'git',
-            mode: '0700',
-            recursive: true
-          )
-        end
-      end
-
-      it 'creates config.toml with default values' do
-        actual_content = get_rendered_toml(chef_run, '/var/opt/gitlab/spamcheck/config.toml')
-        expected_content = {
-          grpc: {
-            port: '8001'
-          },
-          rest: {
-            externalPort: ''
-          },
-          logger: {
-            level: 'info',
-            format: 'json',
-            output: 'stdout'
-          },
-          monitor: {
-            address: ":8003"
-          },
-          extraAttributes: {
-            monitorMode: 'false'
-          },
-          filter: {
-            allowList: {},
-            denyList: {}
-          },
-          preprocessor: {
-            socketPath: '/var/opt/gitlab/spamcheck/sockets/preprocessor.sock'
-          },
-          modelAttributes: {
-            modelPath: '/opt/gitlab/embedded/service/spam-classifier/model/issues/tflite/model.tflite'
-          }
-        }
-        expect(actual_content).to eq(expected_content)
-      end
-
-      it 'creates env directory with default variables' do
-        expect(chef_run).to create_env_dir('/opt/gitlab/etc/spamcheck/env').with_variables(
-          'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/'
-        )
-      end
-
-      it_behaves_like "enabled runit service", "spamcheck", "root", "root"
-      it_behaves_like "enabled runit service", "spam-classifier", "root", "root"
-
-      it 'creates runit files for spamcheck service' do
-        expected_content = <<~EOS
-          #!/bin/bash
-
-          # Let runit capture all script error messages
-          exec 2>&1
-
-          exec chpst -e /opt/gitlab/etc/spamcheck/env -P \\
-            -u git:git \\
-            -U git:git \\
-            /opt/gitlab/embedded/bin/spamcheck -config /var/opt/gitlab/spamcheck/config.toml
-        EOS
-
-        expect(chef_run).to render_file('/opt/gitlab/sv/spamcheck/run').with_content(expected_content)
-      end
-
-      it 'creates runit files for spam-classifier service' do
-        expected_content = <<~EOS
-          #!/bin/bash
-
-          # Let runit capture all script error messages
-          exec 2>&1
-
-          exec chpst -e /opt/gitlab/etc/spamcheck/env -P \\
-            -u git:git \\
-            -U git:git \\
-            /opt/gitlab/embedded/bin/python3 /opt/gitlab/embedded/service/spam-classifier/preprocessor/preprocess.py \\
-              --tokenizer-pickle-path /opt/gitlab/embedded/service/spam-classifier/preprocessor/tokenizer.pickle \\
-              --log-dir /var/log/gitlab/spam-classifier \\
-              --socket-dir /var/opt/gitlab/spamcheck/sockets
-        EOS
-
-        expect(chef_run).to render_file('/opt/gitlab/sv/spam-classifier/run').with_content(expected_content)
-      end
-    end
-
-    context 'with user specified values' do
-      before do
-        stub_gitlab_rb(
-          user: {
-            username: 'randomuser',
-            group: 'randomgroup'
-          },
-          spamcheck: {
-            enable: true,
-            dir: '/data/spamcheck',
-            port: 5001,
-            external_port: 5002,
-            monitoring_address: ':5003',
-            log_level: 'debug',
-            log_format: 'text',
-            log_output: 'file',
-            monitor_mode: true,
-            allowlist: {
-              '14' => 'spamtest/hello'
-            },
-            denylist: {
-              '15' => 'foobar/random'
-            },
-            env_directory: '/env/spamcheck',
-            log_directory: '/log/spamcheck',
-            env: {
-              'FOO' => 'BAR'
-            },
-            classifier: {
-              log_directory: '/log/spam-classifier'
-            }
-          }
-        )
-      end
-
-      it 'creates necessary directories at user specified locations' do
-        %w[
-          /data/spamcheck
-          /data/spamcheck/sockets
-          /log/spamcheck
-          /log/spam-classifier
-        ].each do |dir|
-          expect(chef_run).to create_directory(dir).with(
-            owner: 'randomuser',
-            mode: '0700',
-            recursive: true
-          )
-        end
-      end
-
-      it 'creates config.toml with user specified values' do
-        actual_content = get_rendered_toml(chef_run, '/data/spamcheck/config.toml')
-        expected_content = {
-          grpc: {
-            port: '5001'
-          },
-          rest: {
-            externalPort: '5002'
-          },
-          logger: {
-            level: 'debug',
-            format: 'text',
-            output: 'file'
-          },
-          monitor: {
-            address: ":5003"
-          },
-          extraAttributes: {
-            monitorMode: 'true'
-          },
-          filter: {
-            allowList: {
-              '14': 'spamtest/hello'
-            },
-            denyList: {
-              '15': 'foobar/random'
-            }
-          },
-          preprocessor: {
-            socketPath: '/data/spamcheck/sockets/preprocessor.sock'
-          },
-          modelAttributes: {
-            modelPath: '/opt/gitlab/embedded/service/spam-classifier/model/issues/tflite/model.tflite'
-          }
-        }
-        expect(actual_content).to eq(expected_content)
-      end
-
-      it 'creates env directory with user specified and default variables' do
-        expect(chef_run).to create_env_dir('/env/spamcheck').with_variables(
-          'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
-          'FOO' => 'BAR'
-        )
-      end
-
-      it_behaves_like "enabled runit service", "spamcheck", "root", "root"
-      it_behaves_like "enabled runit service", "spam-classifier", "root", "root"
-
-      it 'creates runit files for spamcheck service' do
-        expected_content = <<~EOS
-          #!/bin/bash
-
-          # Let runit capture all script error messages
-          exec 2>&1
-
-          exec chpst -e /env/spamcheck -P \\
-            -u randomuser:randomgroup \\
-            -U randomuser:randomgroup \\
-            /opt/gitlab/embedded/bin/spamcheck -config /data/spamcheck/config.toml
-        EOS
-
-        expect(chef_run).to render_file('/opt/gitlab/sv/spamcheck/run').with_content(expected_content)
-      end
-
-      it 'creates runit files for spam-classifier service' do
-        expected_content = <<~EOS
-          #!/bin/bash
-
-          # Let runit capture all script error messages
-          exec 2>&1
-
-          exec chpst -e /env/spamcheck -P \\
-            -u randomuser:randomgroup \\
-            -U randomuser:randomgroup \\
-            /opt/gitlab/embedded/bin/python3 /opt/gitlab/embedded/service/spam-classifier/preprocessor/preprocess.py \\
-              --tokenizer-pickle-path /opt/gitlab/embedded/service/spam-classifier/preprocessor/tokenizer.pickle \\
-              --log-dir /log/spam-classifier \\
-              --socket-dir /data/spamcheck/sockets
-        EOS
-
-        expect(chef_run).to render_file('/opt/gitlab/sv/spam-classifier/run').with_content(expected_content)
-      end
-    end
-  end
-
-  describe 'spamcheck::disable' do
-    it_behaves_like "disabled runit service", "spamcheck", "root", "root"
-    it_behaves_like "disabled runit service", "spam-classifier", "root", "root"
-  end
-end
diff --git a/spec/chef/support/shared_context/ctl_shared_context.rb b/spec/chef/support/shared_context/ctl_shared_context.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL3N1cHBvcnQvc2hhcmVkX2NvbnRleHQvY3RsX3NoYXJlZF9jb250ZXh0LnJi
--- /dev/null
+++ b/spec/chef/support/shared_context/ctl_shared_context.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+require 'omnibus-ctl'
+
+RSpec.shared_context 'ctl' do
+  let(:ctl) { Omnibus::Ctl.new('testing-ctl') }
+
+  before do
+    allow_any_instance_of(Omnibus::Ctl).to receive(:require).and_call_original
+
+    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
+      "/opt/testing-ctl/embedded/cookbooks/package/libraries/gitlab_cluster"
+    ) do
+      require_relative("../../../../files/gitlab-cookbooks/package/libraries/gitlab_cluster")
+    end
+
+    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
+      "/opt/testing-ctl/embedded/service/omnibus-ctl-ee/lib/geo/#{command_script}"
+    ) do
+      require_relative("../../../../files/gitlab-ctl-commands-ee/lib/geo/#{command_script}")
+    end
+
+    ctl.load_file("files/gitlab-ctl-commands-ee/#{command_script}.rb")
+  end
+end
diff --git a/spec/chef/support/shared_context/gitlab_rails_shared_context.rb b/spec/chef/support/shared_context/gitlab_rails_shared_context.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL3N1cHBvcnQvc2hhcmVkX2NvbnRleHQvZ2l0bGFiX3JhaWxzX3NoYXJlZF9jb250ZXh0LnJi
--- /dev/null
+++ b/spec/chef/support/shared_context/gitlab_rails_shared_context.rb
@@ -0,0 +1,14 @@
+require 'chef_helper'
+
+RSpec.shared_context 'gitlab-rails' do
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: 'templatesymlink').converge('gitlab::default') }
+  let(:gitlab_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/gitlab.yml') }
+  let(:gitlab_yml_file_content) { ChefSpec::Renderer.new(chef_run, gitlab_yml_template).content }
+  let(:gitlab_yml) { YAML.safe_load(gitlab_yml_file_content, [], [], true, symbolize_names: true) }
+  let(:config_dir) { '/var/opt/gitlab/gitlab-rails/etc/' }
+
+  before do
+    allow(Gitlab).to receive(:[]).and_call_original
+    allow(File).to receive(:symlink?).and_call_original
+  end
+end
diff --git a/spec/chef/support/shared_context/object_store_shared_context.rb b/spec/chef/support/shared_context/object_store_shared_context.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL3N1cHBvcnQvc2hhcmVkX2NvbnRleHQvb2JqZWN0X3N0b3JlX3NoYXJlZF9jb250ZXh0LnJi
--- /dev/null
+++ b/spec/chef/support/shared_context/object_store_shared_context.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+require 'omnibus-ctl'
+
+RSpec.shared_context 'object storage config' do
+  let(:object_config) do
+    {
+      artifacts: { bucket: 'artifacts' },
+      lfs: { bucket: 'lfs-objects' },
+      dependency_proxy: { bucket: 'dependency_proxy' },
+      external_diffs: { bucket: 'external_diffs' },
+      packages: { bucket: 'packages' },
+      terraform_state: { enabled: false, bucket: 'terraform' },
+      ci_secure_files: { bucket: 'ci_secure_files' },
+      uploads: { bucket: 'uploads' },
+      pages: { bucket: 'pages' }
+    }
+  end
+  let(:aws_connection_hash) do
+    {
+      'provider' => 'AWS',
+      'region' => 'eu-west-1',
+      'aws_access_key_id' => 'AKIAKIAKI',
+      'aws_secret_access_key' => 'secret123'
+    }
+  end
+  let(:aws_storage_options_hash) do
+    {
+      'server_side_encryption' => 'AES256',
+      'server_side_encryption_kms_key_id' => 'arn:aws:12345'
+    }
+  end
+  let(:azure_connection_hash) do
+    {
+      'provider' => 'AzureRM',
+      'azure_storage_account_name' => 'testaccount',
+      'azure_storage_access_key' => '1234abcd'
+    }
+  end
+
+  let(:aws_connection_data) { JSON.parse(aws_connection_hash.to_json, symbolize_names: true) }
+  let(:aws_storage_options) { JSON.parse(aws_storage_options_hash.to_json, symbolize_names: true) }
+end
diff --git a/spec/chef/support/shared_examples/consul_service_name.rb b/spec/chef/support/shared_examples/consul_service_name.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL3N1cHBvcnQvc2hhcmVkX2V4YW1wbGVzL2NvbnN1bF9zZXJ2aWNlX25hbWUucmI=
--- /dev/null
+++ b/spec/chef/support/shared_examples/consul_service_name.rb
@@ -0,0 +1,44 @@
+RSpec.shared_examples 'consul service discovery' do |gitlab_rb_key, service_name|
+  let(:gitlab_rb_setting) do
+    {
+      consul: {
+        enable: true,
+        monitoring_service_discovery: true
+      }
+    }
+  end
+
+  describe 'consul service discovery' do
+    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
+
+    context 'by default' do
+      it 'is not registered as consul service' do
+        expect(chef_run).not_to create_consul_service(service_name)
+      end
+    end
+
+    context 'when enabled' do
+      before do
+        stub_gitlab_rb(gitlab_rb_setting)
+      end
+
+      it 'is registered as a consul service with default name' do
+        expect(chef_run).to create_consul_service(service_name)
+      end
+
+      context 'with user specified service name' do
+        before do
+          gitlab_rb_setting[gitlab_rb_key] = {
+            enable: true,
+            consul_service_name: "#{service_name}-foobar"
+          }
+          stub_gitlab_rb(gitlab_rb_setting)
+        end
+
+        it 'is registered as a consul service with user specified name' do
+          expect(chef_run).to create_consul_service("#{service_name}-foobar")
+        end
+      end
+    end
+  end
+end
diff --git a/spec/chef/support/shared_examples/geo_promotion_command_accepts_option_shared_examples.rb b/spec/chef/support/shared_examples/geo_promotion_command_accepts_option_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL3N1cHBvcnQvc2hhcmVkX2V4YW1wbGVzL2dlb19wcm9tb3Rpb25fY29tbWFuZF9hY2NlcHRzX29wdGlvbl9zaGFyZWRfZXhhbXBsZXMucmI=
--- /dev/null
+++ b/spec/chef/support/shared_examples/geo_promotion_command_accepts_option_shared_examples.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+RSpec.shared_examples 'geo promotion command accepts option' do |passed_option, expected_option|
+  it 'accepts given option' do
+    allow_any_instance_of(klass).to receive(:execute).and_return(true)
+
+    # ARGV contains the commands that were passed to rspec, which are
+    # invalid for the omnibus-ctl commands
+    oldargv = ARGV
+    ARGV = [passed_option] # rubocop:disable Style/MutableConstant
+
+    expect(klass).to receive(:new).with(
+      anything, expected_option).and_call_original
+
+    ctl.send(command_script, ARGV)
+
+    ARGV = oldargv
+  end
+end
diff --git a/spec/chef/support/shared_examples/gitlab_geo_promotion_commands_shared_examples.rb b/spec/chef/support/shared_examples/gitlab_geo_promotion_commands_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL3N1cHBvcnQvc2hhcmVkX2V4YW1wbGVzL2dpdGxhYl9nZW9fcHJvbW90aW9uX2NvbW1hbmRzX3NoYXJlZF9leGFtcGxlcy5yYg==
--- /dev/null
+++ b/spec/chef/support/shared_examples/gitlab_geo_promotion_commands_shared_examples.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+RSpec.shared_examples 'gitlab geo promotion commands' do
+  it 'appends a geo replication command' do
+    expect(ctl.get_all_commands_hash).to include(command_name)
+  end
+
+  it 'executes the command when called' do
+    # ARGV contains the commands that were passed to rspec, which are
+    # invalid for the omnibus-ctl commands
+    oldargv = ARGV
+    ARGV = [] # rubocop:disable Style/MutableConstant
+
+    expect_any_instance_of(klass).to receive(:execute)
+
+    ctl.send(command_script)
+
+    ARGV = oldargv
+  end
+end
diff --git a/spec/chef/support/shared_examples/gitlab_yml_object_storage_shared_examples.rb b/spec/chef/support/shared_examples/gitlab_yml_object_storage_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL3N1cHBvcnQvc2hhcmVkX2V4YW1wbGVzL2dpdGxhYl95bWxfb2JqZWN0X3N0b3JhZ2Vfc2hhcmVkX2V4YW1wbGVzLnJi
--- /dev/null
+++ b/spec/chef/support/shared_examples/gitlab_yml_object_storage_shared_examples.rb
@@ -0,0 +1,69 @@
+RSpec.shared_examples 'renders object storage settings in gitlab.yml' do |component, component_default = {}, workhorse_accelerated = true|
+  include_context 'gitlab-rails'
+  include_context 'object storage config'
+
+  describe "for #{component}" do
+    context 'with default values' do
+      it 'renders gitlab.yml with object storage disabled and other default values' do
+        default_values = {
+          enabled: false,
+          remote_directory: component,
+          connection: {}
+        }
+
+        if workhorse_accelerated
+          default_values.merge!(
+            direct_upload: false,
+            background_upload: true,
+            proxy_download: false
+          )
+        end
+
+        default_values.merge!(component_default)
+
+        config = gitlab_yml[:production][component.to_sym][:object_store]
+        expect(config).to eq(default_values)
+      end
+    end
+
+    context 'with user specified values' do
+      before do
+        gitlab_rails_config = {
+          "#{component}_object_store_enabled" => true,
+          "#{component}_object_store_remote_directory" => 'foobar',
+          "#{component}_object_store_connection" => aws_connection_hash
+        }
+
+        if workhorse_accelerated
+          gitlab_rails_config.merge!(
+            "#{component}_object_store_direct_upload" => true,
+            "#{component}_object_store_background_upload" => false,
+            "#{component}_object_store_proxy_download" => true
+          )
+        end
+
+        stub_gitlab_rb(
+          gitlab_rails: gitlab_rails_config.transform_keys(&:to_sym)
+        )
+      end
+
+      it 'renders gitlab.yml with user specified values' do
+        expected_output = {
+          enabled: true,
+          connection: aws_connection_data,
+          remote_directory: 'foobar'
+        }
+
+        if workhorse_accelerated
+          expected_output.merge!(
+            direct_upload: true,
+            background_upload: false,
+            proxy_download: true
+          )
+        end
+
+        expect(gitlab_yml[:production][component.to_sym][:object_store]).to eq(expected_output)
+      end
+    end
+  end
+end
diff --git a/spec/chef/support/shared_examples/logrotate_config.rb b/spec/chef/support/shared_examples/logrotate_config.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL3N1cHBvcnQvc2hhcmVkX2V4YW1wbGVzL2xvZ3JvdGF0ZV9jb25maWcucmI=
--- /dev/null
+++ b/spec/chef/support/shared_examples/logrotate_config.rb
@@ -0,0 +1,9 @@
+RSpec.shared_examples 'configured logrotate service' do |svc, username = 'git', group = 'git'|
+  it 'creates logrotate config file' do
+    expect(chef_run).to render_file("/var/opt/gitlab/logrotate/logrotate.d/#{svc}")
+  end
+
+  it 'specifies su parameter in logrotate config' do
+    expect(chef_run).to render_file("/var/opt/gitlab/logrotate/logrotate.d/#{svc}").with_content(/su #{username} #{group}/)
+  end
+end
diff --git a/spec/chef/support/shared_examples/registry_service.rb b/spec/chef/support/shared_examples/registry_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL3N1cHBvcnQvc2hhcmVkX2V4YW1wbGVzL3JlZ2lzdHJ5X3NlcnZpY2UucmI=
--- /dev/null
+++ b/spec/chef/support/shared_examples/registry_service.rb
@@ -0,0 +1,102 @@
+RSpec.shared_examples 'enabled registry service' do
+  it 'creates default set of directories' do
+    expect(chef_run.node['registry']['dir'])
+      .to eql('/var/opt/gitlab/registry')
+    expect(chef_run.node['registry']['log_directory'])
+      .to eql('/var/log/gitlab/registry')
+    expect(chef_run.node['gitlab']['gitlab-rails']['registry_path'])
+      .to eql('/var/opt/gitlab/gitlab-rails/shared/registry')
+
+    expect(chef_run).to create_directory('/var/opt/gitlab/registry')
+    expect(chef_run).to create_directory('/var/log/gitlab/registry').with(
+      owner: 'registry',
+      group: nil,
+      mode: '0700'
+    )
+    expect(chef_run).to create_directory('/var/opt/gitlab/gitlab-rails/shared/registry').with(
+      owner: 'registry',
+      group: 'git',
+      mode: '0770'
+    )
+  end
+
+  it 'creates default user and group' do
+    expect(chef_run.node['registry']['username'])
+      .to eql('registry')
+    expect(chef_run.node['registry']['group'])
+      .to eql('registry')
+
+    expect(chef_run).to create_account('Docker registry user and group').with(
+      username: 'registry',
+      groupname: 'registry',
+      uid: nil,
+      gid: nil,
+      system: true,
+      home: '/var/opt/gitlab/registry'
+    )
+  end
+
+  it 'creates default self signed key-certificate pair' do
+    expect(chef_run).to create_file('/var/opt/gitlab/registry/gitlab-registry.crt').with(
+      user: 'registry',
+      group: 'registry'
+    )
+    expect(chef_run).to create_file('/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key').with(
+      user: 'git',
+      group: 'git'
+    )
+
+    expect(chef_run).to render_file('/var/opt/gitlab/registry/gitlab-registry.crt')
+      .with_content(/-----BEGIN CERTIFICATE-----/)
+    expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key')
+      .with_content(/-----BEGIN RSA PRIVATE KEY-----/)
+  end
+
+  it 'creates registry config.yml template' do
+    expect(chef_run).to create_template('/var/opt/gitlab/registry/config.yml').with(
+      owner: 'registry',
+      group: nil,
+      mode: '0644'
+    )
+    expect(chef_run).to(
+      render_file('/var/opt/gitlab/registry/config.yml').with_content do |content|
+        expect(content).to match(/version: 0.1/)
+        expect(content).to match(/realm: .*\/jwt\/auth/)
+        expect(content).to match(/addr: localhost:5000/)
+        expect(content).to match(%r(storage: {"filesystem":{"rootdirectory":"/var/opt/gitlab/gitlab-rails/shared/registry"}))
+        expect(content).to match(/health:\s*storagedriver:\s*enabled:\s*true/)
+        expect(content).to match(/log:\s*level: info\s*formatter:\s*text/)
+        expect(content).to match(/validation:\s*disabled: true$/)
+        expect(content).not_to match(/^compatibility:/)
+        expect(content).not_to match(/^middleware:/)
+      end
+    )
+  end
+
+  it 'populates default settings for svlogd' do
+    expect(chef_run).to render_file('/opt/gitlab/sv/registry/log/run')
+      .with_content(/exec svlogd -tt \/var\/log\/gitlab\/registry/)
+  end
+
+  it 'creates a default VERSION file and restarts service' do
+    expect(chef_run).to create_version_file('Create version file for Registry').with(
+      version_file_path: '/var/opt/gitlab/registry/VERSION',
+      version_check_cmd: '/opt/gitlab/embedded/bin/registry --version'
+    )
+
+    expect(chef_run.version_file('Create version file for Registry')).to notify('runit_service[registry]').to(:restart)
+  end
+
+  it 'creates gitlab-rails config with default values' do
+    expect(chef_run).to create_templatesymlink('Create a gitlab.yml and create a symlink to Rails root').with_variables(hash_including('registry_api_url' => 'http://localhost:5000'))
+  end
+
+  it 'sets default storage options' do
+    expect(chef_run.node['registry']['storage']['filesystem'])
+      .to eql('rootdirectory' => '/var/opt/gitlab/gitlab-rails/shared/registry')
+    expect(chef_run.node['registry']['storage']['cache'])
+      .to eql('blobdescriptor' => 'inmemory')
+    expect(chef_run.node['registry']['storage']['delete'])
+      .to eql('enabled' => true)
+  end
+end
diff --git a/spec/chef/support/shared_examples/runit_service.rb b/spec/chef/support/shared_examples/runit_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL3N1cHBvcnQvc2hhcmVkX2V4YW1wbGVzL3J1bml0X3NlcnZpY2UucmI=
--- /dev/null
+++ b/spec/chef/support/shared_examples/runit_service.rb
@@ -0,0 +1,120 @@
+def get_env_string(username, groupname)
+  env_string = ""
+  env_string << username if username
+  env_string << ":#{groupname}" if groupname
+
+  env_string
+end
+
+RSpec.shared_examples 'enabled runit service' do |svc_name, owner, group, username = nil, groupname = nil|
+  it 'creates directories' do
+    expect(chef_run).to create_directory("/opt/gitlab/sv/#{svc_name}").with(
+      owner: owner,
+      group: group,
+      mode: '0755'
+    )
+    expect(chef_run).to create_directory("/opt/gitlab/sv/#{svc_name}/log").with(
+      owner: owner,
+      group: group,
+      mode: '0755'
+    )
+    expect(chef_run).to create_directory("/opt/gitlab/sv/#{svc_name}/log/main").with(
+      owner: owner,
+      group: group,
+      mode: '0755'
+    )
+  end
+
+  it 'creates files' do
+    expect(chef_run).to create_template("/opt/gitlab/sv/#{svc_name}/run").with(
+      owner: owner,
+      group: group,
+      mode: '0755'
+    )
+    expect(chef_run).to create_template("/opt/gitlab/sv/#{svc_name}/log/run").with(
+      owner: owner,
+      group: group,
+      mode: '0755'
+    )
+    expect(chef_run).to create_template("/opt/gitlab/sv/#{svc_name}/log/config").with(
+      owner: owner,
+      group: group,
+      mode: '0644'
+    )
+
+    env_string = get_env_string(username, groupname)
+
+    if env_string.empty?
+      expect(chef_run).to render_file("/opt/gitlab/sv/#{svc_name}/run").with_content(%r{.*})
+    else
+      expect(chef_run).to render_file("/opt/gitlab/sv/#{svc_name}/run").with_content(/-u #{env_string}/)
+      expect(chef_run).to render_file("/opt/gitlab/sv/#{svc_name}/run").with_content(/-U #{env_string}/)
+    end
+
+    expect(chef_run).to render_file("/opt/gitlab/sv/#{svc_name}/log/run").with_content(%r{.*})
+    expect(chef_run).to render_file("/opt/gitlab/sv/#{svc_name}/log/config")
+  end
+
+  it 'verifies that file ownership persists' do
+    expect(chef_run.template("/opt/gitlab/sv/#{svc_name}/log/config")).to notify("ruby_block[verify_chown_persisted_on_#{svc_name}]")
+  end
+
+  it 'raises an error when file ownership does not persist' do
+    log_config_re = /\/opt\/gitlab\/sv\/.*\/log\/config/
+    allow_any_instance_of(OmnibusHelper).to receive(:expected_user?).and_return(true)
+    allow_any_instance_of(OmnibusHelper).to receive(:expected_user?).with(log_config_re, anything).and_return(false)
+
+    allow_any_instance_of(OmnibusHelper).to receive(:expected_group?).and_return(true)
+    allow_any_instance_of(OmnibusHelper).to receive(:expected_group?).with(log_config_re, anything).and_return(false)
+
+    block = chef_run.find_resource('ruby_block', "verify_chown_persisted_on_#{svc_name}")
+    expect { block.block.call }.to raise_error("Unable to persist filesystem ownership changes of /opt/gitlab/sv/#{svc_name}/log/config. See https://docs.gitlab.com/ee/administration/high_availability/nfs.html#recommended-options for guidance.")
+  end
+
+  it 'creates the symlink to the service directory' do
+    expect(chef_run).to create_link("/opt/gitlab/init/#{svc_name}").with(to: '/opt/gitlab/embedded/bin/sv')
+  end
+
+  context 'gitlab customization' do
+    # These are specs related to changes we have made to the upstream runit cookbook
+    before do
+      %w(ok status control).each do |target|
+        file_name = ::File.join('/opt/gitlab/service', svc_name, 'log', 'supervise', target)
+        allow_any_instance_of(OmnibusHelper).to receive(:expected_owner?).with(file_name, username, groupname).and_return(false)
+      end
+    end
+
+    it 'sets the supervise log files with correct permissions' do
+      # For some services, we set the ownership on the sv log files to allow supervisor_owner to run commands
+      unless username.nil?
+        expect(chef_run).to create_directory("/opt/gitlab/service/#{svc_name}/log/supervise").with(mode: '0755')
+        %w(ok status control).each do |target|
+          file_name = ::File.join('/opt/gitlab/service', svc_name, 'log', 'supervise', target)
+          expect(chef_run).to touch_file(file_name).with(
+            owner: username,
+            group: groupname
+          )
+        end
+      end
+    end
+  end
+end
+
+RSpec.shared_examples 'disabled runit service' do |svc_name|
+  it 'does not create directories' do
+    expect(chef_run).not_to create_directory("/opt/gitlab/sv/#{svc_name}")
+    expect(chef_run).not_to create_directory("/opt/gitlab/sv/#{svc_name}/log")
+    expect(chef_run).not_to create_directory("/opt/gitlab/sv/#{svc_name}/log/main")
+  end
+
+  it 'does not create files' do
+    expect(chef_run).not_to create_template("/opt/gitlab/sv/#{svc_name}/run")
+    expect(chef_run).not_to create_template("/opt/gitlab/sv/#{svc_name}/log/run")
+    expect(chef_run).not_to create_template("/var/log/gitlab/#{svc_name}/config")
+  end
+
+  it 'runs the disable service ruby block' do
+    allow_any_instance_of(Chef::Provider::RunitService).to receive(:enabled?).and_return(true)
+    expect(chef_run).to run_ruby_block("disable #{svc_name}")
+  end
+end
diff --git a/spec/chef/support/shared_examples/yaml_check.rb b/spec/chef/support/shared_examples/yaml_check.rb
new file mode 100644
index 0000000000000000000000000000000000000000..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmL3N1cHBvcnQvc2hhcmVkX2V4YW1wbGVzL3lhbWxfY2hlY2sucmI=
--- /dev/null
+++ b/spec/chef/support/shared_examples/yaml_check.rb
@@ -0,0 +1,9 @@
+require 'yaml'
+
+RSpec.shared_examples 'renders a valid YAML file' do |filename|
+  it 'parses YAML with no errors' do
+    expect(chef_run).to render_file(filename).with_content { |content|
+      expect { YAML.parse(content) }.not_to raise_error
+    }
+  end
+end
diff --git a/spec/chef_helper.rb b/spec/chef_helper.rb
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9jaGVmX2hlbHBlci5yYg==..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9jaGVmX2hlbHBlci5yYg== 100644
--- a/spec/chef_helper.rb
+++ b/spec/chef_helper.rb
@@ -5,6 +5,13 @@
 require 'chefspec'
 require 'ohai'
 
-# Load our cookbook libraries so we can stub them in our tests
-cookbooks = %w(package gitlab gitaly mattermost gitlab-ee letsencrypt monitoring patroni gitlab-kas gitlab-pages pgbouncer consul)
+# Load chef specific support libraries to provide common convenience methods for our tests
+Dir["./spec/chef/support/**/*.rb"].each { |library| require library }
+
+# Load our cookbook libraries so we can stub them in our tests. package, gitlab
+# and gitlab-ee needs to be loaded first as others depend on them for proper
+# functionality.
+cookbooks = %w[package gitlab gitlab-ee].map { |cookbook| File.join(__dir__, "../files/gitlab-cookbooks/#{cookbook}") }
+cookbooks = cookbooks.concat(Dir[File.join(__dir__, "../files/gitlab-cookbooks/*")].select { |d| File.directory?(d) }).uniq
+
 cookbooks.each do |cookbook|
@@ -10,5 +17,5 @@
 cookbooks.each do |cookbook|
-  Dir[File.join(__dir__, "../files/gitlab-cookbooks/#{cookbook}/libraries/**/*.rb")].each { |f| require f }
+  Dir["#{cookbook}/libraries/**/*.rb"].each { |library| require library }
 end
 
 def deep_clone(obj)
@@ -35,7 +42,7 @@
   config.platform = platform
   config.version = version
 
-  config.cookbook_path = ['files/gitlab-cookbooks/', 'spec/fixtures/cookbooks']
+  config.cookbook_path = ['files/gitlab-cookbooks/', 'spec/chef/fixtures/cookbooks']
   config.log_level = :error
 
   config.before do
@@ -66,7 +73,9 @@
 
     # Prevent chef converge from reloading any of our previously loaded libraries
     allow(Kernel).to receive(:load).and_call_original
-    cookbooks.each do |cookbook|
+
+    cookbooks.each do |cookbook_path|
+      cookbook = File.basename(cookbook_path)
       allow(Kernel).to receive(:load).with(%r{#{cookbook}/libraries}).and_return(true)
     end
 
diff --git a/spec/fixtures/cookbooks/acme/attributes/default.rb b/spec/fixtures/cookbooks/acme/attributes/default.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvYWNtZS9hdHRyaWJ1dGVzL2RlZmF1bHQucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/acme/attributes/default.rb
+++ /dev/null
@@ -1,1 +0,0 @@
-default['acme']['key_size'] = 2048
diff --git a/spec/fixtures/cookbooks/acme/metadata.rb b/spec/fixtures/cookbooks/acme/metadata.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvYWNtZS9tZXRhZGF0YS5yYg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/acme/metadata.rb
+++ /dev/null
@@ -1,1 +0,0 @@
-name 'acme'
diff --git a/spec/fixtures/cookbooks/acme/resources/certificate.rb b/spec/fixtures/cookbooks/acme/resources/certificate.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvYWNtZS9yZXNvdXJjZXMvY2VydGlmaWNhdGUucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/acme/resources/certificate.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-property :cn, String, name_property: true
-property :crt, String
-property :dir, [String, nil]
-property :key, String
-property :owner, [String, nil]
-property :chain, [String, nil]
-property :wwwroot, String
-property :alt_names, Array
-property :key_size, [Integer, nil]
-property :crt, [String, nil]
-property :group, [String, nil]
-property :contact, [String, Array, nil]
-
-actions :create
-default_action :create
diff --git a/spec/fixtures/cookbooks/acme/resources/selfsigned.rb b/spec/fixtures/cookbooks/acme/resources/selfsigned.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvYWNtZS9yZXNvdXJjZXMvc2VsZnNpZ25lZC5yYg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/acme/resources/selfsigned.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-property :crt, String
-property :key, String
-property :key_size, Integer
-property :chain, String
-property :alt_names, Array
-
-actions :create
-default_action :create
diff --git a/spec/fixtures/cookbooks/test_consul/metadata.rb b/spec/fixtures/cookbooks/test_consul/metadata.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9jb25zdWwvbWV0YWRhdGEucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_consul/metadata.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-name 'test_consul'
-depends 'consul'
diff --git a/spec/fixtures/cookbooks/test_consul/recipes/consul_service_address_port.rb b/spec/fixtures/cookbooks/test_consul/recipes/consul_service_address_port.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9jb25zdWwvcmVjaXBlcy9jb25zdWxfc2VydmljZV9hZGRyZXNzX3BvcnQucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_consul/recipes/consul_service_address_port.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-include_recipe 'test_consul::reload_consul'
-
-consul_service 'node-exporter' do
-  ip_address '0.0.0.0'
-  port 1234
-end
diff --git a/spec/fixtures/cookbooks/test_consul/recipes/consul_service_delete.rb b/spec/fixtures/cookbooks/test_consul/recipes/consul_service_delete.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9jb25zdWwvcmVjaXBlcy9jb25zdWxfc2VydmljZV9kZWxldGUucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_consul/recipes/consul_service_delete.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-include_recipe 'test_consul::reload_consul'
-
-consul_service 'delete_me' do
-  action :delete
-end
diff --git a/spec/fixtures/cookbooks/test_consul/recipes/consul_service_delete_no_reload.rb b/spec/fixtures/cookbooks/test_consul/recipes/consul_service_delete_no_reload.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9jb25zdWwvcmVjaXBlcy9jb25zdWxfc2VydmljZV9kZWxldGVfbm9fcmVsb2FkLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_consul/recipes/consul_service_delete_no_reload.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-include_recipe 'test_consul::reload_consul'
-
-consul_service 'delete_no_reload' do
-  action :delete
-  reload_service false
-end
diff --git a/spec/fixtures/cookbooks/test_consul/recipes/consul_service_meta.rb b/spec/fixtures/cookbooks/test_consul/recipes/consul_service_meta.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9jb25zdWwvcmVjaXBlcy9jb25zdWxfc2VydmljZV9tZXRhLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_consul/recipes/consul_service_meta.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-include_recipe 'test_consul::reload_consul'
-
-meta = { some_key: "value" }
-
-consul_service 'node-exporter' do
-  ip_address '0.0.0.0'
-  port 1234
-  meta meta
-end
diff --git a/spec/fixtures/cookbooks/test_consul/recipes/consul_service_socket.rb b/spec/fixtures/cookbooks/test_consul/recipes/consul_service_socket.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9jb25zdWwvcmVjaXBlcy9jb25zdWxfc2VydmljZV9zb2NrZXQucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_consul/recipes/consul_service_socket.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-include_recipe 'test_consul::reload_consul'
-
-consul_service 'node-exporter' do
-  socket_address '0.0.0.0:5678'
-  reload_service false
-end
diff --git a/spec/fixtures/cookbooks/test_consul/recipes/reload_consul.rb b/spec/fixtures/cookbooks/test_consul/recipes/reload_consul.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9jb25zdWwvcmVjaXBlcy9yZWxvYWRfY29uc3VsLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_consul/recipes/reload_consul.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# A stub to satisfy the `consul_service`
-execute 'reload consul' do
-  action :nothing
-end
diff --git a/spec/fixtures/cookbooks/test_crond/metadata.rb b/spec/fixtures/cookbooks/test_crond/metadata.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9jcm9uZC9tZXRhZGF0YS5yYg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_crond/metadata.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-name 'test_crond'
-depends 'crond'
diff --git a/spec/fixtures/cookbooks/test_crond/recipes/crond_job_delete.rb b/spec/fixtures/cookbooks/test_crond/recipes/crond_job_delete.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9jcm9uZC9yZWNpcGVzL2Nyb25kX2pvYl9kZWxldGUucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_crond/recipes/crond_job_delete.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-crond_job 'delete' do
-  action :delete
-end
diff --git a/spec/fixtures/cookbooks/test_crond/recipes/crond_job_minimal.rb b/spec/fixtures/cookbooks/test_crond/recipes/crond_job_minimal.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9jcm9uZC9yZWNpcGVzL2Nyb25kX2pvYl9taW5pbWFsLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_crond/recipes/crond_job_minimal.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-crond_job 'minimal' do
-  user "rspec"
-  command "echo 'Hello world'"
-end
diff --git a/spec/fixtures/cookbooks/test_gitlab/metadata.rb b/spec/fixtures/cookbooks/test_gitlab/metadata.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9naXRsYWIvbWV0YWRhdGEucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_gitlab/metadata.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-name 'test_gitlab'
-depends 'gitlab'
diff --git a/spec/fixtures/cookbooks/test_gitlab/recipes/puma_config_create.rb b/spec/fixtures/cookbooks/test_gitlab/recipes/puma_config_create.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9naXRsYWIvcmVjaXBlcy9wdW1hX2NvbmZpZ19jcmVhdGUucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_gitlab/recipes/puma_config_create.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-puma_config '/var/opt/gitlab/gitlab-rails/etc/puma.rb' do
-  environment 'production'
-  pid '/opt/gitlab/var/puma/puma.pid'
-  state_path '/opt/gitlab/var/puma/puma.state'
-  listen_socket '/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket'
-  listen_tcp '127.0.0.1:8080'
-  working_directory '/var/opt/gitlab/gitlab-rails/working'
-  worker_processes 2
-  cookbook 'gitlab'
-
-  action :create
-end
diff --git a/spec/fixtures/cookbooks/test_gitlab/recipes/puma_config_custom.rb b/spec/fixtures/cookbooks/test_gitlab/recipes/puma_config_custom.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9naXRsYWIvcmVjaXBlcy9wdW1hX2NvbmZpZ19jdXN0b20ucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_gitlab/recipes/puma_config_custom.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-puma_config '/var/opt/gitlab/gitlab-rails/etc/puma.rb' do
-  environment 'production'
-  pid '/opt/gitlab/var/puma/puma.pid'
-  state_path '/opt/gitlab/var/puma/puma.state'
-  listen_socket '/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket'
-  listen_tcp '127.0.0.1:8080'
-  working_directory '/var/opt/gitlab/gitlab-rails/working'
-  worker_processes 2
-  per_worker_max_memory_mb 1000
-  install_dir '/opt/custom/gitlab'
-  cookbook 'gitlab'
-
-  action :create
-end
diff --git a/spec/fixtures/cookbooks/test_gitlab/recipes/rails_migration_run.rb b/spec/fixtures/cookbooks/test_gitlab/recipes/rails_migration_run.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9naXRsYWIvcmVjaXBlcy9yYWlsc19taWdyYXRpb25fcnVuLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_gitlab/recipes/rails_migration_run.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-ruby_block 'test-dependent' do
-  true
-end
-
-rails_migration 'gitlab-test' do
-  rake_task 'gitlab:db:configure'
-  logfile_prefix 'gitlab-test-db-migrate'
-  helper RailsMigrationHelper.new(node)
-  environment Hash['SOME_ENV' => 'some-value']
-
-  dependent_services ["ruby_block[test-dependent]"]
-end
diff --git a/spec/fixtures/cookbooks/test_gitlab_ee/metadata.rb b/spec/fixtures/cookbooks/test_gitlab_ee/metadata.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9naXRsYWJfZWUvbWV0YWRhdGEucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_gitlab_ee/metadata.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-name 'test_gitlab_ee'
-depends 'gitlab-ee'
diff --git a/spec/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_geo.rb b/spec/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_geo.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9naXRsYWJfZWUvcmVjaXBlcy9wZ2JvdW5jZXJfdXNlcl9jcmVhdGVfZ2VvLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_geo.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-pgbouncer_user 'geo' do
-  helper GeoPgHelper.new(node)
-  user 'pgbouncer-geo'
-  password 'fakepassword-geo'
-  database 'fakedb-geo'
-  add_auth_function true
-  action :create
-end
diff --git a/spec/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_rails.rb b/spec/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_rails.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9naXRsYWJfZWUvcmVjaXBlcy9wZ2JvdW5jZXJfdXNlcl9jcmVhdGVfcmFpbHMucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_gitlab_ee/recipes/pgbouncer_user_create_rails.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-pgbouncer_user 'rails' do
-  helper PgHelper.new(node)
-  user 'pgbouncer-rails'
-  password 'fakepassword-rails'
-  database 'fakedb-rails'
-  add_auth_function true
-  action :create
-end
diff --git a/spec/fixtures/cookbooks/test_package/metadata.rb b/spec/fixtures/cookbooks/test_package/metadata.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wYWNrYWdlL21ldGFkYXRhLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_package/metadata.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-name 'test_package'
-depends 'package'
-depends 'runit'
diff --git a/spec/fixtures/cookbooks/test_package/recipes/account_create.rb b/spec/fixtures/cookbooks/test_package/recipes/account_create.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wYWNrYWdlL3JlY2lwZXMvYWNjb3VudF9jcmVhdGUucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_package/recipes/account_create.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-account 'foobar' do
-  username 'foo'
-  groupname 'bar'
-  manage true
-
-  action :create
-end
diff --git a/spec/fixtures/cookbooks/test_package/recipes/account_remove.rb b/spec/fixtures/cookbooks/test_package/recipes/account_remove.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wYWNrYWdlL3JlY2lwZXMvYWNjb3VudF9yZW1vdmUucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_package/recipes/account_remove.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-account 'foobar' do
-  username 'foo'
-  groupname 'bar'
-  manage true
-
-  action :remove
-end
diff --git a/spec/fixtures/cookbooks/test_package/recipes/env_dir_create.rb b/spec/fixtures/cookbooks/test_package/recipes/env_dir_create.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wYWNrYWdlL3JlY2lwZXMvZW52X2Rpcl9jcmVhdGUucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_package/recipes/env_dir_create.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-vars = {
-  'FOO' => 'Lorem',
-  'BAR' => 'Ipsum'
-}
-
-env_dir '/tmp/env' do
-  variables vars
-end
diff --git a/spec/fixtures/cookbooks/test_package/recipes/gitlab_sysctl_create.rb b/spec/fixtures/cookbooks/test_package/recipes/gitlab_sysctl_create.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wYWNrYWdlL3JlY2lwZXMvZ2l0bGFiX3N5c2N0bF9jcmVhdGUucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_package/recipes/gitlab_sysctl_create.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-gitlab_sysctl 'foo' do
-  value 15000
-end
diff --git a/spec/fixtures/cookbooks/test_package/recipes/storage_directory_create.rb b/spec/fixtures/cookbooks/test_package/recipes/storage_directory_create.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wYWNrYWdlL3JlY2lwZXMvc3RvcmFnZV9kaXJlY3RvcnlfY3JlYXRlLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_package/recipes/storage_directory_create.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-storage_directory '/tmp/bar' do
-  owner 'gitfoo'
-  group 'gitbar'
-  mode '0700'
-end
diff --git a/spec/fixtures/cookbooks/test_package/recipes/templatesymlink_create.rb b/spec/fixtures/cookbooks/test_package/recipes/templatesymlink_create.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wYWNrYWdlL3JlY2lwZXMvdGVtcGxhdGVzeW1saW5rX2NyZWF0ZS5yYg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_package/recipes/templatesymlink_create.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-templatesymlink 'sample template' do
-  link_from '/opt/gitlab/embedded/service/gitlab-rails/config/database.yml'
-  link_to '/var/opt/gitlab/gitlab-rails/etc/database.yml'
-  source 'test-template.erb'
-  variables(value: 500)
-end
diff --git a/spec/fixtures/cookbooks/test_package/recipes/templatesymlink_delete.rb b/spec/fixtures/cookbooks/test_package/recipes/templatesymlink_delete.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wYWNrYWdlL3JlY2lwZXMvdGVtcGxhdGVzeW1saW5rX2RlbGV0ZS5yYg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_package/recipes/templatesymlink_delete.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-templatesymlink 'delete' do
-  link_from '/opt/gitlab/embedded/service/gitlab-rails/config/database.yml'
-  link_to '/var/opt/gitlab/gitlab-rails/etc/database.yml'
-
-  action :delete
-end
diff --git a/spec/fixtures/cookbooks/test_package/recipes/version_file_create.rb b/spec/fixtures/cookbooks/test_package/recipes/version_file_create.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wYWNrYWdlL3JlY2lwZXMvdmVyc2lvbl9maWxlX2NyZWF0ZS5yYg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_package/recipes/version_file_create.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-runit_service 'foo' do
-end
-
-version_file 'Test version file creation' do
-  version_file_path '/tmp/VERSION_TEST'
-  version_check_cmd 'echo 1.0.0-test'
-  notifies :hup, "runit_service[foo]"
-end
diff --git a/spec/fixtures/cookbooks/test_package/templates/default/test-template.erb b/spec/fixtures/cookbooks/test_package/templates/default/test-template.erb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wYWNrYWdlL3RlbXBsYXRlcy9kZWZhdWx0L3Rlc3QtdGVtcGxhdGUuZXJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_package/templates/default/test-template.erb
+++ /dev/null
@@ -1,1 +0,0 @@
-value: <%= @value %>
diff --git a/spec/fixtures/cookbooks/test_postgresql/metadata.rb b/spec/fixtures/cookbooks/test_postgresql/metadata.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL21ldGFkYXRhLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/metadata.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-name 'test_postgresql'
-depends 'postgresql'
-depends 'gitlab'
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_config.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_config.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF9jb25maWcucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_config.rb
+++ /dev/null
@@ -1,1 +0,0 @@
-postgresql_config 'testconfig'
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF9kYXRhYmFzZV9jcmVhdGUucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-postgresql_database 'example' do
-end
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create_with_options.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create_with_options.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF9kYXRhYmFzZV9jcmVhdGVfd2l0aF9vcHRpb25zLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_database_create_with_options.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-postgresql_database 'fakedb' do
-  database_port 9999
-  database_socket '/fake/dir'
-  owner 'fakeuser'
-end
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_extension_enable.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_extension_enable.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF9leHRlbnNpb25fZW5hYmxlLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_extension_enable.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-postgresql_extension 'foobar' do
-  database 'lorem'
-end
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_query_run.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_query_run.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF9xdWVyeV9ydW4ucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_query_run.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require_relative '../../../../../files/gitlab-cookbooks/gitlab/libraries/helpers/geo_pg_helper'
-
-postgresql_query 'create schema' do
-  db_name 'omnibus_test'
-  query "CREATE SCHEMA example AUTHORIZATION foobar;"
-end
-
-postgresql_query 'create schema' do
-  db_name 'omnibus_test'
-  query "CREATE SCHEMA example AUTHORIZATION foobar;"
-  helper GeoPgHelper.new(node)
-end
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_schema_create.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_schema_create.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF9zY2hlbWFfY3JlYXRlLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_schema_create.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-postgresql_schema 'example' do
-  database 'omnibus_gitlab_test'
-end
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_create.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_create.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF91c2VyX2NyZWF0ZS5yYg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_create.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-postgresql_user 'example' do
-end
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_superuser.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_superuser.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF91c2VyX29wdGlvbnNfc3VwZXJ1c2VyLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_superuser.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-postgresql_user 'example' do
-  options ['SUPERUSER']
-end
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_unspecified.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_unspecified.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF91c2VyX29wdGlvbnNfdW5zcGVjaWZpZWQucmI=..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_options_unspecified.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-postgresql_user 'example' do
-end
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_empty.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_empty.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF91c2VyX3Bhc3N3b3JkX2VtcHR5LnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_empty.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-postgresql_user 'empty_password' do
-  password ''
-end
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_md5.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_md5.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF91c2VyX3Bhc3N3b3JkX21kNS5yYg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_md5.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-postgresql_user 'md5_password' do
-  # This is not a secret password:
-  #   ruby -rdigest -e 'puts Digest::MD5.hexdigest("toomanysecrets" + "md5_password")'
-  password 'e99b79fbdf9b997e6918df2385e60f5c'
-end
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_nil.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_nil.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF91c2VyX3Bhc3N3b3JkX25pbC5yYg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_nil.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-postgresql_user 'nil_password' do
-  password nil
-end
diff --git a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_unspecified.rb b/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_unspecified.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9jb29rYm9va3MvdGVzdF9wb3N0Z3Jlc3FsL3JlY2lwZXMvcG9zdGdyZXNxbF91c2VyX3Bhc3N3b3JkX3Vuc3BlY2lmaWVkLnJi..0000000000000000000000000000000000000000
--- a/spec/fixtures/cookbooks/test_postgresql/recipes/postgresql_user_password_unspecified.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-postgresql_user 'no_password' do
-end
diff --git a/spec/fixtures/fauxhai/ubuntu/16.04-docker.json b/spec/fixtures/fauxhai/ubuntu/16.04-docker.json
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9mYXV4aGFpL3VidW50dS8xNi4wNC1kb2NrZXIuanNvbg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/fauxhai/ubuntu/16.04-docker.json
+++ /dev/null
@@ -1,2130 +0,0 @@
-{
-  "block_device": {
-    "sda": {
-      "size": "1677721600",
-      "removable": "0",
-      "model": "PersistentDisk",
-      "rev": "1",
-      "state": "running",
-      "timeout": "30",
-      "vendor": "Google",
-      "queue_depth": "256",
-      "rotational": "1",
-      "physical_block_size": "4096",
-      "logical_block_size": "512"
-    },
-    "loop0": {
-      "size": "177368",
-      "removable": "0",
-      "rotational": "1",
-      "physical_block_size": "512",
-      "logical_block_size": "512"
-    },
-    "loop1": {
-      "size": "177960",
-      "removable": "0",
-      "rotational": "1",
-      "physical_block_size": "512",
-      "logical_block_size": "512"
-    },
-    "loop2": {
-      "size": "177928",
-      "removable": "0",
-      "rotational": "1",
-      "physical_block_size": "512",
-      "logical_block_size": "512"
-    },
-    "loop3": {
-      "size": "0",
-      "removable": "0",
-      "rotational": "1",
-      "physical_block_size": "512",
-      "logical_block_size": "512"
-    },
-    "loop4": {
-      "size": "0",
-      "removable": "0",
-      "rotational": "1",
-      "physical_block_size": "512",
-      "logical_block_size": "512"
-    },
-    "loop5": {
-      "size": "0",
-      "removable": "0",
-      "rotational": "1",
-      "physical_block_size": "512",
-      "logical_block_size": "512"
-    },
-    "loop6": {
-      "size": "0",
-      "removable": "0",
-      "rotational": "1",
-      "physical_block_size": "512",
-      "logical_block_size": "512"
-    },
-    "loop7": {
-      "size": "0",
-      "removable": "0",
-      "rotational": "1",
-      "physical_block_size": "512",
-      "logical_block_size": "512"
-    }
-  },
-  "chef_packages": {
-    "ohai": {
-      "version": "14.3.0",
-      "ohai_root": "/var/lib/gems/2.4.0/gems/ohai-14.3.0/lib/ohai"
-    }
-  },
-  "command": {
-    "ps": "ps -ef"
-  },
-  "counters": {
-    "network": {
-      "interfaces": {
-        "lo": {
-          "tx": {
-            "queuelen": "1",
-            "bytes": 0,
-            "packets": 0,
-            "errors": 0,
-            "drop": 0,
-            "carrier": 0,
-            "collisions": 0
-          },
-          "rx": {
-            "bytes": 0,
-            "packets": 0,
-            "errors": 0,
-            "drop": 0,
-            "overrun": 0
-          }
-        },
-        "eth0": {
-          "rx": {
-            "bytes": 0,
-            "packets": 0,
-            "errors": 0,
-            "drop": 0,
-            "overrun": 0,
-            "frame": 0,
-            "compressed": 0,
-            "multicast": 0
-          },
-          "tx": {
-            "bytes": 0,
-            "packets": 0,
-            "errors": 0,
-            "drop": 0,
-            "overrun": 0,
-            "collisions": 0,
-            "carrier": 0,
-            "compressed": 0
-          }
-        }
-      }
-    }
-  },
-  "cpu": {
-    "real": 1,
-    "total": 1,
-    "cores": 1
-  },
-  "current_user": "fauxhai",
-  "dmi": {
-  },
-  "domain": "local",
-  "filesystem": {
-    "by_device": {
-      "overlay": {
-        "kb_size": "813139348",
-        "kb_used": "314504628",
-        "kb_available": "498618336",
-        "percent_used": "39%",
-        "total_inodes": "102400000",
-        "inodes_used": "7082272",
-        "inodes_available": "95317728",
-        "inodes_percent_used": "7%",
-        "fs_type": "overlay",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "lowerdir=/var/lib/docker/overlay2/l/UVFEI53IFKBMSYIBD6MEWPNZ6F:/var/lib/docker/overlay2/l/BBMNBZRYFC727VBBGOEXGFD3HA:/var/lib/docker/overlay2/l/3CXZ5G5TCWJEUVMPSRSMBQD6Z4:/var/lib/docker/overlay2/l/GCI6OPPHSEJXZTDES7HKM4FJOA:/var/lib/docker/overlay2/l/XQ7VNACY7RTHN2VODETXL7PG2B:/var/lib/docker/overlay2/l/O63N6IYQUXRP6ATLHTRWF76PUK",
-          "upperdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/diff",
-          "workdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/work"
-        ],
-        "mounts": [
-          "/"
-        ]
-      },
-      "tmpfs": {
-        "kb_size": "3829544",
-        "kb_used": "0",
-        "kb_available": "3829544",
-        "percent_used": "0%",
-        "total_inodes": "957386",
-        "inodes_used": "1",
-        "inodes_available": "957385",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "size=65536k",
-          "mode=755"
-        ],
-        "mounts": [
-          "/dev",
-          "/sys/fs/cgroup",
-          "/sys/firmware",
-          "/proc/kcore",
-          "/proc/timer_list",
-          "/proc/timer_stats",
-          "/proc/sched_debug"
-        ]
-      },
-      "/dev/sda1": {
-        "kb_size": "813139348",
-        "kb_used": "314504628",
-        "kb_available": "498618336",
-        "percent_used": "39%",
-        "total_inodes": "102400000",
-        "inodes_used": "7082272",
-        "inodes_available": "95317728",
-        "inodes_percent_used": "7%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "mounts": [
-          "/etc/hosts",
-          "/etc/resolv.conf",
-          "/etc/hostname"
-        ]
-      },
-      "shm": {
-        "kb_size": "65536",
-        "kb_used": "0",
-        "kb_available": "65536",
-        "percent_used": "0%",
-        "total_inodes": "957386",
-        "inodes_used": "1",
-        "inodes_available": "957385",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "size=65536k"
-        ],
-        "mounts": [
-          "/dev/shm"
-        ]
-      },
-      "proc": {
-        "fs_type": "proc",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ],
-        "mounts": [
-          "/proc",
-          "/proc/bus",
-          "/proc/fs",
-          "/proc/irq",
-          "/proc/sys",
-          "/proc/sysrq-trigger"
-        ]
-      },
-      "devpts": {
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=666"
-        ],
-        "mounts": [
-          "/dev/pts",
-          "/dev/console"
-        ]
-      },
-      "sysfs": {
-        "fs_type": "sysfs",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys"
-        ]
-      },
-      "cgroup": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "memory"
-        ],
-        "mounts": [
-          "/sys/fs/cgroup/systemd",
-          "/sys/fs/cgroup/freezer",
-          "/sys/fs/cgroup/hugetlb",
-          "/sys/fs/cgroup/cpu,cpuacct",
-          "/sys/fs/cgroup/net_cls,net_prio",
-          "/sys/fs/cgroup/pids",
-          "/sys/fs/cgroup/blkio",
-          "/sys/fs/cgroup/devices",
-          "/sys/fs/cgroup/cpuset",
-          "/sys/fs/cgroup/perf_event",
-          "/sys/fs/cgroup/memory"
-        ]
-      },
-      "mqueue": {
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/dev/mqueue"
-        ]
-      },
-      "sda": {
-        "mounts": [
-
-        ]
-      },
-      "sda1": {
-        "mounts": [
-
-        ]
-      },
-      "loop0": {
-        "mounts": [
-
-        ]
-      },
-      "loop1": {
-        "mounts": [
-
-        ]
-      },
-      "loop2": {
-        "mounts": [
-
-        ]
-      }
-    },
-    "by_mountpoint": {
-      "/": {
-        "kb_size": "813139348",
-        "kb_used": "314504628",
-        "kb_available": "498618336",
-        "percent_used": "39%",
-        "total_inodes": "102400000",
-        "inodes_used": "7082272",
-        "inodes_available": "95317728",
-        "inodes_percent_used": "7%",
-        "fs_type": "overlay",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "lowerdir=/var/lib/docker/overlay2/l/UVFEI53IFKBMSYIBD6MEWPNZ6F:/var/lib/docker/overlay2/l/BBMNBZRYFC727VBBGOEXGFD3HA:/var/lib/docker/overlay2/l/3CXZ5G5TCWJEUVMPSRSMBQD6Z4:/var/lib/docker/overlay2/l/GCI6OPPHSEJXZTDES7HKM4FJOA:/var/lib/docker/overlay2/l/XQ7VNACY7RTHN2VODETXL7PG2B:/var/lib/docker/overlay2/l/O63N6IYQUXRP6ATLHTRWF76PUK",
-          "upperdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/diff",
-          "workdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/work"
-        ],
-        "devices": [
-          "overlay"
-        ]
-      },
-      "/dev": {
-        "kb_size": "65536",
-        "kb_used": "0",
-        "kb_available": "65536",
-        "percent_used": "0%",
-        "total_inodes": "957386",
-        "inodes_used": "17",
-        "inodes_available": "957369",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "size=65536k",
-          "mode=755"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/sys/fs/cgroup": {
-        "kb_size": "3829544",
-        "kb_used": "0",
-        "kb_available": "3829544",
-        "percent_used": "0%",
-        "total_inodes": "957386",
-        "inodes_used": "16",
-        "inodes_available": "957370",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "mode=755"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/etc/hosts": {
-        "kb_size": "813139348",
-        "kb_used": "314504628",
-        "kb_available": "498618336",
-        "percent_used": "39%",
-        "total_inodes": "102400000",
-        "inodes_used": "7082272",
-        "inodes_available": "95317728",
-        "inodes_percent_used": "7%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "devices": [
-          "/dev/sda1"
-        ]
-      },
-      "/dev/shm": {
-        "kb_size": "65536",
-        "kb_used": "0",
-        "kb_available": "65536",
-        "percent_used": "0%",
-        "total_inodes": "957386",
-        "inodes_used": "1",
-        "inodes_available": "957385",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "size=65536k"
-        ],
-        "devices": [
-          "shm"
-        ]
-      },
-      "/sys/firmware": {
-        "kb_size": "3829544",
-        "kb_used": "0",
-        "kb_available": "3829544",
-        "percent_used": "0%",
-        "total_inodes": "957386",
-        "inodes_used": "1",
-        "inodes_available": "957385",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/proc": {
-        "fs_type": "proc",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "proc"
-        ]
-      },
-      "/dev/pts": {
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=666"
-        ],
-        "devices": [
-          "devpts"
-        ]
-      },
-      "/sys": {
-        "fs_type": "sysfs",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "sysfs"
-        ]
-      },
-      "/sys/fs/cgroup/systemd": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "xattr",
-          "release_agent=/lib/systemd/systemd-cgroups-agent",
-          "name=systemd"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/freezer": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "freezer"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/hugetlb": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "hugetlb"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/cpu,cpuacct": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpu",
-          "cpuacct"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/net_cls,net_prio": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "net_cls",
-          "net_prio"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/pids": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "pids"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/blkio": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "blkio"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/devices": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "devices"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/cpuset": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpuset"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/perf_event": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "perf_event"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/memory": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "memory"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/dev/mqueue": {
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "mqueue"
-        ]
-      },
-      "/etc/resolv.conf": {
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "devices": [
-          "/dev/sda1"
-        ]
-      },
-      "/etc/hostname": {
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "devices": [
-          "/dev/sda1"
-        ]
-      },
-      "/dev/console": {
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=666"
-        ],
-        "devices": [
-          "devpts"
-        ]
-      },
-      "/proc/bus": {
-        "fs_type": "proc",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ],
-        "devices": [
-          "proc"
-        ]
-      },
-      "/proc/fs": {
-        "fs_type": "proc",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ],
-        "devices": [
-          "proc"
-        ]
-      },
-      "/proc/irq": {
-        "fs_type": "proc",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ],
-        "devices": [
-          "proc"
-        ]
-      },
-      "/proc/sys": {
-        "fs_type": "proc",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ],
-        "devices": [
-          "proc"
-        ]
-      },
-      "/proc/sysrq-trigger": {
-        "fs_type": "proc",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ],
-        "devices": [
-          "proc"
-        ]
-      },
-      "/proc/kcore": {
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "size=65536k",
-          "mode=755"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/proc/timer_list": {
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "size=65536k",
-          "mode=755"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/proc/timer_stats": {
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "size=65536k",
-          "mode=755"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/proc/sched_debug": {
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "size=65536k",
-          "mode=755"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      }
-    },
-    "by_pair": {
-      "overlay,/": {
-        "device": "overlay",
-        "kb_size": "813139348",
-        "kb_used": "314504628",
-        "kb_available": "498618336",
-        "percent_used": "39%",
-        "mount": "/",
-        "total_inodes": "102400000",
-        "inodes_used": "7082272",
-        "inodes_available": "95317728",
-        "inodes_percent_used": "7%",
-        "fs_type": "overlay",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "lowerdir=/var/lib/docker/overlay2/l/UVFEI53IFKBMSYIBD6MEWPNZ6F:/var/lib/docker/overlay2/l/BBMNBZRYFC727VBBGOEXGFD3HA:/var/lib/docker/overlay2/l/3CXZ5G5TCWJEUVMPSRSMBQD6Z4:/var/lib/docker/overlay2/l/GCI6OPPHSEJXZTDES7HKM4FJOA:/var/lib/docker/overlay2/l/XQ7VNACY7RTHN2VODETXL7PG2B:/var/lib/docker/overlay2/l/O63N6IYQUXRP6ATLHTRWF76PUK",
-          "upperdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/diff",
-          "workdir=/var/lib/docker/overlay2/a7bc5c3ab80b1b7dd4459606daa24928d243be3cce39890ed9a7ea796f2d3c05/work"
-        ]
-      },
-      "tmpfs,/dev": {
-        "device": "tmpfs",
-        "kb_size": "65536",
-        "kb_used": "0",
-        "kb_available": "65536",
-        "percent_used": "0%",
-        "mount": "/dev",
-        "total_inodes": "957386",
-        "inodes_used": "17",
-        "inodes_available": "957369",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "size=65536k",
-          "mode=755"
-        ]
-      },
-      "tmpfs,/sys/fs/cgroup": {
-        "device": "tmpfs",
-        "kb_size": "3829544",
-        "kb_used": "0",
-        "kb_available": "3829544",
-        "percent_used": "0%",
-        "mount": "/sys/fs/cgroup",
-        "total_inodes": "957386",
-        "inodes_used": "16",
-        "inodes_available": "957370",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "mode=755"
-        ]
-      },
-      "/dev/sda1,/etc/hosts": {
-        "device": "/dev/sda1",
-        "kb_size": "813139348",
-        "kb_used": "314504628",
-        "kb_available": "498618336",
-        "percent_used": "39%",
-        "mount": "/etc/hosts",
-        "total_inodes": "102400000",
-        "inodes_used": "7082272",
-        "inodes_available": "95317728",
-        "inodes_percent_used": "7%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ]
-      },
-      "shm,/dev/shm": {
-        "device": "shm",
-        "kb_size": "65536",
-        "kb_used": "0",
-        "kb_available": "65536",
-        "percent_used": "0%",
-        "mount": "/dev/shm",
-        "total_inodes": "957386",
-        "inodes_used": "1",
-        "inodes_available": "957385",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "size=65536k"
-        ]
-      },
-      "tmpfs,/sys/firmware": {
-        "device": "tmpfs",
-        "kb_size": "3829544",
-        "kb_used": "0",
-        "kb_available": "3829544",
-        "percent_used": "0%",
-        "mount": "/sys/firmware",
-        "total_inodes": "957386",
-        "inodes_used": "1",
-        "inodes_available": "957385",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ]
-      },
-      "proc,/proc": {
-        "device": "proc",
-        "mount": "/proc",
-        "fs_type": "proc",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "devpts,/dev/pts": {
-        "device": "devpts",
-        "mount": "/dev/pts",
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=666"
-        ]
-      },
-      "sysfs,/sys": {
-        "device": "sysfs",
-        "mount": "/sys",
-        "fs_type": "sysfs",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/systemd": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/systemd",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "xattr",
-          "release_agent=/lib/systemd/systemd-cgroups-agent",
-          "name=systemd"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/freezer": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/freezer",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "freezer"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/hugetlb": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/hugetlb",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "hugetlb"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/cpu,cpuacct": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/cpu,cpuacct",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpu",
-          "cpuacct"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/net_cls,net_prio": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/net_cls,net_prio",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "net_cls",
-          "net_prio"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/pids": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/pids",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "pids"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/blkio": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/blkio",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "blkio"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/devices": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/devices",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "devices"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/cpuset": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/cpuset",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpuset"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/perf_event": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/perf_event",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "perf_event"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/memory": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/memory",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "memory"
-        ]
-      },
-      "mqueue,/dev/mqueue": {
-        "device": "mqueue",
-        "mount": "/dev/mqueue",
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "/dev/sda1,/etc/resolv.conf": {
-        "device": "/dev/sda1",
-        "mount": "/etc/resolv.conf",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ]
-      },
-      "/dev/sda1,/etc/hostname": {
-        "device": "/dev/sda1",
-        "mount": "/etc/hostname",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ]
-      },
-      "devpts,/dev/console": {
-        "device": "devpts",
-        "mount": "/dev/console",
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=666"
-        ]
-      },
-      "proc,/proc/bus": {
-        "device": "proc",
-        "mount": "/proc/bus",
-        "fs_type": "proc",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ]
-      },
-      "proc,/proc/fs": {
-        "device": "proc",
-        "mount": "/proc/fs",
-        "fs_type": "proc",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ]
-      },
-      "proc,/proc/irq": {
-        "device": "proc",
-        "mount": "/proc/irq",
-        "fs_type": "proc",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ]
-      },
-      "proc,/proc/sys": {
-        "device": "proc",
-        "mount": "/proc/sys",
-        "fs_type": "proc",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ]
-      },
-      "proc,/proc/sysrq-trigger": {
-        "device": "proc",
-        "mount": "/proc/sysrq-trigger",
-        "fs_type": "proc",
-        "mount_options": [
-          "ro",
-          "relatime"
-        ]
-      },
-      "tmpfs,/proc/kcore": {
-        "device": "tmpfs",
-        "mount": "/proc/kcore",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "size=65536k",
-          "mode=755"
-        ]
-      },
-      "tmpfs,/proc/timer_list": {
-        "device": "tmpfs",
-        "mount": "/proc/timer_list",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "size=65536k",
-          "mode=755"
-        ]
-      },
-      "tmpfs,/proc/timer_stats": {
-        "device": "tmpfs",
-        "mount": "/proc/timer_stats",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "size=65536k",
-          "mode=755"
-        ]
-      },
-      "tmpfs,/proc/sched_debug": {
-        "device": "tmpfs",
-        "mount": "/proc/sched_debug",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "size=65536k",
-          "mode=755"
-        ]
-      },
-      "sda,": {
-        "device": "sda"
-      },
-      "sda1,": {
-        "device": "sda1"
-      },
-      "loop0,": {
-        "device": "loop0"
-      },
-      "loop1,": {
-        "device": "loop1"
-      },
-      "loop2,": {
-        "device": "loop2"
-      }
-    }
-  },
-  "fips": {
-    "kernel": {
-      "enabled": false
-    }
-  },
-  "fqdn": "fauxhai.local",
-  "hostname": "Fauxhai",
-  "idle": "30 days 15 hours 07 minutes 30 seconds",
-  "idletime_seconds": 2646450,
-  "init_package": "bash",
-  "ipaddress": "10.0.0.2",
-  "kernel": {
-    "name": "Linux",
-    "release": "4.4.0-130-generic",
-    "version": "#156-Ubuntu SMP Thu Jun 14 08:53:28 UTC 2018",
-    "machine": "x86_64",
-    "processor": "x86_64",
-    "os": "GNU/Linux",
-    "modules": {
-    }
-  },
-  "keys": {
-    "ssh": {
-      "host_dsa_public": "ssh-dss AAAAB3NzaC1kc3MAAACBAJFo9BLAw4WKEs5hgipk5m423FzBsDXCZSMcC9ca/om/1VYzMqImixGe3uICDzNFUWxFoLJTQAOccyzo6MXZiQqwWJDLFi5qOSr6w2XcMyE+zd4wOyMoDiVM5fizmG8K3FzrqvGjwBcHcBdOQnavSijoj38DN25J9zhrid5BY4WlAAAAFQDxXrCyG52XCzn3FV4ej38wJBkomQAAAIBovGPJ4mP2P6BK8lHl0PPbktwQbWlpJ13oz6REJFDVcUi7vV26bX/BjQX+ohzZQzljdz1SpUbPc/8nuA4darYkVh91eBi307EN8IdxRHj2eBgp/ZG4yshIebG3WHrwJD/xUjjZ1MRfyDT1ermVi4LvjjPgWDxLZnPpMaR6S1nzgQAAAIEAj0Vd6DCWslvlsZ8+N53HWsqPi3gnx35JoLPz9Z2epkKIKqmEHav+93G3hdfztVa4I4t3phoPniQchYryF5+RNg8hqxKzjNtrIqUYCeuf2NJrksNsH7OZygPHZpqt4kTuwAGZxjxEGfAI0y8DhkU2ntp2LnzRnWH106BQBCmcXwo= fauxhai.local",
-      "host_rsa_public": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtLCeqtqr/HbnORckw1ukdLhpfGoOPFi5/esKEokzTqq1gsgQ2V8emmyjfq1i6XXfRtSBxkdlHv/GWdP5wBTuE2G85MzBkVSQPvmwQN8lX/JMPEEtKXkeOo0o92/PiSmvY4eRsdF0mw40Uvg7jtE3f3fxj497kzh5fKtkrHnF4x9gXCbVdr3FqXJfggR5IJwAxToerbK7x/uRS+7YuZI9Pip3tt14nv9ezwXcuGb/tvjWOZINiFl8izVIFKi7sxfTX09p4NgamxRS7TD2Yd0jT8nEoF9UZTsgXcJ1kDSx7N7NxFfNfP6rCdOGRRz4gUhXtsUjG/XkxPeCwZ7A9VnOD fauxhai.local"
-    }
-  },
-  "languages": {
-    "ruby": {
-      "platform": "x86_64-linux-gnu",
-      "version": "2.4.4",
-      "release_date": "2018-03-28",
-      "target": "x86_64-pc-linux-gnu",
-      "target_cpu": "x86_64",
-      "target_vendor": "pc",
-      "target_os": "linux-gnu",
-      "host": "x86_64-pc-linux-gnu",
-      "host_cpu": "x86_64",
-      "host_os": "linux-gnu",
-      "host_vendor": "pc",
-      "bin_dir": "/usr/local/bin",
-      "ruby_bin": "/usr/local/bin/ruby",
-      "gems_dir": "/usr/local/gems",
-      "gem_bin": "/usr/local/bin/gem"
-    },
-    "powershell": null
-  },
-  "lsb": {
-    "id": "Ubuntu",
-    "release": "16.04",
-    "codename": "xenial",
-    "description": "Ubuntu 16.04.3 LTS"
-  },
-  "macaddress": "11:11:11:11:11:11",
-  "machinename": "Fauxhai",
-  "memory": {
-    "total": "1048576kB"
-  },
-  "network": {
-    "interfaces": {
-      "lo": {
-        "mtu": "65536",
-        "flags": [
-          "LOOPBACK",
-          "UP",
-          "LOWER_UP"
-        ],
-        "encapsulation": "Loopback",
-        "addresses": {
-          "127.0.0.1": {
-            "family": "inet",
-            "prefixlen": "8",
-            "netmask": "255.0.0.0",
-            "scope": "Node",
-            "ip_scope": "LOOPBACK"
-          },
-          "::1": {
-            "family": "inet6",
-            "prefixlen": "128",
-            "scope": "Node",
-            "tags": [
-
-            ],
-            "ip_scope": "LINK LOCAL LOOPBACK"
-          }
-        },
-        "state": "unknown"
-      },
-      "eth0": {
-        "type": "eth",
-        "number": "0",
-        "mtu": "1500",
-        "flags": [
-          "BROADCAST",
-          "MULTICAST",
-          "UP",
-          "LOWER_UP"
-        ],
-        "encapsulation": "Ethernet",
-        "addresses": {
-          "11:11:11:11:11:11": {
-            "family": "lladdr"
-          },
-          "10.0.0.2": {
-            "family": "inet",
-            "prefixlen": "24",
-            "netmask": "255.255.255.0",
-            "broadcast": "10.0.0.255",
-            "scope": "Global",
-            "ip_scope": "RFC1918 PRIVATE"
-          },
-          "fe80::11:1111:1111:1111": {
-            "family": "inet6",
-            "prefixlen": "64",
-            "scope": "Link",
-            "tags": [
-
-            ],
-            "ip_scope": "LINK LOCAL UNICAST"
-          }
-        },
-        "state": "up",
-        "arp": {
-          "10.0.0.1": "fe:ff:ff:ff:ff:ff"
-        },
-        "routes": [
-          {
-            "destination": "default",
-            "family": "inet",
-            "via": "10.0.0.1"
-          },
-          {
-            "destination": "10.0.0.0/24",
-            "family": "inet",
-            "scope": "link",
-            "proto": "kernel",
-            "src": "10.0.0.2"
-          },
-          {
-            "destination": "fe80::/64",
-            "family": "inet6",
-            "metric": "256",
-            "proto": "kernel"
-          }
-        ],
-        "ring_params": {
-        }
-      }
-    },
-    "default_interface": "eth0",
-    "default_gateway": "10.0.0.1"
-  },
-  "ohai_time": 1532332846.1504228,
-  "os": "linux",
-  "os_version": "4.4.0-130-generic",
-  "packages": {
-    "adduser": {
-      "version": "3.113+nmu3ubuntu4",
-      "arch": "all"
-    },
-    "apt": {
-      "version": "1.2.27",
-      "arch": "amd64"
-    },
-    "apt-utils": {
-      "version": "1.2.27",
-      "arch": "amd64"
-    },
-    "base-files": {
-      "version": "9.4ubuntu4.5",
-      "arch": "amd64"
-    },
-    "base-passwd": {
-      "version": "3.5.39",
-      "arch": "amd64"
-    },
-    "bash": {
-      "version": "4.3-14ubuntu1.2",
-      "arch": "amd64"
-    },
-    "bsdutils": {
-      "version": "1:2.27.1-6ubuntu3.3",
-      "arch": "amd64"
-    },
-    "ca-certificates": {
-      "version": "20170717~16.04.1",
-      "arch": "all"
-    },
-    "coreutils": {
-      "version": "8.25-2ubuntu3~16.04",
-      "arch": "amd64"
-    },
-    "cron": {
-      "version": "3.0pl1-128ubuntu2",
-      "arch": "amd64"
-    },
-    "curl": {
-      "version": "7.47.0-1ubuntu2.8",
-      "arch": "amd64"
-    },
-    "dash": {
-      "version": "0.5.8-2.1ubuntu2",
-      "arch": "amd64"
-    },
-    "dbus": {
-      "version": "1.10.6-1ubuntu3.3",
-      "arch": "amd64"
-    },
-    "debconf": {
-      "version": "1.5.58ubuntu1",
-      "arch": "all"
-    },
-    "debianutils": {
-      "version": "4.7",
-      "arch": "amd64"
-    },
-    "diffutils": {
-      "version": "1:3.3-3",
-      "arch": "amd64"
-    },
-    "dpkg": {
-      "version": "1.18.4ubuntu1.3",
-      "arch": "amd64"
-    },
-    "dpkg-dev": {
-      "version": "1.18.4ubuntu1.4",
-      "arch": "all"
-    },
-    "e2fslibs": {
-      "version": "1.42.13-1ubuntu1",
-      "arch": "amd64"
-    },
-    "e2fsprogs": {
-      "version": "1.42.13-1ubuntu1",
-      "arch": "amd64"
-    },
-    "fakeroot": {
-      "version": "1.20.2-1ubuntu1",
-      "arch": "amd64"
-    },
-    "findutils": {
-      "version": "4.6.0+git+20160126-2",
-      "arch": "amd64"
-    },
-    "gcc-5-base": {
-      "version": "5.4.0-6ubuntu1~16.04.10",
-      "arch": "amd64"
-    },
-    "gcc-6-base": {
-      "version": "6.0.1-0ubuntu1",
-      "arch": "amd64"
-    },
-    "gnupg": {
-      "version": "1.4.20-1ubuntu3.1",
-      "arch": "amd64"
-    },
-    "gpgv": {
-      "version": "1.4.20-1ubuntu3.1",
-      "arch": "amd64"
-    },
-    "grep": {
-      "version": "2.25-1~16.04.1",
-      "arch": "amd64"
-    },
-    "gzip": {
-      "version": "1.6-4ubuntu1",
-      "arch": "amd64"
-    },
-    "hostname": {
-      "version": "3.16ubuntu2",
-      "arch": "amd64"
-    },
-    "ifupdown": {
-      "version": "0.8.10ubuntu1.4",
-      "arch": "amd64"
-    },
-    "init": {
-      "version": "1.29ubuntu4",
-      "arch": "amd64"
-    },
-    "init-system-helpers": {
-      "version": "1.29ubuntu4",
-      "arch": "all"
-    },
-    "initscripts": {
-      "version": "2.88dsf-59.3ubuntu2",
-      "arch": "amd64"
-    },
-    "insserv": {
-      "version": "1.14.0-5ubuntu3",
-      "arch": "amd64"
-    },
-    "iproute2": {
-      "version": "4.3.0-1ubuntu3.16.04.3",
-      "arch": "amd64"
-    },
-    "isc-dhcp-client": {
-      "version": "4.3.3-5ubuntu12.10",
-      "arch": "amd64"
-    },
-    "javascript-common": {
-      "version": "11",
-      "arch": "all"
-    },
-    "krb5-locales": {
-      "version": "1.13.2+dfsg-5ubuntu2",
-      "arch": "all"
-    },
-    "libacl1": {
-      "version": "2.2.52-3",
-      "arch": "amd64"
-    },
-    "libapparmor1": {
-      "version": "2.10.95-0ubuntu2.7",
-      "arch": "amd64"
-    },
-    "libapt-inst2.0": {
-      "version": "1.2.27",
-      "arch": "amd64"
-    },
-    "libapt-pkg5.0": {
-      "version": "1.2.27",
-      "arch": "amd64"
-    },
-    "libasn1-8-heimdal": {
-      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
-      "arch": "amd64"
-    },
-    "libatm1": {
-      "version": "1:2.5.1-1.5",
-      "arch": "amd64"
-    },
-    "libattr1": {
-      "version": "1:2.4.47-2",
-      "arch": "amd64"
-    },
-    "libaudit-common": {
-      "version": "1:2.4.5-1ubuntu2",
-      "arch": "all"
-    },
-    "libaudit1": {
-      "version": "1:2.4.5-1ubuntu2",
-      "arch": "amd64"
-    },
-    "libblkid1": {
-      "version": "2.27.1-6ubuntu3.3",
-      "arch": "amd64"
-    },
-    "libbz2-1.0": {
-      "version": "1.0.6-8",
-      "arch": "amd64"
-    },
-    "libc-bin": {
-      "version": "2.23-0ubuntu9",
-      "arch": "amd64"
-    },
-    "libc6": {
-      "version": "2.23-0ubuntu10",
-      "arch": "amd64"
-    },
-    "libcap-ng0": {
-      "version": "0.7.7-1",
-      "arch": "amd64"
-    },
-    "libcap2": {
-      "version": "1:2.24-12",
-      "arch": "amd64"
-    },
-    "libcap2-bin": {
-      "version": "1:2.24-12",
-      "arch": "amd64"
-    },
-    "libcomerr2": {
-      "version": "1.42.13-1ubuntu1",
-      "arch": "amd64"
-    },
-    "libcryptsetup4": {
-      "version": "2:1.6.6-5ubuntu2.1",
-      "arch": "amd64"
-    },
-    "libcurl3-gnutls": {
-      "version": "7.47.0-1ubuntu2.8",
-      "arch": "amd64"
-    },
-    "libdb5.3": {
-      "version": "5.3.28-11ubuntu0.1",
-      "arch": "amd64"
-    },
-    "libdbus-1-3": {
-      "version": "1.10.6-1ubuntu3.3",
-      "arch": "amd64"
-    },
-    "libdebconfclient0": {
-      "version": "0.198ubuntu1",
-      "arch": "amd64"
-    },
-    "libdevmapper1.02.1": {
-      "version": "2:1.02.110-1ubuntu10",
-      "arch": "amd64"
-    },
-    "libexpat1": {
-      "version": "2.1.0-7ubuntu0.16.04.3",
-      "arch": "amd64"
-    },
-    "libfakeroot": {
-      "version": "1.20.2-1ubuntu1",
-      "arch": "amd64"
-    },
-    "libfdisk1": {
-      "version": "2.27.1-6ubuntu3.3",
-      "arch": "amd64"
-    },
-    "libffi6": {
-      "version": "3.2.1-4",
-      "arch": "amd64"
-    },
-    "libgcc1": {
-      "version": "1:6.0.1-0ubuntu1",
-      "arch": "amd64"
-    },
-    "libgcrypt20": {
-      "version": "1.6.5-2ubuntu0.3",
-      "arch": "amd64"
-    },
-    "libgdbm3": {
-      "version": "1.8.3-13.1",
-      "arch": "amd64"
-    },
-    "libglib2.0-0": {
-      "version": "2.48.2-0ubuntu1",
-      "arch": "amd64"
-    },
-    "libgmp-dev": {
-      "version": "2:6.1.0+dfsg-2",
-      "arch": "amd64"
-    },
-    "libgmp10": {
-      "version": "2:6.1.0+dfsg-2",
-      "arch": "amd64"
-    },
-    "libgmpxx4ldbl": {
-      "version": "2:6.1.0+dfsg-2",
-      "arch": "amd64"
-    },
-    "libgnutls30": {
-      "version": "3.4.10-4ubuntu1.4",
-      "arch": "amd64"
-    },
-    "libgpg-error0": {
-      "version": "1.21-2ubuntu1",
-      "arch": "amd64"
-    },
-    "libgssapi-krb5-2": {
-      "version": "1.13.2+dfsg-5ubuntu2",
-      "arch": "amd64"
-    },
-    "libgssapi3-heimdal": {
-      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
-      "arch": "amd64"
-    },
-    "libhcrypto4-heimdal": {
-      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
-      "arch": "amd64"
-    },
-    "libheimbase1-heimdal": {
-      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
-      "arch": "amd64"
-    },
-    "libheimntlm0-heimdal": {
-      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
-      "arch": "amd64"
-    },
-    "libhogweed4": {
-      "version": "3.2-1ubuntu0.16.04.1",
-      "arch": "amd64"
-    },
-    "libhx509-5-heimdal": {
-      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
-      "arch": "amd64"
-    },
-    "libidn11": {
-      "version": "1.32-3ubuntu1.2",
-      "arch": "amd64"
-    },
-    "libjs-jquery": {
-      "version": "1.11.3+dfsg-4",
-      "arch": "all"
-    },
-    "libk5crypto3": {
-      "version": "1.13.2+dfsg-5ubuntu2",
-      "arch": "amd64"
-    },
-    "libkeyutils1": {
-      "version": "1.5.9-8ubuntu1",
-      "arch": "amd64"
-    },
-    "libkmod2": {
-      "version": "22-1ubuntu5",
-      "arch": "amd64"
-    },
-    "libkrb5-26-heimdal": {
-      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
-      "arch": "amd64"
-    },
-    "libkrb5-3": {
-      "version": "1.13.2+dfsg-5ubuntu2",
-      "arch": "amd64"
-    },
-    "libkrb5support0": {
-      "version": "1.13.2+dfsg-5ubuntu2",
-      "arch": "amd64"
-    },
-    "libldap-2.4-2": {
-      "version": "2.4.42+dfsg-2ubuntu3.3",
-      "arch": "amd64"
-    },
-    "liblz4-1": {
-      "version": "0.0~r131-2ubuntu2",
-      "arch": "amd64"
-    },
-    "liblzma5": {
-      "version": "5.1.1alpha+20120614-2ubuntu2",
-      "arch": "amd64"
-    },
-    "libmagic1": {
-      "version": "1:5.25-2ubuntu1.1",
-      "arch": "amd64"
-    },
-    "libmnl0": {
-      "version": "1.0.3-5",
-      "arch": "amd64"
-    },
-    "libmount1": {
-      "version": "2.27.1-6ubuntu3.3",
-      "arch": "amd64"
-    },
-    "libmpc3": {
-      "version": "1.0.3-1",
-      "arch": "amd64"
-    },
-    "libncurses5": {
-      "version": "6.0+20160213-1ubuntu1",
-      "arch": "amd64"
-    },
-    "libncursesw5": {
-      "version": "6.0+20160213-1ubuntu1",
-      "arch": "amd64"
-    },
-    "libnettle6": {
-      "version": "3.2-1ubuntu0.16.04.1",
-      "arch": "amd64"
-    },
-    "libp11-kit0": {
-      "version": "0.23.2-5~ubuntu16.04.1",
-      "arch": "amd64"
-    },
-    "libpam-modules": {
-      "version": "1.1.8-3.2ubuntu2",
-      "arch": "amd64"
-    },
-    "libpam-modules-bin": {
-      "version": "1.1.8-3.2ubuntu2",
-      "arch": "amd64"
-    },
-    "libpam-runtime": {
-      "version": "1.1.8-3.2ubuntu2",
-      "arch": "all"
-    },
-    "libpam0g": {
-      "version": "1.1.8-3.2ubuntu2",
-      "arch": "amd64"
-    },
-    "libpcre3": {
-      "version": "2:8.38-3.1",
-      "arch": "amd64"
-    },
-    "libperl5.22": {
-      "version": "5.22.1-9ubuntu0.5",
-      "arch": "amd64"
-    },
-    "libprocps4": {
-      "version": "2:3.3.10-4ubuntu2.3",
-      "arch": "amd64"
-    },
-    "libpython3.5-minimal": {
-      "version": "3.5.2-2ubuntu0~16.04.4",
-      "arch": "amd64"
-    },
-    "libreadline6": {
-      "version": "6.3-8ubuntu2",
-      "arch": "amd64"
-    },
-    "libroken18-heimdal": {
-      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
-      "arch": "amd64"
-    },
-    "librtmp1": {
-      "version": "2.4+20151223.gitfa8646d-1ubuntu0.1",
-      "arch": "amd64"
-    },
-    "libruby2.4": {
-      "version": "2.4.4-1bbox1~xenial1",
-      "arch": "amd64"
-    },
-    "libsasl2-2": {
-      "version": "2.1.26.dfsg1-14build1",
-      "arch": "amd64"
-    },
-    "libsasl2-modules": {
-      "version": "2.1.26.dfsg1-14build1",
-      "arch": "amd64"
-    },
-    "libsasl2-modules-db": {
-      "version": "2.1.26.dfsg1-14build1",
-      "arch": "amd64"
-    },
-    "libseccomp2": {
-      "version": "2.2.3-3ubuntu3",
-      "arch": "amd64"
-    },
-    "libselinux1": {
-      "version": "2.4-3build2",
-      "arch": "amd64"
-    },
-    "libsemanage-common": {
-      "version": "2.3-1build3",
-      "arch": "all"
-    },
-    "libsemanage1": {
-      "version": "2.3-1build3",
-      "arch": "amd64"
-    },
-    "libsepol1": {
-      "version": "2.4-2",
-      "arch": "amd64"
-    },
-    "libsmartcols1": {
-      "version": "2.27.1-6ubuntu3.3",
-      "arch": "amd64"
-    },
-    "libsqlite3-0": {
-      "version": "3.11.0-1ubuntu1",
-      "arch": "amd64"
-    },
-    "libss2": {
-      "version": "1.42.13-1ubuntu1",
-      "arch": "amd64"
-    },
-    "libssl1.0.0": {
-      "version": "1.0.2g-1ubuntu4.13",
-      "arch": "amd64"
-    },
-    "libstdc++6": {
-      "version": "5.4.0-6ubuntu1~16.04.10",
-      "arch": "amd64"
-    },
-    "libsystemd0": {
-      "version": "229-4ubuntu21",
-      "arch": "amd64"
-    },
-    "libtasn1-6": {
-      "version": "4.7-3ubuntu0.16.04.3",
-      "arch": "amd64"
-    },
-    "libtinfo5": {
-      "version": "6.0+20160213-1ubuntu1",
-      "arch": "amd64"
-    },
-    "libudev1": {
-      "version": "229-4ubuntu21",
-      "arch": "amd64"
-    },
-    "libusb-0.1-4": {
-      "version": "2:0.1.12-28",
-      "arch": "amd64"
-    },
-    "libustr-1.0-1": {
-      "version": "1.0.4-5",
-      "arch": "amd64"
-    },
-    "libuuid1": {
-      "version": "2.27.1-6ubuntu3.3",
-      "arch": "amd64"
-    },
-    "libwind0-heimdal": {
-      "version": "1.7~git20150920+dfsg-4ubuntu1.16.04.1",
-      "arch": "amd64"
-    },
-    "libyaml-0-2": {
-      "version": "0.1.6-3",
-      "arch": "amd64"
-    },
-    "login": {
-      "version": "1:4.2-3.1ubuntu5.3",
-      "arch": "amd64"
-    },
-    "lsb-base": {
-      "version": "9.20160110ubuntu0.2",
-      "arch": "all"
-    },
-    "lsb-release": {
-      "version": "9.20160110ubuntu0.2",
-      "arch": "all"
-    },
-    "makedev": {
-      "version": "2.3.1-93ubuntu2~ubuntu16.04.1",
-      "arch": "all"
-    },
-    "mawk": {
-      "version": "1.3.3-17ubuntu2",
-      "arch": "amd64"
-    },
-    "mime-support": {
-      "version": "3.59ubuntu1",
-      "arch": "all"
-    },
-    "mount": {
-      "version": "2.27.1-6ubuntu3.3",
-      "arch": "amd64"
-    },
-    "multiarch-support": {
-      "version": "2.23-0ubuntu9",
-      "arch": "amd64"
-    },
-    "ncurses-base": {
-      "version": "6.0+20160213-1ubuntu1",
-      "arch": "all"
-    },
-    "ncurses-bin": {
-      "version": "6.0+20160213-1ubuntu1",
-      "arch": "amd64"
-    },
-    "netbase": {
-      "version": "5.3",
-      "arch": "all"
-    },
-    "openssl": {
-      "version": "1.0.2g-1ubuntu4.13",
-      "arch": "amd64"
-    },
-    "passwd": {
-      "version": "1:4.2-3.1ubuntu5.3",
-      "arch": "amd64"
-    },
-    "perl": {
-      "version": "5.22.1-9ubuntu0.5",
-      "arch": "amd64"
-    },
-    "perl-base": {
-      "version": "5.22.1-9ubuntu0.5",
-      "arch": "amd64"
-    },
-    "procps": {
-      "version": "2:3.3.10-4ubuntu2.3",
-      "arch": "amd64"
-    },
-    "python3": {
-      "version": "3.5.1-3",
-      "arch": "amd64"
-    },
-    "python3.5-minimal": {
-      "version": "3.5.2-2ubuntu0~16.04.4",
-      "arch": "amd64"
-    },
-    "readline-common": {
-      "version": "6.3-8ubuntu2",
-      "arch": "all"
-    },
-    "ruby2.4": {
-      "version": "2.4.4-1bbox1~xenial1",
-      "arch": "amd64"
-    },
-    "ruby2.4-dev": {
-      "version": "2.4.4-1bbox1~xenial1",
-      "arch": "amd64"
-    },
-    "rubygems-integration": {
-      "version": "1.10",
-      "arch": "all"
-    },
-    "sed": {
-      "version": "4.2.2-7",
-      "arch": "amd64"
-    },
-    "sensible-utils": {
-      "version": "0.0.9",
-      "arch": "all"
-    },
-    "sgml-base": {
-      "version": "1.26+nmu4ubuntu1",
-      "arch": "all"
-    },
-    "shared-mime-info": {
-      "version": "1.5-2ubuntu0.1",
-      "arch": "amd64"
-    },
-    "systemd": {
-      "version": "229-4ubuntu21",
-      "arch": "amd64"
-    },
-    "systemd-sysv": {
-      "version": "229-4ubuntu21",
-      "arch": "amd64"
-    },
-    "sysv-rc": {
-      "version": "2.88dsf-59.3ubuntu2",
-      "arch": "all"
-    },
-    "sysvinit-utils": {
-      "version": "2.88dsf-59.3ubuntu2",
-      "arch": "amd64"
-    },
-    "tar": {
-      "version": "1.28-2.1ubuntu0.1",
-      "arch": "amd64"
-    },
-    "ubuntu-keyring": {
-      "version": "2012.05.19",
-      "arch": "all"
-    },
-    "ucf": {
-      "version": "3.0036",
-      "arch": "all"
-    },
-    "unattended-upgrades": {
-      "version": "0.90ubuntu0.9",
-      "arch": "all"
-    },
-    "unzip": {
-      "version": "6.0-20ubuntu1",
-      "arch": "amd64"
-    },
-    "util-linux": {
-      "version": "2.27.1-6ubuntu3.3",
-      "arch": "amd64"
-    },
-    "xdg-user-dirs": {
-      "version": "0.15-2ubuntu6.16.04.1",
-      "arch": "amd64"
-    },
-    "xml-core": {
-      "version": "0.13+nmu2",
-      "arch": "all"
-    },
-    "zlib1g": {
-      "version": "1:1.2.8.dfsg-2ubuntu4.1",
-      "arch": "amd64"
-    }
-  },
-  "platform": "ubuntu",
-  "platform_family": "debian",
-  "platform_version": "16.04",
-  "root_group": "root",
-  "shard_seed": 161653957,
-  "shells": [
-    "/bin/sh",
-    "/bin/dash",
-    "/bin/bash",
-    "/bin/rbash"
-  ],
-  "time": {
-    "timezone": "GMT"
-  },
-  "uptime": "30 days 15 hours 07 minutes 30 seconds",
-  "uptime_seconds": 2646450,
-  "virtualization": {
-    "systems": {
-    }
-  }
-}
diff --git a/spec/fixtures/fauxhai/ubuntu/16.04-more-cpus.json b/spec/fixtures/fauxhai/ubuntu/16.04-more-cpus.json
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9mYXV4aGFpL3VidW50dS8xNi4wNC1tb3JlLWNwdXMuanNvbg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/fauxhai/ubuntu/16.04-more-cpus.json
+++ /dev/null
@@ -1,1519 +0,0 @@
-{
-  "kernel": {
-    "name": "Linux",
-    "release": "4.8.0-52-generic",
-    "version": "#55~16.04.1-Ubuntu SMP Fri Apr 28 14:36:29 UTC 2017",
-    "machine": "x86_64",
-    "processor": "x86_64",
-    "os": "GNU/Linux",
-    "modules": {
-      "ip6table_filter": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "ip6_tables": {
-        "size": "28672",
-        "refcount": "1"
-      },
-      "iptable_filter": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "ip_tables": {
-        "size": "24576",
-        "refcount": "1"
-      },
-      "x_tables": {
-        "size": "36864",
-        "refcount": "4"
-      },
-      "ppdev": {
-        "size": "20480",
-        "refcount": "0"
-      },
-      "input_leds": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "serio_raw": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "parport_pc": {
-        "size": "32768",
-        "refcount": "0"
-      },
-      "pvpanic": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "parport": {
-        "size": "49152",
-        "refcount": "2"
-      },
-      "ib_iser": {
-        "size": "49152",
-        "refcount": "0",
-        "version": "1.6"
-      },
-      "rdma_cm": {
-        "size": "57344",
-        "refcount": "1"
-      },
-      "iw_cm": {
-        "size": "49152",
-        "refcount": "1"
-      },
-      "ib_cm": {
-        "size": "45056",
-        "refcount": "1"
-      },
-      "ib_core": {
-        "size": "212992",
-        "refcount": "4"
-      },
-      "configfs": {
-        "size": "40960",
-        "refcount": "2",
-        "version": "0.0.2"
-      },
-      "iscsi_tcp": {
-        "size": "20480",
-        "refcount": "0"
-      },
-      "libiscsi_tcp": {
-        "size": "24576",
-        "refcount": "1"
-      },
-      "libiscsi": {
-        "size": "53248",
-        "refcount": "3"
-      },
-      "scsi_transport_iscsi": {
-        "size": "98304",
-        "refcount": "4",
-        "version": "2.0-870"
-      },
-      "autofs4": {
-        "size": "40960",
-        "refcount": "2"
-      },
-      "btrfs": {
-        "size": "1060864",
-        "refcount": "0"
-      },
-      "raid10": {
-        "size": "49152",
-        "refcount": "0"
-      },
-      "raid456": {
-        "size": "110592",
-        "refcount": "0"
-      },
-      "async_raid6_recov": {
-        "size": "20480",
-        "refcount": "1"
-      },
-      "async_memcpy": {
-        "size": "16384",
-        "refcount": "2"
-      },
-      "async_pq": {
-        "size": "16384",
-        "refcount": "2"
-      },
-      "async_xor": {
-        "size": "16384",
-        "refcount": "3"
-      },
-      "async_tx": {
-        "size": "16384",
-        "refcount": "5"
-      },
-      "xor": {
-        "size": "24576",
-        "refcount": "2"
-      },
-      "raid6_pq": {
-        "size": "102400",
-        "refcount": "4"
-      },
-      "libcrc32c": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "raid1": {
-        "size": "40960",
-        "refcount": "0"
-      },
-      "raid0": {
-        "size": "20480",
-        "refcount": "0"
-      },
-      "multipath": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "linear": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "crct10dif_pclmul": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "crc32_pclmul": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "ghash_clmulni_intel": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "aesni_intel": {
-        "size": "167936",
-        "refcount": "0"
-      },
-      "aes_x86_64": {
-        "size": "20480",
-        "refcount": "1"
-      },
-      "lrw": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "glue_helper": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "ablk_helper": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "cryptd": {
-        "size": "24576",
-        "refcount": "3"
-      },
-      "psmouse": {
-        "size": "139264",
-        "refcount": "0"
-      },
-      "virtio_net": {
-        "size": "32768",
-        "refcount": "0"
-      },
-      "virtio_scsi": {
-        "size": "20480",
-        "refcount": "1"
-      }
-    }
-  },
-  "os": "linux",
-  "os_version": "4.8.0-52-generic",
-  "lsb": {
-    "id": "Ubuntu",
-    "description": "Ubuntu 16.04.2 LTS",
-    "release": "16.04",
-    "codename": "xenial"
-  },
-  "platform": "ubuntu",
-  "platform_version": "16.04",
-  "platform_family": "debian",
-  "filesystem": {
-    "by_device": {
-      "udev": {
-        "kb_size": "1886700",
-        "kb_used": "0",
-        "kb_available": "1886700",
-        "percent_used": "0%",
-        "total_inodes": "471675",
-        "inodes_used": "371",
-        "inodes_available": "471304",
-        "inodes_percent_used": "1%",
-        "fs_type": "devtmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "relatime",
-          "size=1886700k",
-          "nr_inodes=471675",
-          "mode=755"
-        ],
-        "mounts": [
-          "/dev"
-        ]
-      },
-      "tmpfs": {
-        "kb_size": "378828",
-        "kb_used": "0",
-        "kb_available": "378828",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "4",
-        "inodes_available": "473527",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "size=378828k",
-          "mode=700",
-          "uid=1001",
-          "gid=1002"
-        ],
-        "mounts": [
-          "/run",
-          "/dev/shm",
-          "/run/lock",
-          "/sys/fs/cgroup",
-          "/run/user/1001"
-        ]
-      },
-      "/dev/sda1": {
-        "kb_size": "10098468",
-        "kb_used": "1401956",
-        "kb_available": "8680128",
-        "percent_used": "14%",
-        "total_inodes": "1280000",
-        "inodes_used": "90038",
-        "inodes_available": "1189962",
-        "inodes_percent_used": "8%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
-        "label": "cloudimg-rootfs",
-        "mounts": [
-          "/"
-        ]
-      },
-      "sysfs": {
-        "fs_type": "sysfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys"
-        ]
-      },
-      "proc": {
-        "fs_type": "proc",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/proc"
-        ]
-      },
-      "devpts": {
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=000"
-        ],
-        "mounts": [
-          "/dev/pts"
-        ]
-      },
-      "securityfs": {
-        "fs_type": "securityfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/kernel/security"
-        ]
-      },
-      "cgroup": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "hugetlb"
-        ],
-        "mounts": [
-          "/sys/fs/cgroup/systemd",
-          "/sys/fs/cgroup/perf_event",
-          "/sys/fs/cgroup/blkio",
-          "/sys/fs/cgroup/devices",
-          "/sys/fs/cgroup/net_cls,net_prio",
-          "/sys/fs/cgroup/cpuset",
-          "/sys/fs/cgroup/cpu,cpuacct",
-          "/sys/fs/cgroup/memory",
-          "/sys/fs/cgroup/freezer",
-          "/sys/fs/cgroup/pids",
-          "/sys/fs/cgroup/hugetlb"
-        ]
-      },
-      "pstore": {
-        "fs_type": "pstore",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/fs/pstore"
-        ]
-      },
-      "hugetlbfs": {
-        "fs_type": "hugetlbfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/dev/hugepages"
-        ]
-      },
-      "mqueue": {
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/dev/mqueue"
-        ]
-      },
-      "systemd-1": {
-        "fs_type": "autofs",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "fd=33",
-          "pgrp=1",
-          "timeout=0",
-          "minproto=5",
-          "maxproto=5",
-          "direct",
-          "pipe_ino=10449"
-        ],
-        "mounts": [
-          "/proc/sys/fs/binfmt_misc"
-        ]
-      },
-      "debugfs": {
-        "fs_type": "debugfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/kernel/debug"
-        ]
-      },
-      "fusectl": {
-        "fs_type": "fusectl",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/fs/fuse/connections"
-        ]
-      },
-      "configfs": {
-        "fs_type": "configfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/kernel/config"
-        ]
-      },
-      "lxcfs": {
-        "fs_type": "fuse.lxcfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "user_id=0",
-          "group_id=0",
-          "allow_other"
-        ],
-        "mounts": [
-          "/var/lib/lxcfs"
-        ]
-      },
-      "/dev/sda": {
-      }
-    },
-    "by_mountpoint": {
-      "/dev": {
-        "kb_size": "1886700",
-        "kb_used": "0",
-        "kb_available": "1886700",
-        "percent_used": "0%",
-        "total_inodes": "471675",
-        "inodes_used": "371",
-        "inodes_available": "471304",
-        "inodes_percent_used": "1%",
-        "fs_type": "devtmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "relatime",
-          "size=1886700k",
-          "nr_inodes=471675",
-          "mode=755"
-        ],
-        "devices": [
-          "udev"
-        ]
-      },
-      "/run": {
-        "kb_size": "378828",
-        "kb_used": "5292",
-        "kb_available": "373536",
-        "percent_used": "2%",
-        "total_inodes": "473531",
-        "inodes_used": "460",
-        "inodes_available": "473071",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "size=378828k",
-          "mode=755"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/": {
-        "kb_size": "10098468",
-        "kb_used": "1401956",
-        "kb_available": "8680128",
-        "percent_used": "14%",
-        "total_inodes": "1280000",
-        "inodes_used": "90038",
-        "inodes_available": "1189962",
-        "inodes_percent_used": "8%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
-        "label": "cloudimg-rootfs",
-        "devices": [
-          "/dev/sda1"
-        ]
-      },
-      "/dev/shm": {
-        "kb_size": "1894124",
-        "kb_used": "0",
-        "kb_available": "1894124",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "1",
-        "inodes_available": "473530",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/run/lock": {
-        "kb_size": "5120",
-        "kb_used": "0",
-        "kb_available": "5120",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "8",
-        "inodes_available": "473523",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "size=5120k"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/sys/fs/cgroup": {
-        "kb_size": "1894124",
-        "kb_used": "0",
-        "kb_available": "1894124",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "16",
-        "inodes_available": "473515",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "mode=755"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/run/user/1001": {
-        "kb_size": "378828",
-        "kb_used": "0",
-        "kb_available": "378828",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "4",
-        "inodes_available": "473527",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "size=378828k",
-          "mode=700",
-          "uid=1001",
-          "gid=1002"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/sys": {
-        "fs_type": "sysfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "sysfs"
-        ]
-      },
-      "/proc": {
-        "fs_type": "proc",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "proc"
-        ]
-      },
-      "/dev/pts": {
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=000"
-        ],
-        "devices": [
-          "devpts"
-        ]
-      },
-      "/sys/kernel/security": {
-        "fs_type": "securityfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "securityfs"
-        ]
-      },
-      "/sys/fs/cgroup/systemd": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "xattr",
-          "release_agent=/lib/systemd/systemd-cgroups-agent",
-          "name=systemd"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/pstore": {
-        "fs_type": "pstore",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "pstore"
-        ]
-      },
-      "/sys/fs/cgroup/perf_event": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "perf_event"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/blkio": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "blkio"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/devices": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "devices"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/net_cls,net_prio": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "net_cls",
-          "net_prio"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/cpuset": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpuset"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/cpu,cpuacct": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpu",
-          "cpuacct"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/memory": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "memory"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/freezer": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "freezer"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/pids": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "pids"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/hugetlb": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "hugetlb"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/dev/hugepages": {
-        "fs_type": "hugetlbfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "hugetlbfs"
-        ]
-      },
-      "/dev/mqueue": {
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "mqueue"
-        ]
-      },
-      "/proc/sys/fs/binfmt_misc": {
-        "fs_type": "autofs",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "fd=33",
-          "pgrp=1",
-          "timeout=0",
-          "minproto=5",
-          "maxproto=5",
-          "direct",
-          "pipe_ino=10449"
-        ],
-        "devices": [
-          "systemd-1"
-        ]
-      },
-      "/sys/kernel/debug": {
-        "fs_type": "debugfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "debugfs"
-        ]
-      },
-      "/sys/fs/fuse/connections": {
-        "fs_type": "fusectl",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "fusectl"
-        ]
-      },
-      "/sys/kernel/config": {
-        "fs_type": "configfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "configfs"
-        ]
-      },
-      "/var/lib/lxcfs": {
-        "fs_type": "fuse.lxcfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "user_id=0",
-          "group_id=0",
-          "allow_other"
-        ],
-        "devices": [
-          "lxcfs"
-        ]
-      }
-    },
-    "by_pair": {
-      "udev,/dev": {
-        "device": "udev",
-        "kb_size": "1886700",
-        "kb_used": "0",
-        "kb_available": "1886700",
-        "percent_used": "0%",
-        "mount": "/dev",
-        "total_inodes": "471675",
-        "inodes_used": "371",
-        "inodes_available": "471304",
-        "inodes_percent_used": "1%",
-        "fs_type": "devtmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "relatime",
-          "size=1886700k",
-          "nr_inodes=471675",
-          "mode=755"
-        ]
-      },
-      "tmpfs,/run": {
-        "device": "tmpfs",
-        "kb_size": "378828",
-        "kb_used": "5292",
-        "kb_available": "373536",
-        "percent_used": "2%",
-        "mount": "/run",
-        "total_inodes": "473531",
-        "inodes_used": "460",
-        "inodes_available": "473071",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "size=378828k",
-          "mode=755"
-        ]
-      },
-      "/dev/sda1,/": {
-        "device": "/dev/sda1",
-        "kb_size": "10098468",
-        "kb_used": "1401956",
-        "kb_available": "8680128",
-        "percent_used": "14%",
-        "mount": "/",
-        "total_inodes": "1280000",
-        "inodes_used": "90038",
-        "inodes_available": "1189962",
-        "inodes_percent_used": "8%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
-        "label": "cloudimg-rootfs"
-      },
-      "tmpfs,/dev/shm": {
-        "device": "tmpfs",
-        "kb_size": "1894124",
-        "kb_used": "0",
-        "kb_available": "1894124",
-        "percent_used": "0%",
-        "mount": "/dev/shm",
-        "total_inodes": "473531",
-        "inodes_used": "1",
-        "inodes_available": "473530",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev"
-        ]
-      },
-      "tmpfs,/run/lock": {
-        "device": "tmpfs",
-        "kb_size": "5120",
-        "kb_used": "0",
-        "kb_available": "5120",
-        "percent_used": "0%",
-        "mount": "/run/lock",
-        "total_inodes": "473531",
-        "inodes_used": "8",
-        "inodes_available": "473523",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "size=5120k"
-        ]
-      },
-      "tmpfs,/sys/fs/cgroup": {
-        "device": "tmpfs",
-        "kb_size": "1894124",
-        "kb_used": "0",
-        "kb_available": "1894124",
-        "percent_used": "0%",
-        "mount": "/sys/fs/cgroup",
-        "total_inodes": "473531",
-        "inodes_used": "16",
-        "inodes_available": "473515",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "mode=755"
-        ]
-      },
-      "tmpfs,/run/user/1001": {
-        "device": "tmpfs",
-        "kb_size": "378828",
-        "kb_used": "0",
-        "kb_available": "378828",
-        "percent_used": "0%",
-        "mount": "/run/user/1001",
-        "total_inodes": "473531",
-        "inodes_used": "4",
-        "inodes_available": "473527",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "size=378828k",
-          "mode=700",
-          "uid=1001",
-          "gid=1002"
-        ]
-      },
-      "sysfs,/sys": {
-        "device": "sysfs",
-        "mount": "/sys",
-        "fs_type": "sysfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "proc,/proc": {
-        "device": "proc",
-        "mount": "/proc",
-        "fs_type": "proc",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "devpts,/dev/pts": {
-        "device": "devpts",
-        "mount": "/dev/pts",
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=000"
-        ]
-      },
-      "securityfs,/sys/kernel/security": {
-        "device": "securityfs",
-        "mount": "/sys/kernel/security",
-        "fs_type": "securityfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/systemd": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/systemd",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "xattr",
-          "release_agent=/lib/systemd/systemd-cgroups-agent",
-          "name=systemd"
-        ]
-      },
-      "pstore,/sys/fs/pstore": {
-        "device": "pstore",
-        "mount": "/sys/fs/pstore",
-        "fs_type": "pstore",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/perf_event": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/perf_event",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "perf_event"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/blkio": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/blkio",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "blkio"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/devices": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/devices",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "devices"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/net_cls,net_prio": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/net_cls,net_prio",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "net_cls",
-          "net_prio"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/cpuset": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/cpuset",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpuset"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/cpu,cpuacct": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/cpu,cpuacct",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpu",
-          "cpuacct"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/memory": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/memory",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "memory"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/freezer": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/freezer",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "freezer"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/pids": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/pids",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "pids"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/hugetlb": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/hugetlb",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "hugetlb"
-        ]
-      },
-      "hugetlbfs,/dev/hugepages": {
-        "device": "hugetlbfs",
-        "mount": "/dev/hugepages",
-        "fs_type": "hugetlbfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "mqueue,/dev/mqueue": {
-        "device": "mqueue",
-        "mount": "/dev/mqueue",
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "systemd-1,/proc/sys/fs/binfmt_misc": {
-        "device": "systemd-1",
-        "mount": "/proc/sys/fs/binfmt_misc",
-        "fs_type": "autofs",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "fd=33",
-          "pgrp=1",
-          "timeout=0",
-          "minproto=5",
-          "maxproto=5",
-          "direct",
-          "pipe_ino=10449"
-        ]
-      },
-      "debugfs,/sys/kernel/debug": {
-        "device": "debugfs",
-        "mount": "/sys/kernel/debug",
-        "fs_type": "debugfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "fusectl,/sys/fs/fuse/connections": {
-        "device": "fusectl",
-        "mount": "/sys/fs/fuse/connections",
-        "fs_type": "fusectl",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "configfs,/sys/kernel/config": {
-        "device": "configfs",
-        "mount": "/sys/kernel/config",
-        "fs_type": "configfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "lxcfs,/var/lib/lxcfs": {
-        "device": "lxcfs",
-        "mount": "/var/lib/lxcfs",
-        "fs_type": "fuse.lxcfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "user_id=0",
-          "group_id=0",
-          "allow_other"
-        ]
-      },
-      "/dev/sda,": {
-        "device": "/dev/sda"
-      }
-    }
-  },
-  "dmi": {
-    "dmidecode_version": "3.0"
-  },
-  "init_package": "systemd",
-  "ohai_time": 1496219099.5081818,
-  "command": {
-    "ps": "ps -ef"
-  },
-  "root_group": "root",
-  "languages": {
-    "ruby": {
-      "platform": "x86_64-linux-gnu",
-      "version": "2.3.1",
-      "release_date": "2016-04-26",
-      "target": "x86_64-pc-linux-gnu",
-      "target_cpu": "x86_64",
-      "target_vendor": "pc",
-      "target_os": "linux-gnu",
-      "host": "x86_64-pc-linux-gnu",
-      "host_cpu": "x86_64",
-      "host_os": "linux-gnu",
-      "host_vendor": "pc",
-      "bin_dir": "/usr/local/bin",
-      "ruby_bin": "/usr/local/bin/ruby",
-      "gems_dir": "/usr/local/gems",
-      "gem_bin": "/usr/local/bin/gem"
-    },
-    "powershell": null
-  },
-  "chef_packages": {
-    "chef": {
-      "version": "13.1.31",
-      "chef_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-13.1.31/lib"
-    },
-    "ohai": {
-      "version": "13.1.0",
-      "ohai_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/ohai-13.1.0/lib/ohai"
-    }
-  },
-  "counters": {
-    "network": {
-      "interfaces": {
-        "eth0": {
-          "rx": {
-            "bytes": "0",
-            "packets": "0",
-            "errors": "0",
-            "drop": 0,
-            "overrun": 0,
-            "frame": 0,
-            "compressed": 0,
-            "multicast": 0
-          },
-          "tx": {
-            "bytes": "342",
-            "packets": "0",
-            "errors": "0",
-            "drop": 0,
-            "overrun": 0,
-            "collisions": "0",
-            "carrier": 0,
-            "compressed": 0
-          }
-        }
-      }
-    }
-  },
-  "current_user": "fauxhai",
-  "domain": "local",
-  "etc": {
-    "passwd": {
-      "fauxhai": {
-        "dir": "/home/fauxhai",
-        "gid": 0,
-        "uid": 0,
-        "shell": "/bin/bash",
-        "gecos": "Fauxhai"
-      }
-    },
-    "group": {
-      "fauxhai": {
-        "gid": 0,
-        "members": [
-          "fauxhai"
-        ]
-      }
-    }
-  },
-  "hostname": "Fauxhai",
-  "fqdn": "fauxhai.local",
-  "ipaddress": "10.0.0.2",
-  "keys": {
-    "ssh": {
-      "host_dsa_public": "ssh-dss AAAAB3NzaC1kc3MAAACBAJFo9BLAw4WKEs5hgipk5m423FzBsDXCZSMcC9ca/om/1VYzMqImixGe3uICDzNFUWxFoLJTQAOccyzo6MXZiQqwWJDLFi5qOSr6w2XcMyE+zd4wOyMoDiVM5fizmG8K3FzrqvGjwBcHcBdOQnavSijoj38DN25J9zhrid5BY4WlAAAAFQDxXrCyG52XCzn3FV4ej38wJBkomQAAAIBovGPJ4mP2P6BK8lHl0PPbktwQbWlpJ13oz6REJFDVcUi7vV26bX/BjQX+ohzZQzljdz1SpUbPc/8nuA4darYkVh91eBi307EN8IdxRHj2eBgp/ZG4yshIebG3WHrwJD/xUjjZ1MRfyDT1ermVi4LvjjPgWDxLZnPpMaR6S1nzgQAAAIEAj0Vd6DCWslvlsZ8+N53HWsqPi3gnx35JoLPz9Z2epkKIKqmEHav+93G3hdfztVa4I4t3phoPniQchYryF5+RNg8hqxKzjNtrIqUYCeuf2NJrksNsH7OZygPHZpqt4kTuwAGZxjxEGfAI0y8DhkU2ntp2LnzRnWH106BQBCmcXwo= fauxhai.local",
-      "host_rsa_public": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtLCeqtqr/HbnORckw1ukdLhpfGoOPFi5/esKEokzTqq1gsgQ2V8emmyjfq1i6XXfRtSBxkdlHv/GWdP5wBTuE2G85MzBkVSQPvmwQN8lX/JMPEEtKXkeOo0o92/PiSmvY4eRsdF0mw40Uvg7jtE3f3fxj497kzh5fKtkrHnF4x9gXCbVdr3FqXJfggR5IJwAxToerbK7x/uRS+7YuZI9Pip3tt14nv9ezwXcuGb/tvjWOZINiFl8izVIFKi7sxfTX09p4NgamxRS7TD2Yd0jT8nEoF9UZTsgXcJ1kDSx7N7NxFfNfP6rCdOGRRz4gUhXtsUjG/XkxPeCwZ7A9VnOD fauxhai.local"
-    }
-  },
-  "macaddress": "11:11:11:11:11:11",
-  "network": {
-    "default_gateway": "10.0.0.1",
-    "default_interface": "eth0",
-    "settings": {
-    },
-    "interfaces": {
-      "eth0": {
-        "addresses": {
-          "10.0.0.2": {
-            "broadcast": "10.0.0.255",
-            "family": "inet",
-            "netmask": "255.255.255.0",
-            "prefixlen": "24",
-            "scope": "Global"
-          }
-        },
-        "arp": {
-          "10.0.0.1": "fe:ff:ff:ff:ff:ff"
-        },
-        "encapsulation": "Ethernet",
-        "flags": [
-          "BROADCAST",
-          "MULTICAST",
-          "UP",
-          "LOWER_UP"
-        ],
-        "mtu": "1500",
-        "number": "0",
-        "routes": [
-          {
-            "destination": "10.0.0.0/24",
-            "family": "inet",
-            "scope": "link",
-            "proto": "kernel",
-            "src": "10.0.0.2"
-          }
-        ],
-        "state": "up",
-        "type": "eth"
-      }
-    }
-  },
-  "uptime": "30 days 15 hours 07 minutes 30 seconds",
-  "uptime_seconds": 2646450,
-  "cpu": {
-    "real": 16,
-    "total": 16,
-    "cores": 16
-  },
-  "memory": {
-    "total": "33554432kB"
-  },
-  "virtualization": {
-    "systems": {
-    }
-  }
-}
diff --git a/spec/fixtures/fauxhai/ubuntu/16.04-no-run-tmpfs.json b/spec/fixtures/fauxhai/ubuntu/16.04-no-run-tmpfs.json
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9mYXV4aGFpL3VidW50dS8xNi4wNC1uby1ydW4tdG1wZnMuanNvbg==..0000000000000000000000000000000000000000
--- a/spec/fixtures/fauxhai/ubuntu/16.04-no-run-tmpfs.json
+++ /dev/null
@@ -1,1438 +0,0 @@
-{
-  "kernel": {
-    "name": "Linux",
-    "release": "4.8.0-52-generic",
-    "version": "#55~16.04.1-Ubuntu SMP Fri Apr 28 14:36:29 UTC 2017",
-    "machine": "x86_64",
-    "processor": "x86_64",
-    "os": "GNU/Linux",
-    "modules": {
-      "ip6table_filter": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "ip6_tables": {
-        "size": "28672",
-        "refcount": "1"
-      },
-      "iptable_filter": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "ip_tables": {
-        "size": "24576",
-        "refcount": "1"
-      },
-      "x_tables": {
-        "size": "36864",
-        "refcount": "4"
-      },
-      "ppdev": {
-        "size": "20480",
-        "refcount": "0"
-      },
-      "input_leds": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "serio_raw": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "parport_pc": {
-        "size": "32768",
-        "refcount": "0"
-      },
-      "pvpanic": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "parport": {
-        "size": "49152",
-        "refcount": "2"
-      },
-      "ib_iser": {
-        "size": "49152",
-        "refcount": "0",
-        "version": "1.6"
-      },
-      "rdma_cm": {
-        "size": "57344",
-        "refcount": "1"
-      },
-      "iw_cm": {
-        "size": "49152",
-        "refcount": "1"
-      },
-      "ib_cm": {
-        "size": "45056",
-        "refcount": "1"
-      },
-      "ib_core": {
-        "size": "212992",
-        "refcount": "4"
-      },
-      "configfs": {
-        "size": "40960",
-        "refcount": "2",
-        "version": "0.0.2"
-      },
-      "iscsi_tcp": {
-        "size": "20480",
-        "refcount": "0"
-      },
-      "libiscsi_tcp": {
-        "size": "24576",
-        "refcount": "1"
-      },
-      "libiscsi": {
-        "size": "53248",
-        "refcount": "3"
-      },
-      "scsi_transport_iscsi": {
-        "size": "98304",
-        "refcount": "4",
-        "version": "2.0-870"
-      },
-      "autofs4": {
-        "size": "40960",
-        "refcount": "2"
-      },
-      "btrfs": {
-        "size": "1060864",
-        "refcount": "0"
-      },
-      "raid10": {
-        "size": "49152",
-        "refcount": "0"
-      },
-      "raid456": {
-        "size": "110592",
-        "refcount": "0"
-      },
-      "async_raid6_recov": {
-        "size": "20480",
-        "refcount": "1"
-      },
-      "async_memcpy": {
-        "size": "16384",
-        "refcount": "2"
-      },
-      "async_pq": {
-        "size": "16384",
-        "refcount": "2"
-      },
-      "async_xor": {
-        "size": "16384",
-        "refcount": "3"
-      },
-      "async_tx": {
-        "size": "16384",
-        "refcount": "5"
-      },
-      "xor": {
-        "size": "24576",
-        "refcount": "2"
-      },
-      "raid6_pq": {
-        "size": "102400",
-        "refcount": "4"
-      },
-      "libcrc32c": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "raid1": {
-        "size": "40960",
-        "refcount": "0"
-      },
-      "raid0": {
-        "size": "20480",
-        "refcount": "0"
-      },
-      "multipath": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "linear": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "crct10dif_pclmul": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "crc32_pclmul": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "ghash_clmulni_intel": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "aesni_intel": {
-        "size": "167936",
-        "refcount": "0"
-      },
-      "aes_x86_64": {
-        "size": "20480",
-        "refcount": "1"
-      },
-      "lrw": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "glue_helper": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "ablk_helper": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "cryptd": {
-        "size": "24576",
-        "refcount": "3"
-      },
-      "psmouse": {
-        "size": "139264",
-        "refcount": "0"
-      },
-      "virtio_net": {
-        "size": "32768",
-        "refcount": "0"
-      },
-      "virtio_scsi": {
-        "size": "20480",
-        "refcount": "1"
-      }
-    }
-  },
-  "os": "linux",
-  "os_version": "4.8.0-52-generic",
-  "lsb": {
-    "id": "Ubuntu",
-    "description": "Ubuntu 16.04.2 LTS",
-    "release": "16.04",
-    "codename": "xenial"
-  },
-  "platform": "ubuntu",
-  "platform_version": "16.04",
-  "platform_family": "debian",
-  "filesystem": {
-    "by_device": {
-      "udev": {
-        "kb_size": "1886700",
-        "kb_used": "0",
-        "kb_available": "1886700",
-        "percent_used": "0%",
-        "total_inodes": "471675",
-        "inodes_used": "371",
-        "inodes_available": "471304",
-        "inodes_percent_used": "1%",
-        "fs_type": "devtmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "relatime",
-          "size=1886700k",
-          "nr_inodes=471675",
-          "mode=755"
-        ],
-        "mounts": [
-          "/dev"
-        ]
-      },
-      "tmpfs": {
-        "kb_size": "378828",
-        "kb_used": "0",
-        "kb_available": "378828",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "4",
-        "inodes_available": "473527",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "size=378828k",
-          "mode=700",
-          "uid=1001",
-          "gid=1002"
-        ],
-        "mounts": [
-          "/dev/shm",
-          "/run/lock",
-          "/sys/fs/cgroup",
-          "/run/user/1001"
-        ]
-      },
-      "/dev/sda1": {
-        "kb_size": "10098468",
-        "kb_used": "1401956",
-        "kb_available": "8680128",
-        "percent_used": "14%",
-        "total_inodes": "1280000",
-        "inodes_used": "90038",
-        "inodes_available": "1189962",
-        "inodes_percent_used": "8%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
-        "label": "cloudimg-rootfs",
-        "mounts": [
-          "/"
-        ]
-      },
-      "sysfs": {
-        "fs_type": "sysfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys"
-        ]
-      },
-      "proc": {
-        "fs_type": "proc",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/proc"
-        ]
-      },
-      "devpts": {
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=000"
-        ],
-        "mounts": [
-          "/dev/pts"
-        ]
-      },
-      "securityfs": {
-        "fs_type": "securityfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/kernel/security"
-        ]
-      },
-      "cgroup": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "hugetlb"
-        ],
-        "mounts": [
-          "/sys/fs/cgroup/systemd",
-          "/sys/fs/cgroup/perf_event",
-          "/sys/fs/cgroup/blkio",
-          "/sys/fs/cgroup/devices",
-          "/sys/fs/cgroup/net_cls,net_prio",
-          "/sys/fs/cgroup/cpuset",
-          "/sys/fs/cgroup/cpu,cpuacct",
-          "/sys/fs/cgroup/memory",
-          "/sys/fs/cgroup/freezer",
-          "/sys/fs/cgroup/pids",
-          "/sys/fs/cgroup/hugetlb"
-        ]
-      },
-      "pstore": {
-        "fs_type": "pstore",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/fs/pstore"
-        ]
-      },
-      "hugetlbfs": {
-        "fs_type": "hugetlbfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/dev/hugepages"
-        ]
-      },
-      "mqueue": {
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/dev/mqueue"
-        ]
-      },
-      "systemd-1": {
-        "fs_type": "autofs",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "fd=33",
-          "pgrp=1",
-          "timeout=0",
-          "minproto=5",
-          "maxproto=5",
-          "direct",
-          "pipe_ino=10449"
-        ],
-        "mounts": [
-          "/proc/sys/fs/binfmt_misc"
-        ]
-      },
-      "debugfs": {
-        "fs_type": "debugfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/kernel/debug"
-        ]
-      },
-      "fusectl": {
-        "fs_type": "fusectl",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/fs/fuse/connections"
-        ]
-      },
-      "configfs": {
-        "fs_type": "configfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/kernel/config"
-        ]
-      },
-      "lxcfs": {
-        "fs_type": "fuse.lxcfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "user_id=0",
-          "group_id=0",
-          "allow_other"
-        ],
-        "mounts": [
-          "/var/lib/lxcfs"
-        ]
-      },
-      "/dev/sda": {
-      }
-    },
-    "by_mountpoint": {
-      "/dev": {
-        "kb_size": "1886700",
-        "kb_used": "0",
-        "kb_available": "1886700",
-        "percent_used": "0%",
-        "total_inodes": "471675",
-        "inodes_used": "371",
-        "inodes_available": "471304",
-        "inodes_percent_used": "1%",
-        "fs_type": "devtmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "relatime",
-          "size=1886700k",
-          "nr_inodes=471675",
-          "mode=755"
-        ],
-        "devices": [
-          "udev"
-        ]
-      },
-      "/": {
-        "kb_size": "10098468",
-        "kb_used": "1401956",
-        "kb_available": "8680128",
-        "percent_used": "14%",
-        "total_inodes": "1280000",
-        "inodes_used": "90038",
-        "inodes_available": "1189962",
-        "inodes_percent_used": "8%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
-        "label": "cloudimg-rootfs",
-        "devices": [
-          "/dev/sda1"
-        ]
-      },
-      "/run/lock": {
-        "kb_size": "5120",
-        "kb_used": "0",
-        "kb_available": "5120",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "8",
-        "inodes_available": "473523",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "size=5120k"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/sys/fs/cgroup": {
-        "kb_size": "1894124",
-        "kb_used": "0",
-        "kb_available": "1894124",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "16",
-        "inodes_available": "473515",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "mode=755"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/run/user/1001": {
-        "kb_size": "378828",
-        "kb_used": "0",
-        "kb_available": "378828",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "4",
-        "inodes_available": "473527",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "size=378828k",
-          "mode=700",
-          "uid=1001",
-          "gid=1002"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/sys": {
-        "fs_type": "sysfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "sysfs"
-        ]
-      },
-      "/proc": {
-        "fs_type": "proc",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "proc"
-        ]
-      },
-      "/dev/pts": {
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=000"
-        ],
-        "devices": [
-          "devpts"
-        ]
-      },
-      "/sys/kernel/security": {
-        "fs_type": "securityfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "securityfs"
-        ]
-      },
-      "/sys/fs/cgroup/systemd": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "xattr",
-          "release_agent=/lib/systemd/systemd-cgroups-agent",
-          "name=systemd"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/pstore": {
-        "fs_type": "pstore",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "pstore"
-        ]
-      },
-      "/sys/fs/cgroup/perf_event": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "perf_event"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/blkio": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "blkio"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/devices": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "devices"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/net_cls,net_prio": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "net_cls",
-          "net_prio"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/cpuset": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpuset"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/cpu,cpuacct": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpu",
-          "cpuacct"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/memory": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "memory"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/freezer": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "freezer"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/pids": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "pids"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/hugetlb": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "hugetlb"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/dev/hugepages": {
-        "fs_type": "hugetlbfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "hugetlbfs"
-        ]
-      },
-      "/dev/mqueue": {
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "mqueue"
-        ]
-      },
-      "/proc/sys/fs/binfmt_misc": {
-        "fs_type": "autofs",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "fd=33",
-          "pgrp=1",
-          "timeout=0",
-          "minproto=5",
-          "maxproto=5",
-          "direct",
-          "pipe_ino=10449"
-        ],
-        "devices": [
-          "systemd-1"
-        ]
-      },
-      "/sys/kernel/debug": {
-        "fs_type": "debugfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "debugfs"
-        ]
-      },
-      "/sys/fs/fuse/connections": {
-        "fs_type": "fusectl",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "fusectl"
-        ]
-      },
-      "/sys/kernel/config": {
-        "fs_type": "configfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "configfs"
-        ]
-      },
-      "/var/lib/lxcfs": {
-        "fs_type": "fuse.lxcfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "user_id=0",
-          "group_id=0",
-          "allow_other"
-        ],
-        "devices": [
-          "lxcfs"
-        ]
-      }
-    },
-    "by_pair": {
-      "udev,/dev": {
-        "device": "udev",
-        "kb_size": "1886700",
-        "kb_used": "0",
-        "kb_available": "1886700",
-        "percent_used": "0%",
-        "mount": "/dev",
-        "total_inodes": "471675",
-        "inodes_used": "371",
-        "inodes_available": "471304",
-        "inodes_percent_used": "1%",
-        "fs_type": "devtmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "relatime",
-          "size=1886700k",
-          "nr_inodes=471675",
-          "mode=755"
-        ]
-      },
-      "/dev/sda1,/": {
-        "device": "/dev/sda1",
-        "kb_size": "10098468",
-        "kb_used": "1401956",
-        "kb_available": "8680128",
-        "percent_used": "14%",
-        "mount": "/",
-        "total_inodes": "1280000",
-        "inodes_used": "90038",
-        "inodes_available": "1189962",
-        "inodes_percent_used": "8%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
-        "label": "cloudimg-rootfs"
-      },
-      "tmpfs,/run/lock": {
-        "device": "tmpfs",
-        "kb_size": "5120",
-        "kb_used": "0",
-        "kb_available": "5120",
-        "percent_used": "0%",
-        "mount": "/run/lock",
-        "total_inodes": "473531",
-        "inodes_used": "8",
-        "inodes_available": "473523",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "size=5120k"
-        ]
-      },
-      "tmpfs,/sys/fs/cgroup": {
-        "device": "tmpfs",
-        "kb_size": "1894124",
-        "kb_used": "0",
-        "kb_available": "1894124",
-        "percent_used": "0%",
-        "mount": "/sys/fs/cgroup",
-        "total_inodes": "473531",
-        "inodes_used": "16",
-        "inodes_available": "473515",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "mode=755"
-        ]
-      },
-      "tmpfs,/run/user/1001": {
-        "device": "tmpfs",
-        "kb_size": "378828",
-        "kb_used": "0",
-        "kb_available": "378828",
-        "percent_used": "0%",
-        "mount": "/run/user/1001",
-        "total_inodes": "473531",
-        "inodes_used": "4",
-        "inodes_available": "473527",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "size=378828k",
-          "mode=700",
-          "uid=1001",
-          "gid=1002"
-        ]
-      },
-      "sysfs,/sys": {
-        "device": "sysfs",
-        "mount": "/sys",
-        "fs_type": "sysfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "proc,/proc": {
-        "device": "proc",
-        "mount": "/proc",
-        "fs_type": "proc",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "devpts,/dev/pts": {
-        "device": "devpts",
-        "mount": "/dev/pts",
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=000"
-        ]
-      },
-      "securityfs,/sys/kernel/security": {
-        "device": "securityfs",
-        "mount": "/sys/kernel/security",
-        "fs_type": "securityfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/systemd": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/systemd",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "xattr",
-          "release_agent=/lib/systemd/systemd-cgroups-agent",
-          "name=systemd"
-        ]
-      },
-      "pstore,/sys/fs/pstore": {
-        "device": "pstore",
-        "mount": "/sys/fs/pstore",
-        "fs_type": "pstore",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/perf_event": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/perf_event",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "perf_event"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/blkio": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/blkio",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "blkio"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/devices": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/devices",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "devices"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/net_cls,net_prio": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/net_cls,net_prio",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "net_cls",
-          "net_prio"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/cpuset": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/cpuset",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpuset"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/cpu,cpuacct": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/cpu,cpuacct",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpu",
-          "cpuacct"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/memory": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/memory",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "memory"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/freezer": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/freezer",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "freezer"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/pids": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/pids",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "pids"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/hugetlb": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/hugetlb",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "hugetlb"
-        ]
-      },
-      "hugetlbfs,/dev/hugepages": {
-        "device": "hugetlbfs",
-        "mount": "/dev/hugepages",
-        "fs_type": "hugetlbfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "mqueue,/dev/mqueue": {
-        "device": "mqueue",
-        "mount": "/dev/mqueue",
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "systemd-1,/proc/sys/fs/binfmt_misc": {
-        "device": "systemd-1",
-        "mount": "/proc/sys/fs/binfmt_misc",
-        "fs_type": "autofs",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "fd=33",
-          "pgrp=1",
-          "timeout=0",
-          "minproto=5",
-          "maxproto=5",
-          "direct",
-          "pipe_ino=10449"
-        ]
-      },
-      "debugfs,/sys/kernel/debug": {
-        "device": "debugfs",
-        "mount": "/sys/kernel/debug",
-        "fs_type": "debugfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "fusectl,/sys/fs/fuse/connections": {
-        "device": "fusectl",
-        "mount": "/sys/fs/fuse/connections",
-        "fs_type": "fusectl",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "configfs,/sys/kernel/config": {
-        "device": "configfs",
-        "mount": "/sys/kernel/config",
-        "fs_type": "configfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "lxcfs,/var/lib/lxcfs": {
-        "device": "lxcfs",
-        "mount": "/var/lib/lxcfs",
-        "fs_type": "fuse.lxcfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "user_id=0",
-          "group_id=0",
-          "allow_other"
-        ]
-      },
-      "/dev/sda,": {
-        "device": "/dev/sda"
-      }
-    }
-  },
-  "dmi": {
-    "dmidecode_version": "3.0"
-  },
-  "init_package": "systemd",
-  "ohai_time": 1496219099.5081818,
-  "command": {
-    "ps": "ps -ef"
-  },
-  "root_group": "root",
-  "languages": {
-    "ruby": {
-      "platform": "x86_64-linux-gnu",
-      "version": "2.3.1",
-      "release_date": "2016-04-26",
-      "target": "x86_64-pc-linux-gnu",
-      "target_cpu": "x86_64",
-      "target_vendor": "pc",
-      "target_os": "linux-gnu",
-      "host": "x86_64-pc-linux-gnu",
-      "host_cpu": "x86_64",
-      "host_os": "linux-gnu",
-      "host_vendor": "pc",
-      "bin_dir": "/usr/local/bin",
-      "ruby_bin": "/usr/local/bin/ruby",
-      "gems_dir": "/usr/local/gems",
-      "gem_bin": "/usr/local/bin/gem"
-    },
-    "powershell": null
-  },
-  "chef_packages": {
-    "chef": {
-      "version": "13.1.31",
-      "chef_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-13.1.31/lib"
-    },
-    "ohai": {
-      "version": "13.1.0",
-      "ohai_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/ohai-13.1.0/lib/ohai"
-    }
-  },
-  "counters": {
-    "network": {
-      "interfaces": {
-        "eth0": {
-          "rx": {
-            "bytes": "0",
-            "packets": "0",
-            "errors": "0",
-            "drop": 0,
-            "overrun": 0,
-            "frame": 0,
-            "compressed": 0,
-            "multicast": 0
-          },
-          "tx": {
-            "bytes": "342",
-            "packets": "0",
-            "errors": "0",
-            "drop": 0,
-            "overrun": 0,
-            "collisions": "0",
-            "carrier": 0,
-            "compressed": 0
-          }
-        }
-      }
-    }
-  },
-  "current_user": "fauxhai",
-  "domain": "local",
-  "etc": {
-    "passwd": {
-      "fauxhai": {
-        "dir": "/home/fauxhai",
-        "gid": 0,
-        "uid": 0,
-        "shell": "/bin/bash",
-        "gecos": "Fauxhai"
-      }
-    },
-    "group": {
-      "fauxhai": {
-        "gid": 0,
-        "members": [
-          "fauxhai"
-        ]
-      }
-    }
-  },
-  "hostname": "Fauxhai",
-  "fqdn": "fauxhai.local",
-  "ipaddress": "10.0.0.2",
-  "keys": {
-    "ssh": {
-      "host_dsa_public": "ssh-dss AAAAB3NzaC1kc3MAAACBAJFo9BLAw4WKEs5hgipk5m423FzBsDXCZSMcC9ca/om/1VYzMqImixGe3uICDzNFUWxFoLJTQAOccyzo6MXZiQqwWJDLFi5qOSr6w2XcMyE+zd4wOyMoDiVM5fizmG8K3FzrqvGjwBcHcBdOQnavSijoj38DN25J9zhrid5BY4WlAAAAFQDxXrCyG52XCzn3FV4ej38wJBkomQAAAIBovGPJ4mP2P6BK8lHl0PPbktwQbWlpJ13oz6REJFDVcUi7vV26bX/BjQX+ohzZQzljdz1SpUbPc/8nuA4darYkVh91eBi307EN8IdxRHj2eBgp/ZG4yshIebG3WHrwJD/xUjjZ1MRfyDT1ermVi4LvjjPgWDxLZnPpMaR6S1nzgQAAAIEAj0Vd6DCWslvlsZ8+N53HWsqPi3gnx35JoLPz9Z2epkKIKqmEHav+93G3hdfztVa4I4t3phoPniQchYryF5+RNg8hqxKzjNtrIqUYCeuf2NJrksNsH7OZygPHZpqt4kTuwAGZxjxEGfAI0y8DhkU2ntp2LnzRnWH106BQBCmcXwo= fauxhai.local",
-      "host_rsa_public": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtLCeqtqr/HbnORckw1ukdLhpfGoOPFi5/esKEokzTqq1gsgQ2V8emmyjfq1i6XXfRtSBxkdlHv/GWdP5wBTuE2G85MzBkVSQPvmwQN8lX/JMPEEtKXkeOo0o92/PiSmvY4eRsdF0mw40Uvg7jtE3f3fxj497kzh5fKtkrHnF4x9gXCbVdr3FqXJfggR5IJwAxToerbK7x/uRS+7YuZI9Pip3tt14nv9ezwXcuGb/tvjWOZINiFl8izVIFKi7sxfTX09p4NgamxRS7TD2Yd0jT8nEoF9UZTsgXcJ1kDSx7N7NxFfNfP6rCdOGRRz4gUhXtsUjG/XkxPeCwZ7A9VnOD fauxhai.local"
-    }
-  },
-  "macaddress": "11:11:11:11:11:11",
-  "network": {
-    "default_gateway": "10.0.0.1",
-    "default_interface": "eth0",
-    "settings": {
-    },
-    "interfaces": {
-      "eth0": {
-        "addresses": {
-          "10.0.0.2": {
-            "broadcast": "10.0.0.255",
-            "family": "inet",
-            "netmask": "255.255.255.0",
-            "prefixlen": "24",
-            "scope": "Global"
-          }
-        },
-        "arp": {
-          "10.0.0.1": "fe:ff:ff:ff:ff:ff"
-        },
-        "encapsulation": "Ethernet",
-        "flags": [
-          "BROADCAST",
-          "MULTICAST",
-          "UP",
-          "LOWER_UP"
-        ],
-        "mtu": "1500",
-        "number": "0",
-        "routes": [
-          {
-            "destination": "10.0.0.0/24",
-            "family": "inet",
-            "scope": "link",
-            "proto": "kernel",
-            "src": "10.0.0.2"
-          }
-        ],
-        "state": "up",
-        "type": "eth"
-      }
-    }
-  },
-  "uptime": "30 days 15 hours 07 minutes 30 seconds",
-  "uptime_seconds": 2646450,
-  "cpu": {
-    "real": 1,
-    "total": 1,
-    "cores": 1
-  },
-  "memory": {
-    "total": "1048576kB"
-  },
-  "virtualization": {
-    "systems": {
-    }
-  }
-}
diff --git a/spec/fixtures/fauxhai/ubuntu/16.04.json b/spec/fixtures/fauxhai/ubuntu/16.04.json
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9mYXV4aGFpL3VidW50dS8xNi4wNC5qc29u..0000000000000000000000000000000000000000
--- a/spec/fixtures/fauxhai/ubuntu/16.04.json
+++ /dev/null
@@ -1,1519 +0,0 @@
-{
-  "kernel": {
-    "name": "Linux",
-    "release": "4.8.0-52-generic",
-    "version": "#55~16.04.1-Ubuntu SMP Fri Apr 28 14:36:29 UTC 2017",
-    "machine": "x86_64",
-    "processor": "x86_64",
-    "os": "GNU/Linux",
-    "modules": {
-      "ip6table_filter": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "ip6_tables": {
-        "size": "28672",
-        "refcount": "1"
-      },
-      "iptable_filter": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "ip_tables": {
-        "size": "24576",
-        "refcount": "1"
-      },
-      "x_tables": {
-        "size": "36864",
-        "refcount": "4"
-      },
-      "ppdev": {
-        "size": "20480",
-        "refcount": "0"
-      },
-      "input_leds": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "serio_raw": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "parport_pc": {
-        "size": "32768",
-        "refcount": "0"
-      },
-      "pvpanic": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "parport": {
-        "size": "49152",
-        "refcount": "2"
-      },
-      "ib_iser": {
-        "size": "49152",
-        "refcount": "0",
-        "version": "1.6"
-      },
-      "rdma_cm": {
-        "size": "57344",
-        "refcount": "1"
-      },
-      "iw_cm": {
-        "size": "49152",
-        "refcount": "1"
-      },
-      "ib_cm": {
-        "size": "45056",
-        "refcount": "1"
-      },
-      "ib_core": {
-        "size": "212992",
-        "refcount": "4"
-      },
-      "configfs": {
-        "size": "40960",
-        "refcount": "2",
-        "version": "0.0.2"
-      },
-      "iscsi_tcp": {
-        "size": "20480",
-        "refcount": "0"
-      },
-      "libiscsi_tcp": {
-        "size": "24576",
-        "refcount": "1"
-      },
-      "libiscsi": {
-        "size": "53248",
-        "refcount": "3"
-      },
-      "scsi_transport_iscsi": {
-        "size": "98304",
-        "refcount": "4",
-        "version": "2.0-870"
-      },
-      "autofs4": {
-        "size": "40960",
-        "refcount": "2"
-      },
-      "btrfs": {
-        "size": "1060864",
-        "refcount": "0"
-      },
-      "raid10": {
-        "size": "49152",
-        "refcount": "0"
-      },
-      "raid456": {
-        "size": "110592",
-        "refcount": "0"
-      },
-      "async_raid6_recov": {
-        "size": "20480",
-        "refcount": "1"
-      },
-      "async_memcpy": {
-        "size": "16384",
-        "refcount": "2"
-      },
-      "async_pq": {
-        "size": "16384",
-        "refcount": "2"
-      },
-      "async_xor": {
-        "size": "16384",
-        "refcount": "3"
-      },
-      "async_tx": {
-        "size": "16384",
-        "refcount": "5"
-      },
-      "xor": {
-        "size": "24576",
-        "refcount": "2"
-      },
-      "raid6_pq": {
-        "size": "102400",
-        "refcount": "4"
-      },
-      "libcrc32c": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "raid1": {
-        "size": "40960",
-        "refcount": "0"
-      },
-      "raid0": {
-        "size": "20480",
-        "refcount": "0"
-      },
-      "multipath": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "linear": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "crct10dif_pclmul": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "crc32_pclmul": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "ghash_clmulni_intel": {
-        "size": "16384",
-        "refcount": "0"
-      },
-      "aesni_intel": {
-        "size": "167936",
-        "refcount": "0"
-      },
-      "aes_x86_64": {
-        "size": "20480",
-        "refcount": "1"
-      },
-      "lrw": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "glue_helper": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "ablk_helper": {
-        "size": "16384",
-        "refcount": "1"
-      },
-      "cryptd": {
-        "size": "24576",
-        "refcount": "3"
-      },
-      "psmouse": {
-        "size": "139264",
-        "refcount": "0"
-      },
-      "virtio_net": {
-        "size": "32768",
-        "refcount": "0"
-      },
-      "virtio_scsi": {
-        "size": "20480",
-        "refcount": "1"
-      }
-    }
-  },
-  "os": "linux",
-  "os_version": "4.8.0-52-generic",
-  "lsb": {
-    "id": "Ubuntu",
-    "description": "Ubuntu 16.04.2 LTS",
-    "release": "16.04",
-    "codename": "xenial"
-  },
-  "platform": "ubuntu",
-  "platform_version": "16.04",
-  "platform_family": "debian",
-  "filesystem": {
-    "by_device": {
-      "udev": {
-        "kb_size": "1886700",
-        "kb_used": "0",
-        "kb_available": "1886700",
-        "percent_used": "0%",
-        "total_inodes": "471675",
-        "inodes_used": "371",
-        "inodes_available": "471304",
-        "inodes_percent_used": "1%",
-        "fs_type": "devtmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "relatime",
-          "size=1886700k",
-          "nr_inodes=471675",
-          "mode=755"
-        ],
-        "mounts": [
-          "/dev"
-        ]
-      },
-      "tmpfs": {
-        "kb_size": "378828",
-        "kb_used": "0",
-        "kb_available": "378828",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "4",
-        "inodes_available": "473527",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "size=378828k",
-          "mode=700",
-          "uid=1001",
-          "gid=1002"
-        ],
-        "mounts": [
-          "/run",
-          "/dev/shm",
-          "/run/lock",
-          "/sys/fs/cgroup",
-          "/run/user/1001"
-        ]
-      },
-      "/dev/sda1": {
-        "kb_size": "10098468",
-        "kb_used": "1401956",
-        "kb_available": "8680128",
-        "percent_used": "14%",
-        "total_inodes": "1280000",
-        "inodes_used": "90038",
-        "inodes_available": "1189962",
-        "inodes_percent_used": "8%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
-        "label": "cloudimg-rootfs",
-        "mounts": [
-          "/"
-        ]
-      },
-      "sysfs": {
-        "fs_type": "sysfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys"
-        ]
-      },
-      "proc": {
-        "fs_type": "proc",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/proc"
-        ]
-      },
-      "devpts": {
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=000"
-        ],
-        "mounts": [
-          "/dev/pts"
-        ]
-      },
-      "securityfs": {
-        "fs_type": "securityfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/kernel/security"
-        ]
-      },
-      "cgroup": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "hugetlb"
-        ],
-        "mounts": [
-          "/sys/fs/cgroup/systemd",
-          "/sys/fs/cgroup/perf_event",
-          "/sys/fs/cgroup/blkio",
-          "/sys/fs/cgroup/devices",
-          "/sys/fs/cgroup/net_cls,net_prio",
-          "/sys/fs/cgroup/cpuset",
-          "/sys/fs/cgroup/cpu,cpuacct",
-          "/sys/fs/cgroup/memory",
-          "/sys/fs/cgroup/freezer",
-          "/sys/fs/cgroup/pids",
-          "/sys/fs/cgroup/hugetlb"
-        ]
-      },
-      "pstore": {
-        "fs_type": "pstore",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/fs/pstore"
-        ]
-      },
-      "hugetlbfs": {
-        "fs_type": "hugetlbfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/dev/hugepages"
-        ]
-      },
-      "mqueue": {
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/dev/mqueue"
-        ]
-      },
-      "systemd-1": {
-        "fs_type": "autofs",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "fd=33",
-          "pgrp=1",
-          "timeout=0",
-          "minproto=5",
-          "maxproto=5",
-          "direct",
-          "pipe_ino=10449"
-        ],
-        "mounts": [
-          "/proc/sys/fs/binfmt_misc"
-        ]
-      },
-      "debugfs": {
-        "fs_type": "debugfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/kernel/debug"
-        ]
-      },
-      "fusectl": {
-        "fs_type": "fusectl",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/fs/fuse/connections"
-        ]
-      },
-      "configfs": {
-        "fs_type": "configfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "mounts": [
-          "/sys/kernel/config"
-        ]
-      },
-      "lxcfs": {
-        "fs_type": "fuse.lxcfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "user_id=0",
-          "group_id=0",
-          "allow_other"
-        ],
-        "mounts": [
-          "/var/lib/lxcfs"
-        ]
-      },
-      "/dev/sda": {
-      }
-    },
-    "by_mountpoint": {
-      "/dev": {
-        "kb_size": "1886700",
-        "kb_used": "0",
-        "kb_available": "1886700",
-        "percent_used": "0%",
-        "total_inodes": "471675",
-        "inodes_used": "371",
-        "inodes_available": "471304",
-        "inodes_percent_used": "1%",
-        "fs_type": "devtmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "relatime",
-          "size=1886700k",
-          "nr_inodes=471675",
-          "mode=755"
-        ],
-        "devices": [
-          "udev"
-        ]
-      },
-      "/run": {
-        "kb_size": "378828",
-        "kb_used": "5292",
-        "kb_available": "373536",
-        "percent_used": "2%",
-        "total_inodes": "473531",
-        "inodes_used": "460",
-        "inodes_available": "473071",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "size=378828k",
-          "mode=755"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/": {
-        "kb_size": "10098468",
-        "kb_used": "1401956",
-        "kb_available": "8680128",
-        "percent_used": "14%",
-        "total_inodes": "1280000",
-        "inodes_used": "90038",
-        "inodes_available": "1189962",
-        "inodes_percent_used": "8%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
-        "label": "cloudimg-rootfs",
-        "devices": [
-          "/dev/sda1"
-        ]
-      },
-      "/dev/shm": {
-        "kb_size": "1894124",
-        "kb_used": "0",
-        "kb_available": "1894124",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "1",
-        "inodes_available": "473530",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/run/lock": {
-        "kb_size": "5120",
-        "kb_used": "0",
-        "kb_available": "5120",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "8",
-        "inodes_available": "473523",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "size=5120k"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/sys/fs/cgroup": {
-        "kb_size": "1894124",
-        "kb_used": "0",
-        "kb_available": "1894124",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "16",
-        "inodes_available": "473515",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "mode=755"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/run/user/1001": {
-        "kb_size": "378828",
-        "kb_used": "0",
-        "kb_available": "378828",
-        "percent_used": "0%",
-        "total_inodes": "473531",
-        "inodes_used": "4",
-        "inodes_available": "473527",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "size=378828k",
-          "mode=700",
-          "uid=1001",
-          "gid=1002"
-        ],
-        "devices": [
-          "tmpfs"
-        ]
-      },
-      "/sys": {
-        "fs_type": "sysfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "sysfs"
-        ]
-      },
-      "/proc": {
-        "fs_type": "proc",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "proc"
-        ]
-      },
-      "/dev/pts": {
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=000"
-        ],
-        "devices": [
-          "devpts"
-        ]
-      },
-      "/sys/kernel/security": {
-        "fs_type": "securityfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "securityfs"
-        ]
-      },
-      "/sys/fs/cgroup/systemd": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "xattr",
-          "release_agent=/lib/systemd/systemd-cgroups-agent",
-          "name=systemd"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/pstore": {
-        "fs_type": "pstore",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ],
-        "devices": [
-          "pstore"
-        ]
-      },
-      "/sys/fs/cgroup/perf_event": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "perf_event"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/blkio": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "blkio"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/devices": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "devices"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/net_cls,net_prio": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "net_cls",
-          "net_prio"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/cpuset": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpuset"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/cpu,cpuacct": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpu",
-          "cpuacct"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/memory": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "memory"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/freezer": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "freezer"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/pids": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "pids"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/sys/fs/cgroup/hugetlb": {
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "hugetlb"
-        ],
-        "devices": [
-          "cgroup"
-        ]
-      },
-      "/dev/hugepages": {
-        "fs_type": "hugetlbfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "hugetlbfs"
-        ]
-      },
-      "/dev/mqueue": {
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "mqueue"
-        ]
-      },
-      "/proc/sys/fs/binfmt_misc": {
-        "fs_type": "autofs",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "fd=33",
-          "pgrp=1",
-          "timeout=0",
-          "minproto=5",
-          "maxproto=5",
-          "direct",
-          "pipe_ino=10449"
-        ],
-        "devices": [
-          "systemd-1"
-        ]
-      },
-      "/sys/kernel/debug": {
-        "fs_type": "debugfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "debugfs"
-        ]
-      },
-      "/sys/fs/fuse/connections": {
-        "fs_type": "fusectl",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "fusectl"
-        ]
-      },
-      "/sys/kernel/config": {
-        "fs_type": "configfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ],
-        "devices": [
-          "configfs"
-        ]
-      },
-      "/var/lib/lxcfs": {
-        "fs_type": "fuse.lxcfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "user_id=0",
-          "group_id=0",
-          "allow_other"
-        ],
-        "devices": [
-          "lxcfs"
-        ]
-      }
-    },
-    "by_pair": {
-      "udev,/dev": {
-        "device": "udev",
-        "kb_size": "1886700",
-        "kb_used": "0",
-        "kb_available": "1886700",
-        "percent_used": "0%",
-        "mount": "/dev",
-        "total_inodes": "471675",
-        "inodes_used": "371",
-        "inodes_available": "471304",
-        "inodes_percent_used": "1%",
-        "fs_type": "devtmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "relatime",
-          "size=1886700k",
-          "nr_inodes=471675",
-          "mode=755"
-        ]
-      },
-      "tmpfs,/run": {
-        "device": "tmpfs",
-        "kb_size": "378828",
-        "kb_used": "5292",
-        "kb_available": "373536",
-        "percent_used": "2%",
-        "mount": "/run",
-        "total_inodes": "473531",
-        "inodes_used": "460",
-        "inodes_available": "473071",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "size=378828k",
-          "mode=755"
-        ]
-      },
-      "/dev/sda1,/": {
-        "device": "/dev/sda1",
-        "kb_size": "10098468",
-        "kb_used": "1401956",
-        "kb_available": "8680128",
-        "percent_used": "14%",
-        "mount": "/",
-        "total_inodes": "1280000",
-        "inodes_used": "90038",
-        "inodes_available": "1189962",
-        "inodes_percent_used": "8%",
-        "fs_type": "ext4",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "data=ordered"
-        ],
-        "uuid": "67e1c0a6-cdc9-4247-b2e5-dd20ef042025",
-        "label": "cloudimg-rootfs"
-      },
-      "tmpfs,/dev/shm": {
-        "device": "tmpfs",
-        "kb_size": "1894124",
-        "kb_used": "0",
-        "kb_available": "1894124",
-        "percent_used": "0%",
-        "mount": "/dev/shm",
-        "total_inodes": "473531",
-        "inodes_used": "1",
-        "inodes_available": "473530",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev"
-        ]
-      },
-      "tmpfs,/run/lock": {
-        "device": "tmpfs",
-        "kb_size": "5120",
-        "kb_used": "0",
-        "kb_available": "5120",
-        "percent_used": "0%",
-        "mount": "/run/lock",
-        "total_inodes": "473531",
-        "inodes_used": "8",
-        "inodes_available": "473523",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "size=5120k"
-        ]
-      },
-      "tmpfs,/sys/fs/cgroup": {
-        "device": "tmpfs",
-        "kb_size": "1894124",
-        "kb_used": "0",
-        "kb_available": "1894124",
-        "percent_used": "0%",
-        "mount": "/sys/fs/cgroup",
-        "total_inodes": "473531",
-        "inodes_used": "16",
-        "inodes_available": "473515",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "ro",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "mode=755"
-        ]
-      },
-      "tmpfs,/run/user/1001": {
-        "device": "tmpfs",
-        "kb_size": "378828",
-        "kb_used": "0",
-        "kb_available": "378828",
-        "percent_used": "0%",
-        "mount": "/run/user/1001",
-        "total_inodes": "473531",
-        "inodes_used": "4",
-        "inodes_available": "473527",
-        "inodes_percent_used": "1%",
-        "fs_type": "tmpfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "size=378828k",
-          "mode=700",
-          "uid=1001",
-          "gid=1002"
-        ]
-      },
-      "sysfs,/sys": {
-        "device": "sysfs",
-        "mount": "/sys",
-        "fs_type": "sysfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "proc,/proc": {
-        "device": "proc",
-        "mount": "/proc",
-        "fs_type": "proc",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "devpts,/dev/pts": {
-        "device": "devpts",
-        "mount": "/dev/pts",
-        "fs_type": "devpts",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "noexec",
-          "relatime",
-          "gid=5",
-          "mode=620",
-          "ptmxmode=000"
-        ]
-      },
-      "securityfs,/sys/kernel/security": {
-        "device": "securityfs",
-        "mount": "/sys/kernel/security",
-        "fs_type": "securityfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/systemd": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/systemd",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "xattr",
-          "release_agent=/lib/systemd/systemd-cgroups-agent",
-          "name=systemd"
-        ]
-      },
-      "pstore,/sys/fs/pstore": {
-        "device": "pstore",
-        "mount": "/sys/fs/pstore",
-        "fs_type": "pstore",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/perf_event": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/perf_event",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "perf_event"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/blkio": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/blkio",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "blkio"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/devices": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/devices",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "devices"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/net_cls,net_prio": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/net_cls,net_prio",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "net_cls",
-          "net_prio"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/cpuset": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/cpuset",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpuset"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/cpu,cpuacct": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/cpu,cpuacct",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "cpu",
-          "cpuacct"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/memory": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/memory",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "memory"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/freezer": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/freezer",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "freezer"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/pids": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/pids",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "pids"
-        ]
-      },
-      "cgroup,/sys/fs/cgroup/hugetlb": {
-        "device": "cgroup",
-        "mount": "/sys/fs/cgroup/hugetlb",
-        "fs_type": "cgroup",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "noexec",
-          "relatime",
-          "hugetlb"
-        ]
-      },
-      "hugetlbfs,/dev/hugepages": {
-        "device": "hugetlbfs",
-        "mount": "/dev/hugepages",
-        "fs_type": "hugetlbfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "mqueue,/dev/mqueue": {
-        "device": "mqueue",
-        "mount": "/dev/mqueue",
-        "fs_type": "mqueue",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "systemd-1,/proc/sys/fs/binfmt_misc": {
-        "device": "systemd-1",
-        "mount": "/proc/sys/fs/binfmt_misc",
-        "fs_type": "autofs",
-        "mount_options": [
-          "rw",
-          "relatime",
-          "fd=33",
-          "pgrp=1",
-          "timeout=0",
-          "minproto=5",
-          "maxproto=5",
-          "direct",
-          "pipe_ino=10449"
-        ]
-      },
-      "debugfs,/sys/kernel/debug": {
-        "device": "debugfs",
-        "mount": "/sys/kernel/debug",
-        "fs_type": "debugfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "fusectl,/sys/fs/fuse/connections": {
-        "device": "fusectl",
-        "mount": "/sys/fs/fuse/connections",
-        "fs_type": "fusectl",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "configfs,/sys/kernel/config": {
-        "device": "configfs",
-        "mount": "/sys/kernel/config",
-        "fs_type": "configfs",
-        "mount_options": [
-          "rw",
-          "relatime"
-        ]
-      },
-      "lxcfs,/var/lib/lxcfs": {
-        "device": "lxcfs",
-        "mount": "/var/lib/lxcfs",
-        "fs_type": "fuse.lxcfs",
-        "mount_options": [
-          "rw",
-          "nosuid",
-          "nodev",
-          "relatime",
-          "user_id=0",
-          "group_id=0",
-          "allow_other"
-        ]
-      },
-      "/dev/sda,": {
-        "device": "/dev/sda"
-      }
-    }
-  },
-  "dmi": {
-    "dmidecode_version": "3.0"
-  },
-  "init_package": "systemd",
-  "ohai_time": 1496219099.5081818,
-  "command": {
-    "ps": "ps -ef"
-  },
-  "root_group": "root",
-  "languages": {
-    "ruby": {
-      "platform": "x86_64-linux-gnu",
-      "version": "2.3.1",
-      "release_date": "2016-04-26",
-      "target": "x86_64-pc-linux-gnu",
-      "target_cpu": "x86_64",
-      "target_vendor": "pc",
-      "target_os": "linux-gnu",
-      "host": "x86_64-pc-linux-gnu",
-      "host_cpu": "x86_64",
-      "host_os": "linux-gnu",
-      "host_vendor": "pc",
-      "bin_dir": "/usr/local/bin",
-      "ruby_bin": "/usr/local/bin/ruby",
-      "gems_dir": "/usr/local/gems",
-      "gem_bin": "/usr/local/bin/gem"
-    },
-    "powershell": null
-  },
-  "chef_packages": {
-    "chef": {
-      "version": "13.1.31",
-      "chef_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-13.1.31/lib"
-    },
-    "ohai": {
-      "version": "13.1.0",
-      "ohai_root": "/opt/chef/embedded/lib/ruby/gems/2.1.0/gems/ohai-13.1.0/lib/ohai"
-    }
-  },
-  "counters": {
-    "network": {
-      "interfaces": {
-        "eth0": {
-          "rx": {
-            "bytes": "0",
-            "packets": "0",
-            "errors": "0",
-            "drop": 0,
-            "overrun": 0,
-            "frame": 0,
-            "compressed": 0,
-            "multicast": 0
-          },
-          "tx": {
-            "bytes": "342",
-            "packets": "0",
-            "errors": "0",
-            "drop": 0,
-            "overrun": 0,
-            "collisions": "0",
-            "carrier": 0,
-            "compressed": 0
-          }
-        }
-      }
-    }
-  },
-  "current_user": "fauxhai",
-  "domain": "local",
-  "etc": {
-    "passwd": {
-      "fauxhai": {
-        "dir": "/home/fauxhai",
-        "gid": 0,
-        "uid": 0,
-        "shell": "/bin/bash",
-        "gecos": "Fauxhai"
-      }
-    },
-    "group": {
-      "fauxhai": {
-        "gid": 0,
-        "members": [
-          "fauxhai"
-        ]
-      }
-    }
-  },
-  "hostname": "Fauxhai",
-  "fqdn": "fauxhai.local",
-  "ipaddress": "10.0.0.2",
-  "keys": {
-    "ssh": {
-      "host_dsa_public": "ssh-dss AAAAB3NzaC1kc3MAAACBAJFo9BLAw4WKEs5hgipk5m423FzBsDXCZSMcC9ca/om/1VYzMqImixGe3uICDzNFUWxFoLJTQAOccyzo6MXZiQqwWJDLFi5qOSr6w2XcMyE+zd4wOyMoDiVM5fizmG8K3FzrqvGjwBcHcBdOQnavSijoj38DN25J9zhrid5BY4WlAAAAFQDxXrCyG52XCzn3FV4ej38wJBkomQAAAIBovGPJ4mP2P6BK8lHl0PPbktwQbWlpJ13oz6REJFDVcUi7vV26bX/BjQX+ohzZQzljdz1SpUbPc/8nuA4darYkVh91eBi307EN8IdxRHj2eBgp/ZG4yshIebG3WHrwJD/xUjjZ1MRfyDT1ermVi4LvjjPgWDxLZnPpMaR6S1nzgQAAAIEAj0Vd6DCWslvlsZ8+N53HWsqPi3gnx35JoLPz9Z2epkKIKqmEHav+93G3hdfztVa4I4t3phoPniQchYryF5+RNg8hqxKzjNtrIqUYCeuf2NJrksNsH7OZygPHZpqt4kTuwAGZxjxEGfAI0y8DhkU2ntp2LnzRnWH106BQBCmcXwo= fauxhai.local",
-      "host_rsa_public": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtLCeqtqr/HbnORckw1ukdLhpfGoOPFi5/esKEokzTqq1gsgQ2V8emmyjfq1i6XXfRtSBxkdlHv/GWdP5wBTuE2G85MzBkVSQPvmwQN8lX/JMPEEtKXkeOo0o92/PiSmvY4eRsdF0mw40Uvg7jtE3f3fxj497kzh5fKtkrHnF4x9gXCbVdr3FqXJfggR5IJwAxToerbK7x/uRS+7YuZI9Pip3tt14nv9ezwXcuGb/tvjWOZINiFl8izVIFKi7sxfTX09p4NgamxRS7TD2Yd0jT8nEoF9UZTsgXcJ1kDSx7N7NxFfNfP6rCdOGRRz4gUhXtsUjG/XkxPeCwZ7A9VnOD fauxhai.local"
-    }
-  },
-  "macaddress": "11:11:11:11:11:11",
-  "network": {
-    "default_gateway": "10.0.0.1",
-    "default_interface": "eth0",
-    "settings": {
-    },
-    "interfaces": {
-      "eth0": {
-        "addresses": {
-          "10.0.0.2": {
-            "broadcast": "10.0.0.255",
-            "family": "inet",
-            "netmask": "255.255.255.0",
-            "prefixlen": "24",
-            "scope": "Global"
-          }
-        },
-        "arp": {
-          "10.0.0.1": "fe:ff:ff:ff:ff:ff"
-        },
-        "encapsulation": "Ethernet",
-        "flags": [
-          "BROADCAST",
-          "MULTICAST",
-          "UP",
-          "LOWER_UP"
-        ],
-        "mtu": "1500",
-        "number": "0",
-        "routes": [
-          {
-            "destination": "10.0.0.0/24",
-            "family": "inet",
-            "scope": "link",
-            "proto": "kernel",
-            "src": "10.0.0.2"
-          }
-        ],
-        "state": "up",
-        "type": "eth"
-      }
-    }
-  },
-  "uptime": "30 days 15 hours 07 minutes 30 seconds",
-  "uptime_seconds": 2646450,
-  "cpu": {
-    "real": 1,
-    "total": 1,
-    "cores": 1
-  },
-  "memory": {
-    "total": "1048576kB"
-  },
-  "virtualization": {
-    "systems": {
-    }
-  }
-}
diff --git a/spec/fixtures/migration/failed-migration-status-file b/spec/fixtures/migration/failed-migration-status-file
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9taWdyYXRpb24vZmFpbGVkLW1pZ3JhdGlvbi1zdGF0dXMtZmlsZQ==..0000000000000000000000000000000000000000
--- a/spec/fixtures/migration/failed-migration-status-file
+++ /dev/null
@@ -1,1 +0,0 @@
-1
diff --git a/spec/fixtures/migration/successful-migration-status-file b/spec/fixtures/migration/successful-migration-status-file
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9maXh0dXJlcy9taWdyYXRpb24vc3VjY2Vzc2Z1bC1taWdyYXRpb24tc3RhdHVzLWZpbGU=..0000000000000000000000000000000000000000
--- a/spec/fixtures/migration/successful-migration-status-file
+++ /dev/null
@@ -1,1 +0,0 @@
-0
diff --git a/spec/gitlab-ctl-commands-ee/geo_replication_spec.rb b/spec/gitlab-ctl-commands-ee/geo_replication_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2dlb19yZXBsaWNhdGlvbl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/geo_replication_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'spec_helper'
-require 'omnibus-ctl'
-
-RSpec.describe 'gitlab-ctl geo-replication' do
-  let(:toggle_command) { double(Geo::ReplicationToggleCommand) }
-
-  subject { Omnibus::Ctl.new('testing-ctl') }
-
-  before do
-    allow_any_instance_of(Omnibus::Ctl).to receive(:require).and_call_original
-    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
-      '/opt/testing-ctl/embedded/service/omnibus-ctl-ee/lib/geo/replication_process'
-    ) do
-      require_relative('../../files/gitlab-ctl-commands-ee/lib/geo/replication_process')
-    end
-    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
-      '/opt/testing-ctl/embedded/service/omnibus-ctl-ee/lib/geo/replication_toggle_command'
-    ) do
-      require_relative('../../files/gitlab-ctl-commands-ee/lib/geo/replication_toggle_command')
-    end
-
-    subject.load_file('files/gitlab-ctl-commands-ee/geo_replication.rb')
-  end
-
-  it 'appends the geo replication pause and resume commands' do
-    expect(subject.get_all_commands_hash).to include('geo-replication-pause')
-    expect(subject.get_all_commands_hash).to include('geo-replication-resume')
-  end
-
-  describe 'pause' do
-    it 'calls pause' do
-      expect(Geo::ReplicationToggleCommand).to receive(:new).with(anything, 'pause', anything).and_return(toggle_command)
-      expect(toggle_command).to receive(:execute!)
-
-      subject.geo_replication_pause
-    end
-  end
-
-  describe 'resume' do
-    it 'calls resume' do
-      expect(Geo::ReplicationToggleCommand).to receive(:new).with(anything, 'resume', anything).and_return(toggle_command)
-      expect(toggle_command).to receive(:execute!)
-
-      subject.geo_replication_resume
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/geo_spec.rb b/spec/gitlab-ctl-commands-ee/geo_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2dlb19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/geo_spec.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require 'omnibus-ctl'
-require 'optparse'
-
-require_relative('../../files/gitlab-ctl-commands/lib/gitlab_ctl')
-require_relative('../../files/gitlab-ctl-commands-ee/lib/geo')
-
-RSpec.describe 'gitlab-ctl geo' do
-  commands = %w(promote)
-
-  command_lines = {
-    'promote' => []
-  }
-
-  command_options = {
-    'promote' => {}
-  }
-
-  describe '.parse_options' do
-    before do
-      allow(Geo::Utils).to receive(:warn_and_exit).and_call_original
-      allow(Kernel).to receive(:exit) { |code| raise "Kernel.exit(#{code})" }
-      allow(Kernel).to receive(:warn)
-    end
-
-    it 'throws an error when global options are invalid' do
-      expect { Geo.parse_options(%w(geo --foo)) }.to raise_error(OptionParser::ParseError)
-    end
-
-    it 'throws an error when sub-command is not specified' do
-      expect { Geo.parse_options(%w(geo -v)) }.to raise_error(OptionParser::ParseError)
-    end
-
-    it 'throws an error when sub-command is not defined' do
-      expect { Geo.parse_options(%w(geo -v foo)) }.to raise_error(OptionParser::ParseError)
-    end
-
-    it 'recognizes global options' do
-      expect(Geo.parse_options(%w(geo -v -q promote))).to include(quiet: true, verbose: true)
-    end
-
-    context 'when sub-command is passed' do
-      commands.each do |cmd|
-        it "parses #{cmd} options" do
-          cmd_line = command_lines[cmd] || []
-          cmd_opts = command_options[cmd] || {}
-          cmd_opts[:command] = cmd
-
-          expect(Geo.parse_options(%W(geo #{cmd}) + cmd_line)).to include(cmd_opts)
-        end
-      end
-    end
-
-    context 'when help option is passed' do
-      it 'shows help message and exit for global help option' do
-        expect { Geo.parse_options(%w(geo -h)) }.to raise_error('Kernel.exit(0)')
-        expect(Geo::Utils).to have_received(:warn_and_exit).with(/Usage help/)
-      end
-
-      commands.each do |cmd|
-        it "shows help message and exit for #{cmd} help option" do
-          expect { Geo.parse_options(%W(geo #{cmd} -h)) }.to raise_error('Kernel.exit(0)')
-          expect(Geo::Utils).to have_received(:warn_and_exit).with(instance_of(OptionParser))
-        end
-      end
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/consul_spec.rb b/spec/gitlab-ctl-commands-ee/lib/consul_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9jb25zdWxfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/consul_spec.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-require 'spec_helper'
-
-$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands-ee/lib')
-
-require 'consul'
-
-RSpec.describe ConsulHandler do
-  describe '#initialize' do
-    it 'creates instance based on args' do
-      instance = ConsulHandler.new([nil, nil, 'consul', 'kv', 'set'], 'rspec')
-      expect(instance.command).to eq(ConsulHandler::Kv)
-      expect(instance.subcommand).to eq('set')
-      expect(instance.input).to eq('rspec')
-    end
-  end
-
-  describe '#execute' do
-    it 'calls the method on command' do
-      instance = ConsulHandler.new([nil, nil, 'consul', 'kv', 'set'], 'rspec')
-      instance.command = spy
-      expect(instance.command).to receive(:set).with('rspec')
-      instance.execute
-    end
-  end
-
-  let(:consul_cmd) { '/opt/gitlab/embedded/bin/consul' }
-
-  describe ConsulHandler::Kv do
-    it 'allows nil values' do
-      results = double('results', run_command: [], error!: nil, stdout: '')
-      expect(Mixlib::ShellOut).to receive(:new).with("#{consul_cmd} kv put foo ").and_return(results)
-      described_class.send(:put, 'foo')
-    end
-
-    describe '#get' do
-      context 'existing key' do
-        before do
-          results = double('results', run_command: [], error!: nil, stdout: 'bar')
-          allow(Mixlib::ShellOut).to receive(:new).with("#{consul_cmd} kv get foo").and_return(results)
-        end
-
-        it 'returns the expected key' do
-          expect(described_class.get('foo')).to eq('bar')
-        end
-      end
-
-      context 'non-existing key' do
-        before do
-          results = double('results', run_command: [], stdout: '', stderr: 'oops')
-          allow(results).to receive(:error!).and_raise(StandardError)
-          allow(Mixlib::ShellOut).to receive(:new).with("#{consul_cmd} kv get foo").and_return(results)
-        end
-
-        it 'raises an error' do
-          expect { described_class.get('foo') }.to raise_error(ConsulHandler::ConsulError, 'StandardError: oops')
-        end
-      end
-    end
-  end
-
-  describe ConsulHandler::Encrypt do
-    describe '#keygen' do
-      it 'returns the generated key' do
-        generated_key = 'BYX6EPaUiUI0TDdm6gAMmmLnpJSwePJ33Xwh6rjCYbg='
-        results = double('results', run_command: [], error!: nil, stdout: generated_key)
-        allow(Mixlib::ShellOut).to receive(:new).with("#{consul_cmd} keygen").and_return(results)
-
-        expect(described_class.keygen).to eq(generated_key)
-      end
-
-      it 'raises an error' do
-        results = double('results', run_command: [], stdout: '', stderr: 'oops')
-        allow(results).to receive(:error!).and_raise(StandardError)
-        allow(Mixlib::ShellOut).to receive(:new).with("#{consul_cmd} keygen").and_return(results)
-
-        expect { described_class.keygen }.to raise_error(ConsulHandler::ConsulError, 'StandardError: oops')
-      end
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/geo/pitr_file_spec.rb b/spec/gitlab-ctl-commands-ee/lib/geo/pitr_file_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9nZW8vcGl0cl9maWxlX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/geo/pitr_file_spec.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-require 'spec_helper'
-require 'geo/pitr_file'
-
-RSpec.describe Geo::PitrFile do
-  let(:lsn) { 'imafakelsn' }
-  let(:filepath) { '/fake/file/path' }
-  subject { described_class.new(filepath, consul_key: 'fake-key') }
-
-  context 'with a local file' do
-    before do
-      allow(Dir).to receive(:exist?).with('/opt/gitlab/service/consul').and_return(false)
-    end
-
-    it "doesn't use consul" do
-      expect(subject.use_consul?).to be(false)
-    end
-
-    context '#create' do
-      it 'creates the pitr file' do
-        expect(File).to receive(:write).with(filepath, lsn)
-        subject.create(lsn)
-      end
-    end
-
-    context '#delete' do
-      it 'removes the pitr file' do
-        allow(File).to receive(:exist?).with(filepath).and_return(true)
-        expect(File).to receive(:delete).with(filepath)
-
-        subject.delete
-      end
-
-      it 'does not raise an error if the file does not exist' do
-        allow(File).to receive(:exist?).with(filepath).and_return(false)
-        allow(File).to receive(:delete).with(filepath).and_raise(Errno::ENOENT)
-
-        expect { subject.delete }.to_not raise_error(Geo::PitrFileError, "Unable to delete PITR")
-      end
-    end
-
-    context '#get' do
-      before do
-        allow(File).to receive(:read).with(filepath).and_return(lsn)
-      end
-
-      it 'returns the correct lsn' do
-        expect(subject.get).to eq(lsn)
-      end
-
-      it 'raises an error if the file does not exist' do
-        allow(File).to receive(:read).with(filepath).and_raise(Errno::ENOENT)
-
-        expect { subject.get }.to raise_error(Geo::PitrFileError, "Unable to fetch PITR")
-      end
-    end
-  end
-
-  context 'when consul is enabled' do
-    before do
-      allow(Dir).to receive(:exist?).with('/opt/gitlab/service/consul').and_return(true)
-    end
-
-    it "uses consul" do
-      expect(subject.use_consul?).to be(true)
-    end
-
-    context '#create' do
-      it 'create the write key entry' do
-        expect(ConsulHandler::Kv).to receive(:put).with(subject.consul_key, lsn)
-
-        subject.create(lsn)
-      end
-    end
-
-    context '#delete' do
-      it 'removes the consul key' do
-        expect(ConsulHandler::Kv).to receive(:delete).with(subject.consul_key)
-
-        subject.delete
-      end
-
-      it 'raises an error if the key does not exist' do
-        expect { subject.delete }.to raise_error(Geo::PitrFileError, 'Unable to delete PITR')
-      end
-    end
-
-    context '#get' do
-      context 'existing key' do
-        before do
-          allow(ConsulHandler::Kv).to receive(:get).with(subject.consul_key).and_return(lsn)
-        end
-
-        it 'returns the correct lsn' do
-          expect(subject.get).to eq(lsn)
-        end
-      end
-
-      context 'non-existing key' do
-        it 'raises an error if the key does not exist' do
-          expect { subject.get }.to raise_error(Geo::PitrFileError, 'Unable to fetch PITR')
-        end
-      end
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/geo/promote_db_spec.rb b/spec/gitlab-ctl-commands-ee/lib/geo/promote_db_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9nZW8vcHJvbW90ZV9kYl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/geo/promote_db_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-require 'spec_helper'
-require 'geo/promote_db'
-require 'gitlab_ctl/util'
-
-RSpec.describe Geo::PromoteDb, '#execute' do
-  let(:instance) { double(base_path: '/opt/gitlab/embedded') }
-
-  subject(:command) { described_class.new(instance) }
-
-  before do
-    allow($stdout).to receive(:puts)
-    allow($stdout).to receive(:print)
-
-    allow(command).to receive(:run_command).with(any_args)
-
-    allow(command).to receive(:run_command).and_return(double('error!' => nil))
-  end
-
-  context 'when PITR file does not exist' do
-    it 'does not run PITR recovery' do
-      expect(command).not_to receive(:write_recovery_settings)
-
-      command.execute
-    end
-  end
-
-  context 'postgres dir is in non-default location' do
-    it 'uses the specified postgres directory when writing ' do
-      expected_dir = '/non/default/location'
-      allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'postgresql' => { 'dir' => expected_dir } })
-
-      expect(described_class.new(instance).postgresql_dir_path).to eq(expected_dir)
-    end
-  end
-
-  context 'when PITR file exists' do
-    let(:lsn) { '16/B374D848' }
-
-    before do
-      allow(command).to receive(:lsn_from_pitr_file).and_return(lsn)
-      allow(command).to receive(:write_geo_config_file).and_return(true)
-    end
-
-    it 'writes out the recovery settings' do
-      expect(command).to receive(:write_recovery_settings).with(lsn)
-
-      expect { command.execute }.to output(
-        /Recovery to point #{lsn} and promoting.../).to_stdout
-    end
-
-    it 'writes out the geo configuration file' do
-      expect(command).to receive(:write_geo_config_file).once
-
-      command.execute
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/geo/promote_spec.rb b/spec/gitlab-ctl-commands-ee/lib/geo/promote_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9nZW8vcHJvbW90ZV9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/geo/promote_spec.rb
+++ /dev/null
@@ -1,499 +0,0 @@
-require 'spec_helper'
-require 'geo/promote'
-require 'geo/promote_db'
-require 'gitlab_ctl/util'
-
-require_relative('../../../../files/gitlab-ctl-commands-ee/lib/patroni')
-
-RSpec.describe Geo::Promote, '#execute' do
-  let(:base_path) { '/opt/gitlab/embedded' }
-  let(:ctl) { double(base_path: base_path, data_path: '/var/opt/gitlab/postgresql/data') }
-  let(:config_path) { Dir.mktmpdir }
-  let(:gitlab_cluster_config_path) { File.join(config_path, 'gitlab-cluster.json') }
-  let(:options) { { force: false } }
-
-  subject(:command) { described_class.new(ctl, options) }
-
-  before do
-    stub_const('GitlabCluster::CONFIG_PATH', config_path)
-    stub_const('GitlabCluster::JSON_FILE', gitlab_cluster_config_path)
-
-    allow($stdin).to receive(:gets).and_return('y')
-    allow($stdout).to receive(:puts)
-    allow($stdout).to receive(:print)
-
-    allow(ctl).to receive(:log).with(any_args)
-    allow(ctl).to receive(:service_enabled?).and_return(false)
-    allow(command).to receive(:run_command).with(any_args)
-  end
-
-  around do |example|
-    example.run
-  rescue SystemExit
-  end
-
-  after do
-    FileUtils.rm_rf(config_path)
-  end
-
-  describe '#execute' do
-    context 'when not running in force mode' do
-      it 'asks for confirmation' do
-        expect { command.execute }.to output(/Are you sure you want to proceed\?/).to_stdout
-      end
-    end
-
-    context 'when running in force mode' do
-      let(:options) { { force: true } }
-
-      it 'does not ask for final confirmation' do
-        expect { command.execute }.to_not output(/Are you sure you want to proceed\?/).to_stdout
-      end
-    end
-
-    context 'when there are no services to promote' do
-      before do
-        allow(ctl).to receive(:service_enabled?).and_return(false)
-      end
-
-      it 'prints a message' do
-        expect { command.execute }.to output(
-          /No actions are required to promote this node./).to_stdout
-      end
-
-      it 'returns 0 as exit code' do
-        expect { command.execute }.to raise_error(SystemExit) do |error|
-          expect(error.status).to eq(0)
-        end
-      end
-    end
-
-    context 'when puma is enabled' do
-      before do
-        stub_service_enabled('puma')
-      end
-
-      shared_examples 'promote secondary site to primary site' do
-        context 'on a primary node' do
-          it 'does not try to promote the secondary site to primary site' do
-            stub_primary_node
-
-            allow(command).to receive(:run_reconfigure)
-            allow(command).to receive(:restart_services)
-
-            expect(command).not_to receive(:run_command).with("#{base_path}/bin/gitlab-rake geo:set_secondary_as_primary", live: true)
-
-            command.execute
-          end
-        end
-
-        context 'on a secondary node' do
-          before do
-            stub_secondary_node
-            allow(command).to receive(:run_reconfigure)
-          end
-
-          it 'promotes the secondary site to primary site' do
-            allow(command).to receive(:restart_services)
-
-            expect(command).to receive(:run_command).with("#{base_path}/bin/gitlab-rake geo:set_secondary_as_primary", live: true).once.and_return(double(error?: false))
-
-            command.execute
-          end
-
-          it 'restarts the puma service' do
-            allow(command).to receive(:promote_to_primary)
-
-            expect(ctl).to receive(:run_sv_command_for_service).with('restart', 'puma').once.and_return(double(zero?: true))
-
-            command.execute
-          end
-        end
-
-        context 'on a misconfigured node' do
-          it 'aborts promotion with an error message' do
-            stub_misconfigured_node
-
-            expect { command.execute }.to output(/Unable to detect the role of this Geo node/).to_stdout
-          end
-        end
-      end
-
-      context 'on a single-server secondary site' do
-        before do
-          stub_single_server_secondary_site
-        end
-
-        it 'toggles the Geo primary/secondary roles' do
-          allow(command).to receive(:run_reconfigure)
-          allow(command).to receive(:promote_to_primary)
-          allow(command).to receive(:restart_services)
-
-          command.execute
-
-          expect(read_file_content(gitlab_cluster_config_path)).to eq("primary" => true, "secondary" => false)
-        end
-
-        it 'runs reconfigure' do
-          allow(command).to receive(:toggle_geo_services)
-          allow(command).to receive(:promote_to_primary)
-          allow(command).to receive(:restart_services)
-
-          expect(ctl).to receive(:run_chef).with(reconfigure_cmd).once.and_return(double(success?: true))
-
-          command.execute
-        end
-
-        include_examples 'promote secondary site to primary site'
-      end
-
-      context 'on a multiple-server secondary site' do
-        before do
-          stub_multiple_server_secondary_site
-        end
-
-        include_examples 'promote secondary site to primary site'
-
-        it 'disables Geo secondary settings' do
-          allow(command).to receive(:run_reconfigure)
-          allow(command).to receive(:promote_to_primary)
-          allow(command).to receive(:restart_services)
-
-          command.execute
-
-          expect(read_file_content(gitlab_cluster_config_path)).to eq("geo_secondary" => { "enable" => false })
-        end
-
-        it 'runs reconfigure' do
-          allow(command).to receive(:promote_to_primary)
-          allow(command).to receive(:restart_services)
-
-          expect(ctl).to receive(:run_chef).with(reconfigure_cmd).once.and_return(double(success?: true))
-
-          command.execute
-        end
-      end
-    end
-
-    context 'when gitaly is enabled' do
-      before do
-        stub_service_enabled('gitaly')
-      end
-
-      it 'restarts the gitaly service' do
-        allow(command).to receive(:toggle_geo_services)
-        allow(command).to receive(:promote_to_primary)
-        allow(command).to receive(:run_reconfigure)
-
-        expect(ctl).to receive(:run_sv_command_for_service).with('restart', 'gitaly').once.and_return(double(zero?: true))
-
-        command.execute
-      end
-    end
-
-    context 'when praefect is enabled' do
-      before do
-        stub_service_enabled('praefect')
-      end
-
-      it 'restarts the praefect service' do
-        allow(command).to receive(:toggle_geo_services)
-        allow(command).to receive(:promote_to_primary)
-        allow(command).to receive(:run_reconfigure)
-
-        expect(ctl).to receive(:run_sv_command_for_service).with('restart', 'praefect').once.and_return(double(zero?: true))
-
-        command.execute
-      end
-    end
-
-    context 'when workhorse is enabled' do
-      before do
-        stub_service_enabled('gitlab-workhorse')
-      end
-
-      it 'restarts the workhorse service' do
-        allow(command).to receive(:toggle_geo_services)
-        allow(command).to receive(:promote_to_primary)
-        allow(command).to receive(:run_reconfigure)
-
-        expect(ctl).to receive(:run_sv_command_for_service).with('restart', 'gitlab-workhorse').once.and_return(double(zero?: true))
-
-        command.execute
-      end
-    end
-
-    shared_examples 'single-server secondary site' do
-      context 'on a single-server secondary site' do
-        before do
-          stub_single_server_secondary_site
-        end
-
-        it 'toggles the Geo primary/secondary roles' do
-          allow(command).to receive(:run_reconfigure)
-
-          command.execute
-
-          expect(read_file_content(gitlab_cluster_config_path)).to eq("primary" => true, "secondary" => false)
-        end
-
-        it 'runs reconfigure' do
-          expect(ctl).to receive(:run_chef).with(reconfigure_cmd).once.and_return(double(success?: true))
-
-          command.execute
-        end
-      end
-    end
-
-    shared_examples 'multiple-server secondary site' do |settings|
-      context 'on a multiple-server secondary site' do
-        before do
-          stub_multiple_server_secondary_site
-        end
-
-        it 'disables Geo secondary settings' do
-          allow(command).to receive(:run_reconfigure)
-
-          command.execute
-
-          expect(read_file_content(gitlab_cluster_config_path)).to eq(settings)
-        end
-      end
-    end
-
-    context 'when sidekiq is enabled' do
-      before do
-        stub_service_enabled('sidekiq')
-      end
-
-      include_examples 'single-server secondary site'
-      include_examples 'multiple-server secondary site', "geo_secondary" => { "enable" => false }
-    end
-
-    context 'when geo-logcursor is enabled' do
-      before do
-        allow(ctl).to receive(:service_enabled?).with('geo-logcursor').and_return(true)
-      end
-
-      include_examples 'single-server secondary site'
-      include_examples 'multiple-server secondary site', "geo_logcursor" => { "enable" => false }
-    end
-
-    context 'when geo-postgresql is enabled' do
-      before do
-        allow(ctl).to receive(:service_enabled?).with('geo-postgresql').and_return(true)
-      end
-
-      include_examples 'single-server secondary site'
-      include_examples 'multiple-server secondary site', "geo_postgresql" => { "enable" => false }
-    end
-
-    context 'when postgresql is enabled' do
-      before do
-        stub_service_enabled('postgresql')
-      end
-
-      context 'when PostgreSQL is in recovery mode' do
-        before do
-          stub_pg_is_in_recovery
-        end
-
-        it 'promotes database to begin read-write operations' do
-          allow(command).to receive(:run_reconfigure)
-
-          expect_any_instance_of(Geo::PromoteDb).to receive(:execute).once.and_return(true)
-
-          command.execute
-        end
-      end
-
-      context 'when PostgreSQL is not in recovery mode' do
-        before do
-          stub_pg_is_not_in_recovery
-        end
-
-        it 'does not promote the PostgreSQL database' do
-          allow(command).to receive(:run_reconfigure)
-
-          expect_any_instance_of(Geo::PromoteDb).not_to receive(:execute)
-
-          command.execute
-        end
-      end
-
-      it 'runs reconfigure' do
-        allow(command).to receive(:promote_database)
-
-        expect(ctl).to receive(:run_chef).with(reconfigure_cmd).once.and_return(double(success?: true))
-
-        command.execute
-      end
-    end
-
-    context 'when patroni is enabled' do
-      before do
-        stub_service_enabled('patroni')
-      end
-
-      shared_examples 'promotes a Patroni leader' do
-        context 'when PostgreSQL is in recovery mode' do
-          before do
-            stub_pg_is_in_recovery
-          end
-
-          it 'promotes database to begin read-write operations' do
-            allow(command).to receive(:disable_patroni_standby_cluster)
-            allow(command).to receive(:run_reconfigure)
-
-            expect_any_instance_of(Geo::PromoteDb).to receive(:execute).once.and_return(true)
-
-            command.execute
-          end
-        end
-
-        context 'when PostgreSQL is not in recovery mode' do
-          before do
-            stub_pg_is_not_in_recovery
-          end
-
-          it 'does not promote the PostgreSQL database' do
-            allow(command).to receive(:disable_patroni_standby_cluster)
-            allow(command).to receive(:run_reconfigure)
-
-            expect_any_instance_of(Geo::PromoteDb).not_to receive(:execute)
-
-            command.execute
-          end
-        end
-
-        it 'disables Patroni Standby cluster settings' do
-          allow(command).to receive(:promote_postgresql_read_write)
-          allow(command).to receive(:run_reconfigure)
-
-          command.execute
-
-          expect(read_file_content(gitlab_cluster_config_path)).to eq("patroni" => { "standby_cluster" => { "enable" => false } })
-        end
-
-        it 'pauses Patroni, runs reconfigure and resume Patroni' do
-          allow(command).to receive(:promote_postgresql_read_write)
-          allow(command).to receive(:disable_patroni_standby_cluster)
-
-          expect(command).to receive(:run_command).with(patroni_pause_cmd).twice.and_return(double(success?: true))
-          expect(ctl).to receive(:run_chef).with(reconfigure_cmd).twice.and_return(double(success?: true))
-          expect(command).to receive(:run_command).with(patroni_resume_cmd).twice.and_return(double(success?: true))
-
-          command.execute
-        end
-      end
-
-      context 'on a Patroni standby leader' do
-        before do
-          allow(Patroni::Client).to receive(:new).and_return(double(standby_leader?: true, leader?: false, replica?: false))
-        end
-
-        include_examples 'promotes a Patroni leader'
-      end
-
-      context 'on a Patroni leader' do
-        before do
-          allow(Patroni::Client).to receive(:new).and_return(double(standby_leader?: false, leader?: true, replica?: false))
-        end
-
-        include_examples 'promotes a Patroni leader'
-      end
-
-      context 'on a Patroni replica' do
-        before do
-          allow(Patroni::Client).to receive(:new).and_return(double(standby_leader?: false, leader?: false, replica?: true))
-        end
-
-        it 'does not promote the PostgreSQL database' do
-          allow(command).to receive(:run_reconfigure)
-
-          expect_any_instance_of(Geo::PromoteDb).not_to receive(:execute)
-
-          command.execute
-        end
-
-        it 'disables Patroni Standby cluster settings' do
-          allow(command).to receive(:promote_postgresql_read_write)
-          allow(command).to receive(:run_reconfigure)
-
-          command.execute
-
-          expect(read_file_content(gitlab_cluster_config_path)).to eq("patroni" => { "standby_cluster" => { "enable" => false } })
-        end
-
-        it 'runs reconfigure' do
-          allow(command).to receive(:promote_postgresql_read_write)
-          allow(command).to receive(:disable_patroni_standby_cluster)
-
-          expect(command).not_to receive(:run_command).with(patroni_pause_cmd)
-          expect(ctl).to receive(:run_chef).with(reconfigure_cmd).twice.and_return(double(success?: true))
-          expect(command).not_to receive(:run_command).with(patroni_resume_cmd)
-
-          command.execute
-        end
-      end
-    end
-
-    context 'when promotion succeeds' do
-      before do
-        allow(command).to receive(:ask_for_confirmation)
-        allow(command).to receive(:check_running_services)
-        allow(command).to receive(:promote_database)
-        allow(command).to receive(:toggle_geo_services)
-        allow(command).to receive(:promote_to_primary)
-        allow(command).to receive(:run_reconfigure)
-        allow(command).to receive(:restart_services)
-      end
-
-      it 'prints a success message' do
-        expect { command.execute }.to output(
-          /You successfully promoted the current node! It might take some time to reload the services, and for the changes to take effect./).to_stdout
-      end
-    end
-
-    let(:patroni_pause_cmd) { "#{base_path}/bin/gitlab-ctl patroni pause" }
-    let(:patroni_resume_cmd) { "#{base_path}/bin/gitlab-ctl patroni resume" }
-    let(:pg_is_in_recovery_cmd) { "#{base_path}/bin/gitlab-psql -c \"SELECT pg_is_in_recovery();\" -q -t" }
-    let(:reconfigure_cmd) { "#{base_path}/embedded/cookbooks/dna.json" }
-
-    def stub_service_enabled(service)
-      allow(ctl).to receive(:service_enabled?).with(service).and_return(true)
-    end
-
-    def stub_primary_node
-      allow(command).to receive(:run_command).with("#{base_path}/bin/gitlab-rake geo:site:role", live: true).and_return(double(error?: false, stdout: 'primary'))
-    end
-
-    def stub_secondary_node
-      allow(command).to receive(:run_command).with("#{base_path}/bin/gitlab-rake geo:site:role", live: true).and_return(double(error?: false, stdout: 'secondary'))
-    end
-
-    def stub_misconfigured_node
-      allow(command).to receive(:run_command).with("#{base_path}/bin/gitlab-rake geo:site:role", live: true).and_return(double(error?: true, stdout: 'misconfigured'))
-    end
-
-    def stub_single_server_secondary_site
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return('roles' => { 'geo-secondary' => { 'enable' => true } })
-    end
-
-    def stub_multiple_server_secondary_site
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return('roles' => {})
-    end
-
-    def stub_pg_is_in_recovery
-      allow(command).to receive(:run_command).with(pg_is_in_recovery_cmd, anything).and_return(double(error?: false, stdout: 't'))
-    end
-
-    def stub_pg_is_not_in_recovery
-      allow(command).to receive(:run_command).with(pg_is_in_recovery_cmd, anything).and_return(double(error?: false, stdout: 'f'))
-    end
-
-    def read_file_content(fullpath)
-      JSON.parse(File.read(fullpath))
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/geo/promote_to_primary_node_spec.rb b/spec/gitlab-ctl-commands-ee/lib/geo/promote_to_primary_node_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9nZW8vcHJvbW90ZV90b19wcmltYXJ5X25vZGVfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/geo/promote_to_primary_node_spec.rb
+++ /dev/null
@@ -1,275 +0,0 @@
-require 'spec_helper'
-require 'fileutils'
-require 'geo/promote_to_primary_node'
-require 'geo/promote_db'
-require 'geo/promotion_preflight_checks'
-require 'gitlab_ctl/util'
-
-RSpec.describe Geo::PromoteToPrimaryNode, '#execute' do
-  let(:options) { { skip_preflight_checks: true } }
-
-  let(:instance) { double(base_path: '/opt/gitlab/embedded', data_path: '/var/opt/gitlab/postgresql/data') }
-
-  subject(:command) { described_class.new(instance, options) }
-
-  let(:config_path) { Dir.mktmpdir }
-
-  before do
-    allow($stdout).to receive(:puts)
-    allow($stdout).to receive(:print)
-
-    allow(command).to receive(:run_command).with(any_args)
-  end
-
-  after do
-    FileUtils.rm_rf(config_path)
-  end
-
-  describe '#promote_postgresql_to_primary' do
-    before do
-      allow(STDIN).to receive(:gets).and_return('y')
-
-      allow(command).to receive(:toggle_geo_roles).and_return(true)
-      allow(command).to receive(:reconfigure).and_return(true)
-      allow(command).to receive(:promote_to_primary).and_return(true)
-      allow(command).to receive(:success_message).and_return(true)
-    end
-
-    it 'promotes the database' do
-      expect_any_instance_of(Geo::PromoteDb).to receive(:execute)
-
-      command.execute
-    end
-  end
-
-  describe '#run_preflight_checks' do
-    before do
-      allow(STDIN).to receive(:gets).and_return('y')
-
-      allow(command).to receive(:toggle_geo_roles).and_return(true)
-      allow(command).to receive(:promote_postgresql_to_primary).and_return(true)
-      allow(command).to receive(:reconfigure).and_return(true)
-      allow(command).to receive(:promote_to_primary).and_return(true)
-      allow(command).to receive(:success_message).and_return(true)
-    end
-
-    context 'when `--skip-preflight-checks` is passed' do
-      it 'does not run execute promotion preflight checks' do
-        expect_any_instance_of(Geo::PromotionPreflightChecks).not_to receive(:execute)
-
-        command.execute
-      end
-    end
-
-    context 'when `--skip-preflight-checks` is not passed' do
-      let(:options) { { confirm_primary_is_down: true } }
-
-      before do
-        allow_any_instance_of(Geo::PromotionPreflightChecks).to receive(
-          :execute).and_return(true)
-      end
-
-      it 'runs preflight checks' do
-        expect_any_instance_of(Geo::PromotionPreflightChecks).to receive(:execute)
-
-        command.execute
-      end
-
-      it 'passes given options to preflight checks command' do
-        expect(Geo::PromotionPreflightChecks).to receive(:new).with(
-          '/opt/gitlab/embedded', options).and_call_original
-
-        command.execute
-      end
-    end
-  end
-
-  describe '#toggle_geo_roles' do
-    let(:gitlab_cluster_config_path) { File.join(config_path, 'gitlab-cluster.json') }
-
-    before do
-      stub_const('GitlabCluster::CONFIG_PATH', config_path)
-      stub_const('GitlabCluster::JSON_FILE', gitlab_cluster_config_path)
-
-      allow(STDIN).to receive(:gets).and_return('y')
-
-      allow(command).to receive(:run_preflight_checks).and_return(true)
-      allow(command).to receive(:promote_postgresql_to_primary).and_return(true)
-      allow(command).to receive(:reconfigure).and_return(true)
-      allow(command).to receive(:promote_to_primary).and_return(true)
-      allow(command).to receive(:success_message).and_return(true)
-    end
-
-    context 'when the cluster configuration file does not exist' do
-      it 'creates the file with the Geo primary role enabled and secondary role disabled' do
-        command.execute
-
-        expect(File.exist?(gitlab_cluster_config_path)).to eq(true)
-        expect(read_file_content(gitlab_cluster_config_path)).to eq("primary" => true, "secondary" => false)
-      end
-    end
-
-    context 'when the cluster configuration file exists' do
-      it 'disables the Geo secondary role' do
-        write_file_content(gitlab_cluster_config_path, primary: false, secondary: true)
-
-        command.execute
-
-        expect(read_file_content(gitlab_cluster_config_path)).to eq("primary" => true, "secondary" => false)
-      end
-    end
-
-    def read_file_content(fullpath)
-      JSON.parse(File.read(fullpath))
-    end
-
-    def write_file_content(fullpath, content)
-      File.open(fullpath, 'w') do |f|
-        f.write(content.to_json)
-        f.chmod(0600)
-      end
-    end
-  end
-
-  context 'when preflight checks pass' do
-    before do
-      allow(STDIN).to receive(:gets).and_return('y')
-
-      allow_any_instance_of(Geo::PromotionPreflightChecks).to receive(
-        :execute).and_return(true)
-
-      allow(command).to receive(:toggle_geo_roles).and_return(true)
-      allow(command).to receive(:promote_postgresql_to_primary).and_return(true)
-      allow(command).to receive(:reconfigure).and_return(true)
-      allow(command).to receive(:promote_to_primary).and_return(true)
-      allow(command).to receive(:success_message).and_return(true)
-    end
-
-    context 'when running in force mode' do
-      let(:options) { { force: true } }
-
-      it 'does not ask for final confirmation' do
-        expect { command.execute }.not_to output(
-          /WARNING\: Secondary will now be promoted to primary./).to_stdout
-      end
-    end
-
-    context 'when not running in force mode' do
-      let(:options) { { force: false } }
-
-      it 'asks for confirmation' do
-        expect { command.execute }.to output(
-          /WARNING\: Secondary will now be promoted to primary./).to_stdout
-      end
-
-      context 'when final confirmation is given' do
-        it 'calls all the subcommands' do
-          expect(command).to receive(:toggle_geo_roles)
-          expect(command).to receive(:promote_postgresql_to_primary)
-          expect(command).to receive(:reconfigure)
-          expect(command).to receive(:promote_to_primary)
-          expect(command).to receive(:success_message)
-
-          command.execute
-        end
-      end
-    end
-  end
-
-  context 'when preflight checks fail' do
-    around do |example|
-      example.run
-    rescue SystemExit
-    end
-
-    before do
-      allow(STDIN).to receive(:gets).and_return('n')
-
-      allow(command).to receive(:promote_postgresql_to_primary).and_return(true)
-      allow(command).to receive(:reconfigure).and_return(true)
-      allow(command).to receive(:promote_to_primary).and_return(true)
-
-      allow_any_instance_of(Geo::PromotionPreflightChecks).to receive(
-        :execute).and_raise(SystemExit)
-    end
-
-    context 'when running in force mode' do
-      let(:options) { { force: true } }
-
-      it 'asks for confirmation' do
-        expect { command.execute }.to output(
-          /Are you sure you want to proceed?/
-        ).to_stdout
-      end
-
-      it 'exits with 1 if user denies' do
-        allow(STDIN).to receive(:gets).and_return('n')
-
-        expect { command.execute }.to raise_error(SystemExit) do |error|
-          expect(error.status).to eq(1)
-        end
-      end
-
-      it 'calls all the subcommands if user affirms' do
-        allow(STDIN).to receive(:gets).and_return('y')
-
-        is_expected.to receive(:toggle_geo_roles)
-        is_expected.to receive(:promote_postgresql_to_primary)
-        is_expected.to receive(:reconfigure)
-        is_expected.to receive(:promote_to_primary)
-        is_expected.to receive(:success_message)
-
-        command.execute
-      end
-    end
-
-    context 'when not running in force mode' do
-      let(:options) { { force: false } }
-
-      it 'exits with 1' do
-        expect { command.execute }.to raise_error(SystemExit)
-      end
-    end
-  end
-
-  context 'when writing to the cluster configuration file fail' do
-    around do |example|
-      example.run
-    rescue SystemExit
-    end
-
-    before do
-      allow(STDIN).to receive(:gets).and_return('y')
-
-      allow(command).to receive(:run_preflight_checks).and_return(true)
-
-      allow(GitlabCluster.config).to receive(:save).and_return(false)
-    end
-
-    it 'exits with 1' do
-      expect { command.execute }.to raise_error(SystemExit)
-    end
-  end
-
-  context 'when writing to the cluster configuration file succeed' do
-    before do
-      allow(STDIN).to receive(:gets).and_return('y')
-
-      allow(command).to receive(:promote_postgresql_to_primary).and_return(true)
-      allow(command).to receive(:reconfigure).and_return(true)
-      allow(command).to receive(:promote_to_primary).and_return(true)
-      allow(command).to receive(:success_message).and_return(true)
-
-      allow(GitlabCluster.config).to receive(:save).and_return(true)
-    end
-
-    it 'calls all the subcommands' do
-      expect(command).to receive(:promote_postgresql_to_primary)
-      expect(command).to receive(:reconfigure)
-      expect(command).to receive(:promote_to_primary)
-      expect(command).to receive(:success_message)
-
-      command.execute
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/geo/promotion_preflight_checks_spec.rb b/spec/gitlab-ctl-commands-ee/lib/geo/promotion_preflight_checks_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9nZW8vcHJvbW90aW9uX3ByZWZsaWdodF9jaGVja3Nfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/geo/promotion_preflight_checks_spec.rb
+++ /dev/null
@@ -1,145 +0,0 @@
-require 'spec_helper'
-require 'geo/promotion_preflight_checks'
-require 'gitlab_ctl/util'
-
-RSpec.describe Geo::PromotionPreflightChecks, '#execute' do
-  let(:confirmation) { 'y' }
-  let(:options) { { confirm_primary_is_down: true } }
-
-  subject(:command) { described_class.new(nil, options) }
-
-  before do
-    allow(STDIN).to receive(:gets).and_return(confirmation)
-    allow(command).to receive(:run_command).with(any_args)
-
-    shell_out_object = double.tap do |shell_out_object|
-      allow(shell_out_object).to receive(:stdout).and_return('SUCCESS')
-      allow(shell_out_object).to receive(:error?).and_return(false)
-    end
-
-    allow(command).to receive(:run_command).with(
-      'gitlab-rake gitlab:geo:check_replication_verification_status')
-      .and_return(shell_out_object)
-  end
-
-  it 'prints preflight check instructions' do
-    expect { command.execute }.to output(
-      /Ensure you have completed the following manual preflight checks/)
-      .to_stdout
-  end
-
-  context 'when manual checks are confirmed' do
-    it 'does not raise an error' do
-      expect { command.execute }.to_not raise_error
-    end
-
-    it 'runs `check_replication_verification_status` task' do
-      is_expected.to receive(:run_command).with(
-        'gitlab-rake gitlab:geo:check_replication_verification_status').once
-
-      command.execute
-    end
-
-    context 'when gitlab:geo:check_replication_verification_status fails' do
-      around do |example|
-        example.run
-      rescue SystemExit
-      end
-
-      before do
-        shell_out_object = double.tap do |shell_out_object|
-          allow(shell_out_object).to receive(:stdout).and_return('error message')
-          allow(shell_out_object).to receive(:error?).and_return(true)
-        end
-
-        allow(command).to receive(:run_command).with(
-          'gitlab-rake gitlab:geo:check_replication_verification_status')
-          .once
-          .and_return(shell_out_object)
-      end
-
-      it 'prints error message when rake task errors' do
-        expect { command.execute }.to output(
-          /error message\nERROR: Replication\/verification is incomplete./).to_stdout
-      end
-
-      it 'exits with 1 when rake task errors' do
-        expect { command.execute }.to raise_error(SystemExit) do |error|
-          expect(error.status).to eq(1)
-        end
-      end
-    end
-
-    context 'when gitlab:geo:check_replication_verification_status passes' do
-      it 'prints a success message' do
-        expect { command.execute }.to output(
-          /All preflight checks have passed. This node can now be promoted./)
-          .to_stdout
-      end
-    end
-  end
-
-  context 'when manual checks are not confirmed' do
-    let(:confirmation) { 'n' }
-
-    around do |example|
-      example.run
-    rescue SystemExit
-    end
-
-    it 'print error message' do
-      expect { command.execute }.to output(
-        /ERROR: Manual preflight checks were not performed/
-      ).to_stdout
-    end
-  end
-
-  describe 'option --confirm-primary-is-down' do
-    before do
-      allow(command).to receive(:confirm_manual_checks).and_return(true)
-    end
-
-    context 'when the option is not passed' do
-      let(:options) { {} }
-      let(:confirmation) { 'n' }
-
-      around do |example|
-        example.run
-      rescue SystemExit
-      end
-
-      it 'asks user for confirmation' do
-        expect { command.execute }.to output(
-          /Is primary down? (N\/y)/)
-          .to_stdout
-      end
-
-      it 'prints an error message when user doesn not select y/Y' do
-        expect { command.execute }.to output(
-          /ERROR: Primary node must be down./)
-          .to_stdout
-      end
-    end
-
-    context 'when the option is passed' do
-      it 'does not ask user for confirmation' do
-        expect { command.execute }.not_to output(
-          /Is primary down? (N\/y)/)
-          .to_stdout
-      end
-    end
-  end
-
-  context 'when all checks pass' do
-    before do
-      allow(command).to receive(:confirm_manual_checks).and_return(true)
-      allow(command).to receive(:confirm_primary_is_down).and_return(true)
-    end
-
-    it 'prints a success message' do
-      expect { command.execute }.to output(
-        /All preflight checks have passed. This node can now be promoted./)
-        .to_stdout
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/geo/replication_process_spec.rb b/spec/gitlab-ctl-commands-ee/lib/geo/replication_process_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9nZW8vcmVwbGljYXRpb25fcHJvY2Vzc19zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/geo/replication_process_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'spec_helper'
-
-$LOAD_PATH << './files/gitlab-ctl-commands-ee/lib'
-$LOAD_PATH << './files/gitlab-ctl-commands/lib'
-
-require 'geo/replication_process'
-require 'gitlab_ctl/util'
-
-RSpec.describe Geo::ReplicationProcess do
-  let(:error_text) { 'AN ERROR' }
-  let(:good_status) { double('Command status', error?: false) }
-  let(:bad_status) { double('Command status', error?: true, stdout: error_text) }
-  let(:pause_cmd) { %r{gitlab-psql .* -c 'SELECT pg_wal_replay_pause\(\);'} }
-  let(:resume_cmd) { %r{gitlab-psql .* -c 'SELECT pg_wal_replay_resume\(\);'} }
-  let(:instance) { double(base_path: '/opt/gitlab/embedded', data_path: '/var/opt/gitlab/postgresql/data') }
-  let(:db_name) { 'gitlab_db_name' }
-  let(:options) do
-    {
-      db_name: db_name
-    }
-  end
-
-  subject { described_class.new(instance, options) }
-
-  describe '#pause' do
-    it 'logs a message if the rake task throws an error' do
-      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-rake geo:replication:pause/).and_return(bad_status)
-      expect(GitlabCtl::Util).to receive(:run_command).with(pause_cmd).and_return(good_status)
-
-      expect do
-        subject.pause
-      end.to output(/#{error_text}/).to_stdout
-    end
-
-    it 'raises an exception if unable to pause replication' do
-      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-rake geo:replication:pause/).and_return(good_status)
-      expect(GitlabCtl::Util).to receive(:run_command).with(pause_cmd).and_return(bad_status)
-
-      expect do
-        subject.pause
-      end.to raise_error(/Unable to pause postgres replication/)
-    end
-
-    it 'provides the requested database from the options' do
-      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-rake geo:replication:pause/).and_return(good_status)
-      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-psql -d #{db_name}/).and_return(good_status)
-
-      subject.pause
-    end
-  end
-
-  describe '#resume' do
-    it 'raises an exception if unable to resume pg replication' do
-      expect(GitlabCtl::Util).to receive(:run_command).with(resume_cmd).and_return(bad_status)
-
-      expect do
-        subject.resume
-      end.to raise_error(/Unable to resume postgres replication/)
-    end
-
-    it 'raises an error if rake task to resume fails' do
-      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-rake geo:replication:resume/).and_return(bad_status)
-      expect(GitlabCtl::Util).to receive(:run_command).with(resume_cmd).and_return(good_status)
-
-      expect do
-        subject.resume
-      end.to raise_error(/Unable to resume replication from primary/)
-    end
-
-    it 'provides the requested database from the options' do
-      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-rake geo:replication:resume/).and_return(good_status)
-      expect(GitlabCtl::Util).to receive(:run_command).with(/gitlab-psql -d #{db_name}/).and_return(good_status)
-
-      subject.resume
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/geo/replication_spec.rb b/spec/gitlab-ctl-commands-ee/lib/geo/replication_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9nZW8vcmVwbGljYXRpb25fc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/geo/replication_spec.rb
+++ /dev/null
@@ -1,143 +0,0 @@
-require 'spec_helper'
-
-$LOAD_PATH << './files/gitlab-ctl-commands-ee/lib'
-$LOAD_PATH << './files/gitlab-ctl-commands/lib'
-
-require 'geo/replication'
-require 'gitlab_ctl/util'
-
-RSpec.describe Geo::Replication, '#execute' do
-  let(:command) { spy('command spy', error?: false) }
-  let(:instance) { double(base_path: '/opt/gitlab/embedded', data_path: '/var/opt/gitlab') }
-  let(:file) { spy('file spy') }
-  let(:options) do
-    {
-      now: true,
-      host: 'localhost',
-      port: 9999,
-      user: 'my-user',
-      sslmode: 'disable',
-      sslcompression: 1,
-      recovery_target_timeline: 'latest',
-      db_name: 'gitlab_db_name'
-    }
-  end
-
-  subject { described_class.new(instance, options) }
-
-  before do
-    allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'postgresql' => { 'dir' => '/var/opt/gitlab/postgresql' } })
-
-    allow(instance).to receive(:service_enabled?).and_return(true)
-    allow(STDOUT).to receive(:puts)
-    allow(subject).to receive(:print)
-    allow(File).to receive(:open).and_yield(file)
-    allow(File).to receive(:exist?).with('/var/opt/gitlab/postgresql/data/standby.signal').and_return(true)
-    allow(File).to receive(:exist?).with(anything).and_call_original
-    allow(File).to receive(:write).with('/var/opt/gitlab/postgresql/data/standby.signal', "")
-
-    allow(GitlabCtl::Util).to receive(:run_command).and_return(command)
-    allow(subject).to receive(:postgresql_user).and_return('gitlab-psql')
-    allow(subject).to receive(:postgresql_group).and_return('gitlab-psql')
-    allow(subject).to receive(:postgresql_version).and_return(12)
-  end
-
-  it 'replicates geo database' do
-    expect(subject).to receive(:ask_pass).and_return('password')
-    expect(GitlabCtl::Util).to receive(:run_command)
-      .with(%r{/embedded/bin/pg_basebackup}, anything)
-
-    subject.execute
-  end
-
-  it 'uses the db_name option' do
-    expect(subject).to receive(:ask_pass).and_return('password')
-    expect(GitlabCtl::Util).to receive(:run_command)
-      .with(%r{gitlab_db_name}, anything)
-
-    subject.execute
-  end
-
-  it 'uses the supplied postgres directory' do
-    expected_dir = '/non/default/location'
-    allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'postgresql' => { 'dir' => expected_dir } })
-
-    expect(described_class.new(instance, options).postgresql_dir_path).to eq(expected_dir)
-  end
-
-  it 'writes recovery settings to postgresql.conf and creates a standby file' do
-    allow(File).to receive(:write)
-    allow(STDIN).to receive(:gets).and_return("pass\n")
-    allow(subject).to receive(:ask_pass).and_return('password')
-
-    expect(subject).to receive(:write_recovery_settings!)
-    expect(subject).to receive(:create_standby_file!)
-
-    subject.execute
-  end
-
-  context 'when there is TTY available' do
-    before do
-      allow(STDIN).to receive(:tty?).and_return(true)
-    end
-
-    it 'asks for database password in an interactive mode' do
-      expect(STDIN).to receive(:getpass).and_return('password')
-
-      subject.execute
-    end
-  end
-
-  context 'when there is no TTY available' do
-    before do
-      allow(STDIN).to receive(:tty?).and_return(false)
-    end
-
-    it 'asks for a database password in a non-interactive mode' do
-      expect(STDIN).to receive(:gets).and_return('pass')
-
-      subject.execute
-
-      expect(file).to have_received(:write).with(/recovery_target_timeline = 'latest'/)
-    end
-
-    it 'strips the password' do
-      allow(STDIN).to receive(:gets).and_return("pass\n")
-
-      subject.execute
-
-      expect(file).to have_received(:write).with("localhost:9999:*:my-user:pass\n")
-      expect(file).to have_received(:write).with(/password=pass sslmode=disable sslcompression=1'\n/)
-    end
-  end
-
-  context 'with a custom port' do
-    let(:options) { { now: true, skip_backup: true, host: 'localhost', port: 9999, user: 'my-user', slot_name: 'foo', db_name: 'gitlab_db_name' } }
-    let(:cmd) { %q(PGPASSFILE=/var/opt/gitlab/postgresql/.pgpass /opt/gitlab/embedded/bin/gitlab-psql -h localhost -p 9999 -U my-user -d gitlab_db_name -t -c "SELECT slot_name FROM pg_create_physical_replication_slot('foo');") }
-    let(:status) { double(error?: false, stdout: '') }
-
-    it 'executes a gitlab-psql call to check replication slots' do
-      expect(subject).to receive(:check_gitlab_active?).and_return(true)
-      expect(subject).to receive(:ask_pass).and_return('mypassword')
-
-      allow(GitlabCtl::Util).to receive(:run_command).and_return(status)
-      expect(GitlabCtl::Util).to receive(:run_command).with(cmd, anything).and_return(status)
-
-      subject.execute
-    end
-  end
-
-  context 'when user has to provide a confirmation text' do
-    let(:options) { { now: false, host: 'localhost', port: 9999, user: 'my-user', slot_name: 'foo' } }
-
-    it 'asks for confirmation string' do
-      allow(subject).to receive(:ask_pass).and_return('mypass')
-      expect(STDIN).to receive(:gets).and_return('replicate')
-      expect(GitlabCtl::Util).to receive(:run_command)
-        .with("PGPASSFILE=/var/opt/gitlab/postgresql/.pgpass /opt/gitlab/embedded/embedded/bin/pg_basebackup -h localhost -p 9999 -D /var/opt/gitlab/postgresql/data -U my-user -v -P -X stream -S foo",
-              anything)
-
-      subject.execute
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/geo/replication_toggle_command_spec.rb b/spec/gitlab-ctl-commands-ee/lib/geo/replication_toggle_command_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9nZW8vcmVwbGljYXRpb25fdG9nZ2xlX2NvbW1hbmRfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/geo/replication_toggle_command_spec.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-require 'spec_helper'
-
-$LOAD_PATH << './files/gitlab-ctl-commands-ee/lib'
-$LOAD_PATH << './files/gitlab-ctl-commands/lib'
-
-require 'geo/replication_toggle_command'
-require 'geo/promote_to_primary_node'
-require 'gitlab_ctl/util'
-
-RSpec.describe Geo::ReplicationToggleCommand do
-  let(:status) { double('Command status', error?: false) }
-  let(:arguments) { [] }
-  let(:ctl_instance) { double('gitlab-ctl instance', base_path: '', data_path: 'data_path') }
-
-  before do
-    allow_any_instance_of(Geo::ReplicationToggleCommand).to receive(:current_lsn).and_return('16/B374D848')
-  end
-
-  describe 'pause' do
-    subject { described_class.new(ctl_instance, 'pause', arguments) }
-
-    it 'calls pause' do
-      expect_any_instance_of(Geo::ReplicationProcess).to receive(:pause)
-      expect(File).to receive(:write).with('data_path/postgresql/data/geo-pitr-file', '16/B374D848')
-
-      expect { subject.execute! }.to output(/Create Geo point-in-time recovery file/).to_stdout
-    end
-
-    it 'rescues and exits if postgres has an error' do
-      expect_any_instance_of(Geo::ReplicationProcess).to receive(:pause).and_raise(Geo::PsqlError, "Oh nose!")
-
-      expect do
-        expect { subject.execute! }.to raise_error(SystemExit)
-      end.to output(/Postgres encountered an error: Oh nose!/).to_stdout
-    end
-
-    context 'database specified' do
-      let(:arguments) { %w(--db_name=database_i_want) }
-
-      it 'uses the specified database' do
-        expect(Geo::ReplicationProcess).to receive(:new).with(any_args, { db_name: 'database_i_want' }).and_call_original
-        expect_any_instance_of(Geo::ReplicationProcess).to receive(:pause)
-        expect(File).to receive(:write).with('data_path/postgresql/data/geo-pitr-file', '16/B374D848')
-
-        expect { subject.execute! }.to output(/Create Geo point-in-time recovery file/).to_stdout
-      end
-    end
-  end
-
-  describe 'resume' do
-    subject { described_class.new(ctl_instance, 'resume', arguments) }
-
-    before do
-      allow(File).to receive(:delete).with('data_path/postgresql/data/geo-pitr-file').and_return(1)
-    end
-
-    it 'calls resume' do
-      expect_any_instance_of(Geo::ReplicationProcess).to receive(:resume)
-
-      expect { subject.execute! }.to output(/Remove Geo point-in-time recovery file/).to_stdout
-    end
-
-    it 'rescues and exits if postgres has an error' do
-      expect_any_instance_of(Geo::ReplicationProcess).to receive(:resume).and_raise(Geo::PsqlError, "Oh nose!")
-
-      expect do
-        expect { subject.execute! }.to raise_error(SystemExit)
-      end.to output(/Postgres encountered an error: Oh nose!/).to_stdout
-    end
-
-    it 'rescues and exits if rake has an error' do
-      expect_any_instance_of(Geo::ReplicationProcess).to receive(:resume).and_raise(Geo::RakeError, "Oh nose!")
-
-      expect do
-        expect { subject.execute! }.to raise_error(SystemExit)
-      end.to output(/Rake encountered an error: Oh nose!/).to_stdout
-    end
-
-    context 'database specified' do
-      let(:arguments) { %w(--db_name=database_i_want) }
-
-      it 'uses the specified database' do
-        expect(Geo::ReplicationProcess).to receive(:new).with(any_args, { db_name: 'database_i_want' }).and_call_original
-        expect_any_instance_of(Geo::ReplicationProcess).to receive(:resume)
-
-        expect { subject.execute! }.to output(/Remove Geo point-in-time recovery file/).to_stdout
-      end
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/patroni_spec.rb b/spec/gitlab-ctl-commands-ee/lib/patroni_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9wYXRyb25pX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/patroni_spec.rb
+++ /dev/null
@@ -1,225 +0,0 @@
-require 'spec_helper'
-require 'omnibus-ctl'
-require 'optparse'
-
-require_relative('../../../files/gitlab-ctl-commands/lib/gitlab_ctl')
-require_relative('../../../files/gitlab-ctl-commands-ee/lib/patroni')
-
-RSpec.describe 'Patroni' do
-  core_commands = %w(bootstrap check-leader check-replica check-standby-leader reinitialize-replica)
-  additional_commands = %w(members pause resume failover switchover restart reload)
-  all_commands = core_commands + additional_commands
-
-  command_lines = {
-    'bootstrap' => %w(--srcdir=SRCDIR --scope=SCOPE --datadir=DATADIR),
-    'pause' => %w(-w),
-    'resume' => %w(--wait),
-    'failover' => %w(--master MASTER --candidate CANDIDATE),
-    'switchover' => %w(--master MASTER --candidate CANDIDATE --scheduled SCHEDULED),
-    'reinitialize-replica' => %w(--wait --member MEMBER),
-    'restart' => [],
-    'reload' => []
-  }
-
-  command_options = {
-    'bootstrap' => { srcdir: 'SRCDIR', scope: 'SCOPE', datadir: 'DATADIR' },
-    'pause' => { wait: true },
-    'resume' => { wait: true },
-    'failover' => { master: 'MASTER', candidate: 'CANDIDATE' },
-    'switchover' => { master: 'MASTER', candidate: 'CANDIDATE', scheduled: 'SCHEDULED' },
-    'reinitialize-replica' => { wait: true, member: 'MEMBER' },
-    'restart' => {},
-    'reload' => {}
-
-  }
-
-  patronictl_command = {
-    'members' => 'list',
-    'pause' => 'pause -w',
-    'resume' => 'resume -w',
-    'failover' => 'failover --force --master MASTER --candidate CANDIDATE',
-    'switchover' => 'switchover --force --master MASTER --candidate CANDIDATE --scheduled SCHEDULED',
-    'restart' => 'restart --force fake-scope fake-node',
-    'reload' => 'reload --force fake-scope fake-node'
-  }
-
-  describe '.parse_options' do
-    before do
-      allow(Patroni::Utils).to receive(:warn_and_exit).and_call_original
-      allow(Kernel).to receive(:exit) { |code| raise "Kernel.exit(#{code})" }
-      allow(Kernel).to receive(:warn)
-    end
-
-    it 'should throw error when global options are invalid' do
-      expect { Patroni.parse_options(%w(patroni --foo)) }.to raise_error(OptionParser::ParseError)
-    end
-
-    it 'should throw error when sub-command is not specified' do
-      expect { Patroni.parse_options(%w(patroni -v)) }.to raise_error(OptionParser::ParseError)
-    end
-
-    it 'should throw error when sub-command is not defined' do
-      expect { Patroni.parse_options(%w(patroni -v foo)) }.to raise_error(OptionParser::ParseError)
-    end
-
-    it 'should recognize global options' do
-      expect(Patroni.parse_options(%w(patroni -v -q members))).to include(quiet: true, verbose: true)
-    end
-
-    context 'when sub-command is passed' do
-      all_commands.each do |cmd|
-        it "should parse #{cmd} options" do
-          cmd_line = command_lines[cmd] || []
-          cmd_opts = command_options[cmd] || {}
-          cmd_opts[:command] = cmd
-
-          expect(Patroni.parse_options(%W(patroni #{cmd}) + cmd_line)).to include(cmd_opts)
-        end
-      end
-    end
-
-    context 'when help option is passed' do
-      it 'should show help message and exit for global help option' do
-        expect { Patroni.parse_options(%w(patroni -h)) }.to raise_error('Kernel.exit(0)')
-        expect(Patroni::Utils).to have_received(:warn_and_exit).with(/Usage help/)
-      end
-
-      all_commands.each do |cmd|
-        it "should show help message and exit for #{cmd} help option" do
-          expect { Patroni.parse_options(%W(patroni #{cmd} -h)) }.to raise_error('Kernel.exit(0)')
-          expect(Patroni::Utils).to have_received(:warn_and_exit).with(instance_of(OptionParser))
-        end
-      end
-    end
-  end
-
-  describe 'additional commands' do
-    before do
-      allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'patroni' => { 'config_dir' => '/fake' } })
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({ 'patroni' => { 'scope' => 'fake-scope', 'name' => 'fake-node' } })
-      allow(GitlabCtl::Util).to receive(:run_command)
-    end
-
-    additional_commands.each do |cmd|
-      it "should run the relevant patronictl command for #{cmd}" do
-        Patroni.send(cmd.to_sym, command_options[cmd] || {})
-
-        expect(GitlabCtl::Util).to have_received(:run_command).with(
-          "/opt/gitlab/embedded/bin/patronictl -c /fake/patroni.yaml #{patronictl_command[cmd]}",
-          user: 'root', live: false)
-      end
-    end
-  end
-
-  describe 'command output on non-Patroni node' do
-    before do
-      allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'patroni' => nil })
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({ 'patroni' => nil })
-    end
-
-    additional_commands.each do |cmd|
-      it "should raise errors when running command #{cmd}" do
-        expect { Patroni.send(cmd.to_sym, command_options[cmd]) }.to(
-          raise_error(RuntimeError, /no Patroni configuration/)
-        )
-      end
-    end
-  end
-
-  describe '.init_db' do
-    before do
-      allow(GitlabCtl::Util).to receive(:run_command)
-    end
-
-    it 'should call initdb command with the specified options' do
-      Patroni.init_db(command_options['bootstrap'])
-
-      expect(GitlabCtl::Util).to have_received(:run_command).with('/opt/gitlab/embedded/bin/initdb -D DATADIR -E UTF8')
-    end
-  end
-
-  describe '.copy_config' do
-    before do
-      allow(FileUtils).to receive(:cp_r)
-    end
-
-    it 'should call initdb command with the specified options' do
-      Patroni.copy_config(command_options['bootstrap'])
-
-      expect(FileUtils).to have_received(:cp_r).with('SRCDIR/.', 'DATADIR')
-    end
-  end
-
-  describe '.leader?, .replica?, and .standyLeader?' do
-    before do
-      allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'patroni' => { 'api_address' => 'http://localhost:8009' } })
-      allow_any_instance_of(Patroni::Client).to receive(:get).with('/leader').and_yield(Struct.new(:code).new(leader_status))
-      allow_any_instance_of(Patroni::Client).to receive(:get).with('/replica').and_yield(Struct.new(:code).new(replica_status))
-      allow_any_instance_of(Patroni::Client).to receive(:get).with('/standby-leader').and_yield(Struct.new(:code).new(standby_leader_status))
-    end
-
-    context 'when node is leader' do
-      let(:leader_status) { '200' }
-      let(:replica_status) { '503' }
-      let(:standby_leader_status) { '503' }
-
-      it 'should identify node role' do
-        expect(Patroni.leader?({})).to be(true)
-        expect(Patroni.replica?({})).to be(false)
-        expect(Patroni.standby_leader?({})).to be(false)
-      end
-    end
-
-    context 'when node is replica' do
-      let(:leader_status) { '503' }
-      let(:replica_status) { '200' }
-      let(:standby_leader_status) { '503' }
-
-      it 'should identify node role' do
-        expect(Patroni.leader?({})).to be(false)
-        expect(Patroni.replica?({})).to be(true)
-        expect(Patroni.standby_leader?({})).to be(false)
-      end
-    end
-
-    context 'when node is standby leader' do
-      let(:leader_status) { '503' }
-      let(:replica_status) { '503' }
-      let(:standby_leader_status) { '200' }
-
-      it 'should identify node role' do
-        expect(Patroni.leader?({})).to be(false)
-        expect(Patroni.replica?({})).to be(false)
-        expect(Patroni.standby_leader?({})).to be(true)
-      end
-    end
-  end
-
-  describe '.reinitialize_replica' do
-    before do
-      allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ 'patroni' => { 'config_dir' => '/fake' } })
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({ 'patroni' => { 'scope' => 'fake-scope', 'name' => 'fake-node' } })
-      allow(GitlabCtl::Util).to receive(:run_command)
-    end
-
-    context 'when member option is set' do
-      it 'calls reinit command with the specified options to reinitialize the cluster member' do
-        Patroni.reinitialize_replica(command_options['reinitialize-replica'])
-
-        expect(GitlabCtl::Util).to have_received(:run_command).with(
-          '/opt/gitlab/embedded/bin/patronictl -c /fake/patroni.yaml reinit --force --wait fake-scope MEMBER',
-          user: 'root', live: true)
-      end
-    end
-
-    context 'when member option is not set' do
-      it 'calls reinit command with the specified options to reinitialize the current cluster member' do
-        Patroni.reinitialize_replica(command_options['reinitialize-replica'].slice(:wait))
-
-        expect(GitlabCtl::Util).to have_received(:run_command).with(
-          '/opt/gitlab/embedded/bin/patronictl -c /fake/patroni.yaml reinit --force --wait fake-scope fake-node',
-          user: 'root', live: true)
-      end
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/pgbouncer_spec.rb b/spec/gitlab-ctl-commands-ee/lib/pgbouncer_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9wZ2JvdW5jZXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/pgbouncer_spec.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-require 'spec_helper'
-
-$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands-ee/lib')
-
-require 'pgbouncer'
-
-RSpec.describe Pgbouncer::Databases do
-  let(:fake_ohai) do
-    {
-      'gitlab' => {
-        'gitlab-rails' => {
-          'db_database' => 'fake_database'
-        },
-      },
-      'pgbouncer' => {
-        'databases_ini' => '/fakedata/pgbouncer/databases.ini',
-        'databases_json' => '/fakedata/pgbouncer/databases.json'
-      }
-    }
-  end
-
-  let(:fake_databases_json) do
-    {
-      fakedb: {
-        host: 'fakehost',
-        user: 'fakeuser',
-        port: 9999,
-        password: 'dslgkjdfgklfsd'
-      }
-    }.to_json.to_s
-  end
-
-  before do
-    allow(GitlabCtl::Util).to receive(:get_fqdn).and_return('fakehost')
-    allow(Dir).to receive(:exist?).and_call_original
-    allow(Dir).to receive(:exist?).with('/fakedata/pgbouncer').and_return(true)
-    allow(File).to receive(:exist?).with('/fakedata/pgbouncer/databases.json').and_return(true)
-    allow(File).to receive(:read).and_call_original
-    allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return(fake_ohai)
-    allow(File).to receive(:read).with('/fakedata/pgbouncer/databases.json').and_return(fake_databases_json)
-    @obj = Pgbouncer::Databases.new({}, '/fakeinstall', '/fakedata')
-  end
-
-  context 'default behavior' do
-    it 'allows creating a new object' do
-      expect(@obj.class).to be(Pgbouncer::Databases)
-    end
-
-    it 'creates the database object' do
-      results = {
-        "fakedb" => "host=fakehost port=9999 auth_user=fakeuser"
-      }
-      expect(@obj.databases).to eq(results)
-    end
-
-    it 'renders the template' do
-      expect(@obj.render).to eq(
-        "[databases]\n\nfakedb = host=fakehost port=9999 auth_user=fakeuser\n\n"
-      )
-    end
-  end
-
-  context 'with options' do
-    before do
-      options = {
-        'databases_ini' => '/another/databases.ini',
-        'databases_json' => '/another/databases.json',
-        'port' => 8888,
-        'user' => 'fakeuser'
-      }
-      allow(File).to receive(:exist?).with('/another/databases.json').and_return(true)
-      allow(File).to receive(:read).with('/another/databases.json').and_return(fake_databases_json)
-      @obj = Pgbouncer::Databases.new(options, '/fakeinstall', '/fakedata')
-    end
-
-    it 'sets the custom options' do
-      expect(@obj.ini_file).to eq('/another/databases.ini')
-      expect(@obj.json_file).to eq('/another/databases.json')
-    end
-
-    it 'renders the template' do
-      expect(@obj.render).to eq(
-        "[databases]\n\nfakedb = host=fakehost port=8888 auth_user=fakeuser\n\n"
-      )
-    end
-  end
-
-  context 'with empty databases.json' do
-    before do
-      allow(File).to receive(:read).with('/fakedata/pgbouncer/databases.json').and_return({}.to_s)
-      @obj = Pgbouncer::Databases.new({}, '/fakeinstall', '/fakedata')
-    end
-
-    it 'should generate an databases.ini with sane defaults' do
-      expect(@obj.render).to eq(
-        "[databases]\n\nfake_database = \n\n"
-      )
-    end
-  end
-
-  context 'with pgbouncer listening' do
-    before do
-      allow(@obj).to receive(:show_databases).and_return("nyan")
-    end
-
-    it 'should be running' do
-      expect(@obj.running?).to be true
-    end
-  end
-
-  context 'with pgbouncer not listening' do
-    before do
-      allow(@obj).to receive(:show_databases).and_raise(GitlabCtl::Errors::ExecutionError.new("nya", "nya", "neko"))
-    end
-
-    it 'should not be running' do
-      expect(@obj.running?).to be false
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/lib/postgresql/ee_spec.rb b/spec/gitlab-ctl-commands-ee/lib/postgresql/ee_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL2xpYi9wb3N0Z3Jlc3FsL2VlX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/lib/postgresql/ee_spec.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-require 'spec_helper'
-
-$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
-$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands-ee/lib')
-
-require 'gitlab_ctl/util'
-require 'postgresql/ee'
-
-RSpec.describe GitlabCtl::PostgreSQL::EE do
-  describe "#get_primary" do
-    context 'when Consul disabled' do
-      before do
-        allow(GitlabCtl::Util).to receive(:get_node_attributes)
-          .and_return({})
-      end
-
-      it 'should raise an error' do
-        expect { described_class.get_primary } .to raise_error(/Consul agent is not enabled/)
-      end
-    end
-
-    context 'when PostgreSQL service name is not defined' do
-      before do
-        allow(GitlabCtl::Util).to receive(:get_node_attributes)
-          .and_return(
-            {
-              'consul' => {
-                'enable' => true,
-              }
-            }
-          )
-      end
-
-      it 'should raise an error' do
-        expect { described_class.get_primary } .to raise_error(/PostgreSQL service name is not defined/)
-      end
-    end
-
-    context 'when required settings are available' do
-      before do
-        allow(GitlabCtl::Util).to receive(:get_node_attributes)
-          .and_return(
-            {
-              'consul' => {
-                'enable' => true,
-              },
-              'patroni' => {
-                'scope' => 'fake'
-              }
-            }
-          )
-        allow_any_instance_of(Resolv::DNS).to receive(:getresources)
-          .with('master.fake.service.consul', Resolv::DNS::Resource::IN::SRV)
-          .and_return([Struct.new(:target, :port).new('fake.address', 6432)])
-        allow_any_instance_of(Resolv::DNS).to receive(:getaddress)
-          .with('fake.address')
-          .and_return('1.2.3.4')
-      end
-
-      it 'should get the list of PostgreSQL endpoints' do
-        expect(described_class.get_primary).to eq ['1.2.3.4:6432']
-      end
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands-ee/promote_db_spec.rb b/spec/gitlab-ctl-commands-ee/promote_db_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL3Byb21vdGVfZGJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/promote_db_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require 'geo/promote_db'
-
-RSpec.describe 'gitlab-ctl promote-db' do
-  let(:klass) { Geo::PromoteDb }
-  let(:command_name) { 'promote-db' }
-  let(:command_script) { 'promote_db' }
-
-  include_context 'ctl'
-
-  it_behaves_like 'gitlab geo promotion commands', 'promote-db'
-
-  # rubocop:disable Style/MutableConstant, Lint/ConstantDefinitionInBlock
-  it 'prints the deprecation message' do
-    # ARGV contains the commands that were passed to rspec, which are
-    # invalid for the omnibus-ctl commands
-    oldargv = ARGV
-    ARGV = []
-
-    expect_any_instance_of(klass).to receive(:execute)
-
-    expect { ctl.send(command_script) }.to output(
-      /WARNING: As of GitLab 14.5, this command is deprecated/).to_stdout
-
-    ARGV = oldargv
-  end
-  # rubocop:enable Style/MutableConstant, Lint/ConstantDefinitionInBlock
-end
diff --git a/spec/gitlab-ctl-commands-ee/promote_to_primary_node_spec.rb b/spec/gitlab-ctl-commands-ee/promote_to_primary_node_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL3Byb21vdGVfdG9fcHJpbWFyeV9ub2RlX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/promote_to_primary_node_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require 'geo/promote_to_primary_node'
-
-RSpec.describe 'gitlab-ctl promote-to-primary-node' do
-  let(:klass) { Geo::PromoteToPrimaryNode }
-  let(:command_name) { 'promote-to-primary-node' }
-  let(:command_script) { 'promote_to_primary_node' }
-
-  include_context 'ctl'
-
-  it_behaves_like 'gitlab geo promotion commands', 'promote-to-primary-node'
-
-  it_behaves_like 'geo promotion command accepts option',
-                  '--confirm-primary-is-down',
-                  { confirm_primary_is_down: true }
-
-  it_behaves_like 'geo promotion command accepts option',
-                  '--skip-preflight-checks',
-                  { skip_preflight_checks: true }
-
-  it_behaves_like 'geo promotion command accepts option',
-                  '--force',
-                  { force: true }
-
-  # rubocop:disable Style/MutableConstant, Lint/ConstantDefinitionInBlock
-  it 'prints the deprecation message' do
-    # ARGV contains the commands that were passed to rspec, which are
-    # invalid for the omnibus-ctl commands
-    oldargv = ARGV
-    ARGV = [] # rubocop:disable Style/MutableConstant
-
-    expect_any_instance_of(klass).to receive(:execute)
-
-    expect { ctl.send(command_script) }.to output(
-      /WARNING: As of GitLab 14.5, this command is deprecated/).to_stdout
-
-    ARGV = oldargv
-  end
-  # rubocop:enable Style/MutableConstant, Lint/ConstantDefinitionInBlock
-end
diff --git a/spec/gitlab-ctl-commands-ee/promotion_preflight_checks_spec.rb b/spec/gitlab-ctl-commands-ee/promotion_preflight_checks_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL3Byb21vdGlvbl9wcmVmbGlnaHRfY2hlY2tzX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/promotion_preflight_checks_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-require 'geo/promotion_preflight_checks'
-
-RSpec.shared_context 'promotion-preflight-checks' do
-  let(:command_name) { 'promotion-preflight-checks' }
-  let(:klass) { Geo::PromotionPreflightChecks }
-  let(:command_script) { 'promotion_preflight_checks' }
-end
-
-RSpec.describe 'gitlab-ctl promotion-preflight-checks' do
-  include_context 'promotion-preflight-checks'
-  include_context 'ctl'
-
-  it_behaves_like 'gitlab geo promotion commands'
-
-  it_behaves_like 'geo promotion command accepts option',
-                  '--confirm-primary-is-down',
-                  { confirm_primary_is_down: true }
-end
diff --git a/spec/gitlab-ctl-commands-ee/replicate_geo_database_spec.rb b/spec/gitlab-ctl-commands-ee/replicate_geo_database_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzLWVlL3JlcGxpY2F0ZV9nZW9fZGF0YWJhc2Vfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands-ee/replicate_geo_database_spec.rb
+++ /dev/null
@@ -1,143 +0,0 @@
-require 'spec_helper'
-require 'omnibus-ctl'
-
-RSpec.describe 'gitlab-ctl replicate-geo-database' do
-  subject { Omnibus::Ctl.new('testing-ctl') }
-
-  let(:required_arguments) do
-    %w(--host=gitlab-primary.geo
-       --slot-name=gitlab_primary_geo)
-  end
-
-  before do
-    allow_any_instance_of(Omnibus::Ctl).to receive(:require).and_call_original
-    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
-      '/opt/testing-ctl/embedded/service/omnibus-ctl-ee/lib/geo/replication'
-    ) do
-      require_relative('../../files/gitlab-ctl-commands-ee/lib/geo/replication')
-    end
-
-    subject.load_file('files/gitlab-ctl-commands-ee/replicate_geo_database.rb')
-  end
-
-  it 'appends a geo replication command' do
-    expect(subject.get_all_commands_hash).to include('replicate-geo-database')
-  end
-
-  describe "#replicate_geo_database" do
-    it 'executes the geo replication command' do
-      stub_command_arguments(required_arguments)
-
-      expect(Geo::Replication).to receive(:new).and_call_original
-        .with(subject, hash_including(host: 'gitlab-primary.geo',
-                                      slot_name: 'gitlab_primary_geo'))
-
-      expect_any_instance_of(Geo::Replication).to receive(:execute)
-
-      replicate_geo_database
-    end
-
-    it 'applies defaults to optional arguments' do
-      stub_command_arguments(required_arguments)
-
-      expect(Geo::Replication).to receive(:new).and_call_original
-        .with(subject, hash_including(host: 'gitlab-primary.geo',
-                                      slot_name: 'gitlab_primary_geo',
-                                      user: 'gitlab_replicator',
-                                      port: 5432,
-                                      password: nil,
-                                      now: false,
-                                      force: false,
-                                      skip_backup: false,
-                                      skip_replication_slot: false,
-                                      backup_timeout: 1800,
-                                      sslmode: 'verify-full',
-                                      sslcompression: 0,
-                                      recovery_target_timeline: 'latest',
-                                      db_name: 'gitlabhq_production'))
-
-      expect_any_instance_of(Geo::Replication).to receive(:execute)
-
-      replicate_geo_database
-    end
-
-    it 'requires the host argument' do
-      stub_command_arguments(%w(--slot-name=gitlab_primary_geo))
-
-      expect(Geo::Replication).not_to receive(:new)
-
-      # Important to catch this SystemExit or else RSpec exits
-      expect { subject.replicate_geo_database }.to raise_error(SystemExit).and(
-        output(/missing argument: host/).to_stdout
-      )
-    end
-
-    it 'requires the slot-name argument' do
-      stub_command_arguments(%w(--host=gitlab-primary.geo))
-
-      expect(Geo::Replication).not_to receive(:new)
-
-      # Important to catch this SystemExit or else RSpec exits
-      expect { subject.replicate_geo_database }.to raise_error(SystemExit).and(
-        output(/missing argument: --slot-name/).to_stdout
-      )
-    end
-
-    context 'with SSL compression enabled' do
-      it 'enables SSL compression' do
-        stub_command_arguments(
-          required_arguments +
-            %w(--sslmode=disable
-               --sslcompression=1
-               --no-wait))
-
-        expect(Geo::Replication).to receive(:new).and_call_original
-          .with(subject, hash_including(host: 'gitlab-primary.geo',
-                                        slot_name: 'gitlab_primary_geo',
-                                        sslmode: 'disable',
-                                        sslcompression: 1,
-                                        now: true))
-
-        expect_any_instance_of(Geo::Replication).to receive(:execute)
-
-        replicate_geo_database
-      end
-    end
-
-    context 'with db_name specified' do
-      it 'sets the db_name option' do
-        stub_command_arguments(
-          required_arguments +
-            %w(--db-name=custom_db_name))
-
-        expect(Geo::Replication).to receive(:new).and_call_original
-          .with(subject, hash_including(host: 'gitlab-primary.geo',
-                                        slot_name: 'gitlab_primary_geo',
-                                        db_name: 'custom_db_name'))
-
-        expect_any_instance_of(Geo::Replication).to receive(:execute)
-
-        replicate_geo_database
-      end
-    end
-  end
-
-  def stub_command_arguments(arguments)
-    expect_any_instance_of(Omnibus::Ctl::GeoReplicationCommand)
-      .to receive(:arguments).and_return(arguments)
-  end
-
-  def replicate_geo_database
-    # GeoReplicationCommand is designed to `exit 1` when there is a problem
-    # during option parsing. Tests that trigger this exit will also silently
-    # exit RSpec immediately, *and* they will *not* fail.
-    #
-    # So if a test expects a successful call, we *must* catch at least
-    # SystemExit for the test to be valid. And since other errors are always
-    # possible, and should also fail the test, we don't need to specify
-    # SystemExit here.
-    #
-    # In short, this is necessary for a failable test.
-    expect { subject.replicate_geo_database }.not_to raise_error
-  end
-end
diff --git a/spec/gitlab-ctl-commands/gitlab_ctl_spec.rb b/spec/gitlab-ctl-commands/gitlab_ctl_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2dpdGxhYl9jdGxfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands/gitlab_ctl_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'spec_helper'
-require 'omnibus-ctl'
-
-RSpec.describe 'gitlab-ctl' do
-  before do
-    allow_any_instance_of(Omnibus::Ctl).to receive(:require).and_call_original
-    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
-      '/opt/testing-ctl/embedded/service/omnibus-ctl/lib/gitlab_ctl'
-    ) do
-      require_relative('../../files/gitlab-ctl-commands/lib/gitlab_ctl')
-    end
-  end
-
-  let(:ctl_dir) { "#{File.dirname(__FILE__)}/../../files/gitlab-ctl-commands" }
-
-  it 'should be able to load all files' do
-    # ARGV contains the commands that were passed to rspec, which are
-    # invalid for the omnibus-ctl commands
-    oldargv = ARGV
-    ARGV = [] # rubocop:disable Style/MutableConstant
-    expect do
-      ctl = Omnibus::Ctl.new('testing-ctl')
-      ctl.load_files(@ctl_dir)
-    end.not_to raise_error
-    ARGV = oldargv
-  end
-end
diff --git a/spec/gitlab-ctl-commands/lib/gitlab_ctl_backup_spec.rb b/spec/gitlab-ctl-commands/lib/gitlab_ctl_backup_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2xpYi9naXRsYWJfY3RsX2JhY2t1cF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands/lib/gitlab_ctl_backup_spec.rb
+++ /dev/null
@@ -1,318 +0,0 @@
-require 'chef_helper'
-
-$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
-
-require 'gitlab_ctl'
-
-RSpec.describe GitlabCtl::Backup do
-  let(:backup_dir_path) { '/etc/gitlab/config_backup' }
-
-  # Valid backup files match the regular expression
-  let(:future_valid_backup_files) do
-    ['gitlab_config_2302886428_2042_12_22.tar']
-  end
-
-  let(:past_valid_backup_files) do
-    [
-      'gitlab_config_1603388428_2020_10_22.tar',
-      'gitlab_config_1606070428_2020_11_22.tar',
-      'gitlab_config_1608662428_2020_12_22.tar',
-    ]
-  end
-
-  let(:valid_backup_files) do
-    future_valid_backup_files + past_valid_backup_files
-  end
-
-  let(:invalid_backup_files) do
-    [
-      'lab_config_1600793789_2020_09_22.tar',
-      'gitlab_config_manual.tar',
-      'my_cool_backup.tar'
-    ]
-  end
-
-  let(:all_backup_files) do
-    valid_backup_files + invalid_backup_files
-  end
-
-  let(:warning_message) { "WARNING: In GitLab 14.0 we will begin removing all configuration backups older than" }
-
-  before do
-    allow(GitlabCtl::Backup).to receive(:print_warning?).and_return(false)
-    allow(File).to receive(:exist?).and_return(true)
-    allow(FileUtils).to receive(:chmod)
-    allow(FileUtils).to receive(:chown)
-    allow(FileUtils).to receive(:mkdir)
-    allow_any_instance_of(Kernel).to receive(:system).and_return(true)
-    allow_any_instance_of(Kernel).to receive(:exit!)
-    allow(Dir).to receive(:chdir).and_yield
-    allow(FileUtils).to receive(:rm)
-    allow(Time).to receive(:now).and_return(Time.utc(2021))
-    # Don't let messages output during test
-    allow(STDOUT).to receive(:write)
-  end
-
-  context 'with default settings' do
-    let(:options) { { delete_old_backups: nil } }
-
-    before do
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({})
-    end
-
-    it 'should default to deleting old backups' do
-      backup = GitlabCtl::Backup.new
-      expect(backup.wants_pruned).to eq(true)
-    end
-
-    context 'when the backup path is readable by non-root' do
-      before do
-        allow_any_instance_of(GitlabCtl::Backup).to receive(:secure?).and_return(false)
-      end
-
-      it 'should warn the administrator' do
-        expect { GitlabCtl::Backup.perform }.to output(/WARNING: #{backup_dir_path} may be read by non-root users/).to_stderr
-      end
-    end
-
-    context 'when the backup path is readable by only root' do
-      let(:archive_name) { 'gitlab_config_8675309_1981_11_16.tar' }
-
-      before do
-        allow_any_instance_of(GitlabCtl::Backup).to receive(:secure?).and_return(true)
-        allow_any_instance_of(GitlabCtl::Backup).to receive(:archive_name).and_return(archive_name)
-      end
-
-      it 'should use proper tar command' do
-        expect_any_instance_of(Kernel).to receive(:system).with(
-          *%W(tar --absolute-names --dereference --verbose --create --file /etc/gitlab/config_backup/#{archive_name}
-              --exclude /etc/gitlab/config_backup -- /etc/gitlab)
-        )
-        GitlabCtl::Backup.perform
-      end
-
-      it 'should set proper file mode on archive file' do
-        expect(FileUtils).to receive(:chmod).with(0600, %r{#{backup_dir_path}/#{archive_name}})
-        GitlabCtl::Backup.perform
-      end
-
-      it 'should notify about archive creation starting' do
-        expect { GitlabCtl::Backup.perform }.to output(
-          %r{Creating configuration backup archive: #{archive_name}}
-        ).to_stdout
-      end
-
-      it 'should put notify about archive creation completion' do
-        expect { GitlabCtl::Backup.perform }.to output(
-          %r{Configuration backup archive complete: #{backup_dir_path}/#{archive_name}}
-        ).to_stdout
-      end
-
-      it 'should be able to accept a backup directory path argument' do
-        custom_dir = "/TanukisOfUnusualSize"
-        options = { backup_path: custom_dir }
-        expect { GitlabCtl::Backup.perform(options) }.to output(
-          %r{Configuration backup archive complete: #{custom_dir}/#{archive_name}}
-        ).to_stdout
-      end
-
-      context 'when etc backup path does not exist' do
-        before do
-          allow(File).to receive(:exist?).with(backup_dir_path).and_return(false)
-        end
-
-        it 'should log proper message' do
-          expect { GitlabCtl::Backup.perform }.to output(
-            %r{Could not find '#{backup_dir_path}' directory\. Creating\.}).to_stdout
-        end
-
-        it 'should create directory' do
-          expect(FileUtils).to receive(:mkdir).with(backup_dir_path, mode: 0700)
-          GitlabCtl::Backup.perform
-        end
-
-        it 'should set proper owner and group' do
-          expect(FileUtils).to receive(:chown).with('root', 'root', backup_dir_path)
-          GitlabCtl::Backup.perform
-        end
-
-        context 'when /etc/gitlab is NFS share' do
-          before do
-            allow(STDERR).to receive(:write)
-            allow(FileUtils).to receive(:chown).with('root', 'root', backup_dir_path).and_raise(Errno::EPERM)
-          end
-
-          it 'should put proper output to STDERR' do
-            expect { GitlabCtl::Backup.perform }.to output(
-              /Warning: Could not change owner of #{backup_dir_path} to 'root:root'. As a result your backups may be accessible to some non-root users./).to_stderr
-          end
-        end
-      end
-    end
-
-    context 'when etc path does not exist' do
-      let(:etc_path) { '/etc/gitlab' }
-
-      before do
-        allow(File).to receive(:exist?).with(etc_path).and_return(false)
-      end
-
-      it "should abort with proper message" do
-        expect { GitlabCtl::Backup.perform }.to output(/Could not find '#{etc_path}' directory. Is your package installed correctly?/).to_stderr.and raise_error
-      end
-    end
-  end
-
-  context 'when backup_keep_time is non-zero' do
-    let(:options) { { delete_old_backups: true } }
-    let(:node_config) do
-      {
-        'gitlab' => {
-          'gitlab-rails' => {
-            'backup_keep_time' => 1
-          }
-        }
-      }
-    end
-
-    before do
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(node_config)
-      allow(GitlabCtl::Backup).to receive(:secure?).and_return(true)
-      @backup = GitlabCtl::Backup.new(options)
-    end
-
-    context 'when no valid files exist' do
-      before do
-        allow(Dir).to receive(:glob).and_return(invalid_backup_files)
-        allow_any_instance_of(Kernel).to receive(:warn)
-      end
-
-      it 'should find no files to remove' do
-        expect(@backup.removable_archives.length).to equal(0)
-        expect(FileUtils).not_to have_received(:rm)
-        expect { @backup.prune }.to output(/done. Removed 0 older configuration backups./).to_stdout
-      end
-    end
-
-    context 'when files exist to remove' do
-      before do
-        allow(Dir).to receive(:glob).and_return(all_backup_files)
-      end
-
-      it 'should identify the correct files to remove' do
-        removed = past_valid_backup_files.map do |x|
-          File.join(backup_dir_path, x)
-        end
-
-        expect(@backup.removable_archives).to match_array(removed)
-        @backup.remove_backups
-        removed.each do |f|
-          expect(FileUtils).to have_received(:rm).with(f)
-        end
-      end
-
-      context 'when file removal fails' do
-        let(:failed_file) { File.join(backup_dir_path, past_valid_backup_files[1]) }
-        let(:message) { "Permission denied @ unlink_internal - #{failed_file}" }
-        let(:removed_files) do
-          files = past_valid_backup_files.map do |f|
-            File.join(backup_dir_path, f)
-          end
-
-          files.select! { |f| f != failed_file }
-        end
-
-        before do
-          allow(FileUtils).to receive(:rm).with(failed_file).and_raise(Errno::EACCES, message)
-        end
-
-        it 'removes the remaining expected files' do
-          allow_any_instance_of(Kernel).to receive(:warn)
-          @backup.remove_backups
-          removed_files.each do |filename|
-            expect(FileUtils).to have_received(:rm).with(filename)
-          end
-        end
-
-        it 'sets the proper file removal count' do
-          allow_any_instance_of(Kernel).to receive(:warn)
-          expect { @backup.remove_backups }.to output(/done. Removed #{removed_files.length} older configuration backups./).to_stdout
-        end
-
-        it 'prints the error from file that could not be removed' do
-          expect { @backup.remove_backups }.to output(a_string_matching(message)).to_stderr
-        end
-      end
-    end
-
-    context 'when the only valid files are after backup_keep_time' do
-      before do
-        allow(Dir).to receive(:glob).and_return(future_valid_backup_files)
-      end
-
-      it 'should find no files to remove' do
-        allow_any_instance_of(Kernel).to receive(:warn)
-        expect(@backup.removable_archives.length).to equal(0)
-        expect(FileUtils).not_to have_received(:rm)
-        expect { @backup.prune }.to output(/done. Removed 0 older configuration backups./).to_stdout
-      end
-    end
-  end
-
-  context 'when backup_keep_time is zero' do
-    let(:options) { { delete_old_backups: nil } }
-    let(:node_config) do
-      {
-        'gitlab' => {
-          'gitlab-rails' => {
-            'backup_keep_time' => 0
-          }
-        }
-      }
-    end
-
-    before do
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(node_config)
-      allow(GitlabCtl::Backup).to receive(:secure?).and_return(true)
-      allow(Dir).to receive(:glob).and_return(all_backup_files)
-      @backup = GitlabCtl::Backup.new(options)
-    end
-
-    it 'should skip performing a backup' do
-      expect { @backup.prune }.to output(/Keeping all older configuration backups/).to_stdout
-      expect(FileUtils).not_to have_received(:rm)
-    end
-  end
-
-  context 'when node attributes invocation fails' do
-    let(:options) { { delete_old_backups: nil } }
-
-    before do
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_raise(GitlabCtl::Errors::NodeError, "Oh no, node failed")
-      allow_any_instance_of(Kernel).to receive(:warn)
-      allow(Dir).to receive(:glob).and_return(all_backup_files)
-    end
-
-    it 'should not remove old archives' do
-      backup = GitlabCtl::Backup.new(options)
-      expect { backup.prune }.to output(/Keeping all older configuration backups/).to_stdout
-      expect(FileUtils).not_to have_received(:rm)
-    end
-  end
-
-  context 'when node attributes returns an empty hash' do
-    let(:options) { { delete_old_backups: nil } }
-
-    before do
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({})
-      allow_any_instance_of(Kernel).to receive(:warn)
-      allow(Dir).to receive(:glob).and_return(all_backup_files)
-    end
-
-    it 'should not remove old archives' do
-      backup = GitlabCtl::Backup.new(options)
-      expect { backup.prune }.to output(/Keeping all older configuration backups/).to_stdout
-      expect(FileUtils).not_to have_received(:rm)
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands/lib/gitlab_ctl_pg_upgrade_spec.rb b/spec/gitlab-ctl-commands/lib/gitlab_ctl_pg_upgrade_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2xpYi9naXRsYWJfY3RsX3BnX3VwZ3JhZGVfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands/lib/gitlab_ctl_pg_upgrade_spec.rb
+++ /dev/null
@@ -1,145 +0,0 @@
-require 'spec_helper'
-
-$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
-
-require 'gitlab_ctl'
-
-def stub_postgresql_json_attributes(config = nil)
-  options = config || {}
-  options['port'] ||= '5432'
-  node_data = { postgresql: options.transform_keys(&:to_sym) }
-  public_data = { 'postgresql' => options }
-  allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(node_data)
-  allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return(public_data)
-end
-
-RSpec.describe GitlabCtl::PgUpgrade do
-  before do
-    @fake_default_dir = '/fake/data/postgresql/data'
-    allow(GitlabCtl::Util).to receive(:get_command_output).with(
-      "/fakebasedir/embedded/bin/pg_ctl --version"
-    ).and_return('fakeoldverision')
-  end
-
-  context 'with a default configuration' do
-    before do
-      stub_postgresql_json_attributes
-      @dbw = GitlabCtl::PgUpgrade.new('/fakebasedir', '/fake/data', 'fakenewversion', nil, 123)
-    end
-
-    it 'should create a new object' do
-      expect(@dbw).to be_instance_of(GitlabCtl::PgUpgrade)
-    end
-
-    it 'should allow for a custom base directory' do
-      expect(@dbw.base_path).to eq('/fakebasedir')
-    end
-
-    it 'should use the specified timeout' do
-      expect(@dbw.timeout).to eq(123)
-    end
-
-    it 'should set tmp_data_dir to data_dir if tmp_dir is nil on initialization' do
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({})
-
-      expect(@dbw.tmp_data_dir).to eq(@dbw.data_dir)
-    end
-
-    it 'should return the appropriate data version' do
-      stub_postgresql_json_attributes
-      allow(File).to receive(:read).with(
-        File.join(@fake_default_dir, 'PG_VERSION')
-      ).and_return("99.99\n")
-
-      expect(@dbw.fetch_data_version).to eq('99.99')
-    end
-
-    context 'when determining if there is enough free space to perform an upgrade' do
-      before do
-        stub_postgresql_json_attributes
-      end
-
-      it 'detects when there is not enough available disk space for upgrade' do
-        allow(GitlabCtl::Util).to receive(:get_command_output).with(
-          "du -s --block-size=1m #{@dbw.data_dir}", nil, 123
-        ).and_return("200000\n#{@dbw.data_dir}")
-
-        allow(GitlabCtl::Util).to receive(:get_command_output).with(
-          "df -P --block-size=1m #{@dbw.data_dir} | awk '{print $4}'", nil, 123
-        ).and_return("Available\n220000")
-
-        expect(@dbw.enough_free_space?(@dbw.data_dir)).to eq(false)
-      end
-
-      it 'detects when there is enough available disk space for upgrade' do
-        allow(GitlabCtl::Util).to receive(:get_command_output).with(
-          "du -s --block-size=1m #{@dbw.data_dir}", nil, 123
-        ).and_return("200000\n#{@dbw.data_dir}")
-
-        allow(GitlabCtl::Util).to receive(:get_command_output).with(
-          "df -P --block-size=1m #{@dbw.data_dir} | awk '{print $4}'", nil, 123
-        ).and_return("Available\n250000")
-
-        expect(@dbw.enough_free_space?(@dbw.data_dir)).to eq(true)
-      end
-    end
-
-    context 'when a failed upgrade attempt happened' do
-      before do
-        allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({})
-      end
-
-      let(:target_data_dir) { "future_data_dir" }
-      describe '#upgrade_artifact_exists?' do
-        it 'returns false when the directory does not exist' do
-          expect(@dbw.upgrade_artifact_exists?(target_data_dir)).to be false
-        end
-
-        it 'returns false when the directory exists with no data' do
-          allow(File).to receive(:exist?).with(target_data_dir).and_return(true)
-          allow(Dir).to receive(:empty?).with(target_data_dir).and_return(true)
-          expect(@dbw.upgrade_artifact_exists?(target_data_dir)).to be false
-        end
-
-        it 'returns true when the directory exists with data' do
-          allow(File).to receive(:exist?).with(target_data_dir).and_return(true)
-          allow(Dir).to receive(:empty?).with(target_data_dir).and_return(false)
-          expect(@dbw.upgrade_artifact_exists?(target_data_dir)).to be true
-        end
-      end
-    end
-  end
-
-  it 'should use the configured port when running pg_upgrade' do
-    stub_postgresql_json_attributes({ 'port' => '1959' })
-    @dbw = GitlabCtl::PgUpgrade.new('/fakebasedir', '/fake/data', 'fakenewversion', nil, 123)
-    expect(@dbw.port).to eq('1959')
-  end
-
-  it 'should call pg_command with the appropriate command' do
-    stub_postgresql_json_attributes({ 'username' => 'arbitrary-user-name' })
-    @dbw = GitlabCtl::PgUpgrade.new('/fakebasedir', '/fake/data', 'fakenewversion', nil, 123)
-    expect(GitlabCtl::Util).to receive(
-      :get_command_output
-    ).with('su - arbitrary-user-name -c "fake command"', nil, 123)
-    @dbw.run_pg_command('fake command')
-  end
-
-  context 'when an explicit data directory is specified' do
-    it 'should use it as data_dir' do
-      stub_postgresql_json_attributes({ 'dir' => 'randomdir' })
-      @dbw = GitlabCtl::PgUpgrade.new('/fakebasedir', '/fake/data', 'fakenewversion', nil, 123)
-
-      expect(@dbw.data_dir).to eq('randomdir/data')
-    end
-  end
-
-  context 'when an explicit data directory is not specified' do
-    it 'should use find data_dir using dir/data' do
-      stub_postgresql_json_attributes({ 'dir' => 'parentdir' })
-      @dbw = GitlabCtl::PgUpgrade.new('/fakebasedir', '/fake/data', 'fakenewversion', nil, 123)
-
-      expect(@dbw.data_dir).to eq('parentdir/data')
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands/lib/postgresql/replication_spec.rb b/spec/gitlab-ctl-commands/lib/postgresql/replication_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2xpYi9wb3N0Z3Jlc3FsL3JlcGxpY2F0aW9uX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands/lib/postgresql/replication_spec.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-require 'spec_helper'
-$LOAD_PATH << File.join(__dir__, '../../../../files/gitlab-ctl-commands/lib')
-
-require 'postgresql/replication'
-
-RSpec.describe PostgreSQL::Replication do
-  let(:ctl) { spy('gitlab ctl') }
-  let(:util) { spy('gitlab ctl util', error?: false) }
-  let(:attributes) { spy('node attributes spy') }
-
-  subject { described_class.new(ctl) }
-
-  before do
-    stub_const('GitlabCtl::Util', util)
-
-    allow(util).to receive(:get_node_attributes)
-      .and_return(attributes)
-  end
-
-  it 'asks for the password' do
-    expect(subject).to receive(:ask_password).and_return('pass').once
-
-    subject.set_password!
-  end
-
-  it 'sets a password' do
-    allow(subject).to receive(:ask_password).and_return('mypass')
-
-    subject.set_password!
-
-    expect(util).to have_received(:run_command)
-      .with(%r{WITH ENCRYPTED PASSWORD 'mypass'})
-  end
-
-  context 'when postgresql is enabled and patroni disabled' do
-    before do
-      allow(ctl).to receive(:service_enabled?).with('postgresql').and_return(true)
-      allow(ctl).to receive(:service_enabled?).with('patroni').and_return(false)
-    end
-
-    it 'sets a password for user defined in `sql_replication_user` attribute' do
-      allow(subject).to receive(:ask_password).and_return('mypass')
-      allow(attributes).to receive(:to_s).and_return('myuser')
-
-      subject.set_password!
-
-      expect(util).to have_received(:run_command)
-        .with(%r{ALTER USER myuser WITH ENCRYPTED PASSWORD 'mypass'})
-    end
-  end
-
-  it 'raises an error if replication user is not configured' do
-    allow(subject).to receive(:ask_password).and_return('mypass')
-    allow(attributes).to receive(:dig).and_return(nil)
-
-    expect { subject.set_password! }.to raise_error(ArgumentError)
-  end
-
-  context 'when postgresql is disabled and patroni enabled' do
-    before do
-      allow(ctl).to receive(:service_enabled?).with('postgresql').and_return(false)
-      allow(ctl).to receive(:service_enabled?).with('patroni').and_return(true)
-    end
-
-    it 'sets a password for user defined in `sql_replication_user` attribute' do
-      allow(subject).to receive(:ask_password).and_return('mypass')
-      allow(attributes).to receive(:to_s).and_return('myuser')
-
-      subject.set_password!
-
-      expect(util).to have_received(:run_command)
-        .with(%r{ALTER USER myuser WITH ENCRYPTED PASSWORD 'mypass'})
-    end
-  end
-
-  context 'when postgresql and patroni services are disabled' do
-    around do |example|
-      example.run
-    rescue SystemExit
-    end
-
-    before do
-      allow(ctl).to receive(:service_enabled?).with('postgresql').and_return(false)
-      allow(ctl).to receive(:service_enabled?).with('patroni').and_return(false)
-    end
-
-    it 'prints an error message' do
-      expect { subject.set_password! }.to output(
-        /There is no PostgreSQL instance enabled in Omnibus/).to_stdout
-    end
-
-    it 'exits with 1' do
-      expect { subject.set_password! }.to raise_error(SystemExit) do |error|
-        expect(error.status).to eq(1)
-      end
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands/lib/postgresql_spec.rb b/spec/gitlab-ctl-commands/lib/postgresql_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2xpYi9wb3N0Z3Jlc3FsX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands/lib/postgresql_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require 'spec_helper'
-
-$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
-
-require 'postgresql'
-
-RSpec.describe GitlabCtl::PostgreSQL do
-  describe "#postgresql_username" do
-    before do
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(
-        {
-          'postgresql' => {
-            'username' => 'bar'
-          }
-        }
-      )
-    end
-    it 'detects username correctly' do
-      expect(described_class.postgresql_username).to eq('bar')
-    end
-  end
-
-  describe '#postgresql_group' do
-    before do
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(
-        {
-          'postgresql' => {
-            'group' => 'foo'
-          }
-        }
-      )
-    end
-
-    it 'returns the correct group' do
-      expect(described_class.postgresql_group).to eq('foo')
-    end
-  end
-
-  describe '#postgresql_version' do
-    before do
-      allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(
-        {
-          'postgresql' => {
-            'dir' => '/var/opt/gitlab'
-          }
-        }
-      )
-    end
-
-    context 'when PG_VERSION file exists' do
-      before do
-        allow(File).to receive(:exist?).and_return(true)
-        allow(File).to receive(:read).and_return('12\n')
-      end
-
-      it 'returns the version' do
-        expect(described_class.postgresql_version).to eq(12)
-      end
-    end
-
-    context 'when PG_VERSION file exists' do
-      before do
-        allow(File).to receive(:exist?).and_return(false)
-      end
-
-      it 'returns nil' do
-        expect(described_class.postgresql_version).to be_nil
-      end
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands/lib/praefect_spec.rb b/spec/gitlab-ctl-commands/lib/praefect_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2xpYi9wcmFlZmVjdF9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands/lib/praefect_spec.rb
+++ /dev/null
@@ -1,207 +0,0 @@
-require 'spec_helper'
-require 'optparse'
-
-require_relative('../../../files/gitlab-ctl-commands/lib/praefect')
-
-RSpec.describe Praefect do
-  describe '.parse_options!' do
-    before do
-      allow(Kernel).to receive(:exit) { |code| raise "Kernel.exit(#{code})" }
-    end
-
-    shared_examples 'unknown option is specified' do
-      it 'throws an error' do
-        expect { Praefect.parse_options!(%W(praefect #{command} --unknown)) }.to raise_error(OptionParser::InvalidOption, /unknown/)
-      end
-    end
-
-    it 'throws an error when command is not specified' do
-      expect { Praefect.parse_options!(%w(praefect)) }.to raise_error(OptionParser::ParseError, /Praefect command is not specified/)
-    end
-
-    it 'throws an error when unknown command is specified' do
-      expect { Praefect.parse_options!(%w(praefect unknown-command)) }.to raise_error(OptionParser::ParseError, /Unknown Praefect command: unknown-command/)
-    end
-
-    shared_examples 'parses repository options' do
-      it 'throws an error when an argument for --virtual-storage-name is not specified' do
-        expect { Praefect.parse_options!(%W(praefect #{command} --dir dir --virtual-storage-name)) }.to raise_error(OptionParser::MissingArgument, /virtual-storage-name/)
-      end
-
-      it 'throws an error when --virtual-storage-name is not specified' do
-        expect { Praefect.parse_options!(%W(praefect #{command} --dir dir)) }.to raise_error(OptionParser::ParseError, /Option --virtual-storage-name must be specified/)
-      end
-
-      it 'throws an error when an argument for --repository-relative-path is not specified' do
-        expect { Praefect.parse_options!(%W(praefect #{command} --dir dir --repository-relative-path)) }.to raise_error(OptionParser::MissingArgument, /repository-relative-path/)
-      end
-
-      it 'throws an error when --repository-relative-path is not specified' do
-        expect { Praefect.parse_options!(%W(praefect #{command} --dir dir --virtual-storage-name name)) }.to raise_error(OptionParser::ParseError, /Option --repository-relative-path must be specified/)
-      end
-
-      it 'successfully parses correct params' do
-        expected_options = { command: command, dir: 'dir', virtual_storage_name: 'name', repository_relative_path: 'path' }
-
-        expect(Praefect.parse_options!(%W(praefect #{command} --dir dir --virtual-storage-name name --repository-relative-path path))).to eq(expected_options)
-      end
-    end
-
-    context 'when command is remove-repository' do
-      let(:command) { 'remove-repository' }
-
-      it_behaves_like 'parses repository options'
-      it_behaves_like 'unknown option is specified'
-
-      it 'successfully parses apply' do
-        expect(Praefect.parse_options!(%W(praefect #{command}
-                                          --dir dir
-                                          --virtual-storage-name name
-                                          --repository-relative-path path
-                                          --apply))).to include(apply: true)
-
-        expect(Praefect.parse_options!(%W(praefect #{command}
-                                          --dir dir
-                                          --virtual-storage-name name
-                                          --repository-relative-path path
-                                          --apply something))).to include(apply: true)
-
-        expect(Praefect.parse_options!(%W(praefect #{command}
-                                          --dir dir
-                                          --virtual-storage-name name
-                                          --repository-relative-path path))).not_to include(:apply)
-      end
-    end
-
-    context 'when command is check' do
-      let(:command) { 'check' }
-
-      it_behaves_like 'unknown option is specified'
-    end
-
-    context 'when command is track-repository' do
-      let(:command) { 'track-repository' }
-
-      it_behaves_like 'parses repository options'
-      it_behaves_like 'unknown option is specified'
-
-      it 'successfully parses authoritative-storage' do
-        expected_options = { command: command,
-                             dir: 'dir',
-                             virtual_storage_name: 'name',
-                             repository_relative_path: 'path',
-                             authoritative_storage: 'storage-1',
-                             replicate_immediately: true }
-
-        expect(Praefect.parse_options!(%W(praefect #{command}
-                                          --dir dir
-                                          --virtual-storage-name name
-                                          --repository-relative-path path
-                                          --authoritative-storage storage-1
-                                          --replicate-immediately))).to eq(expected_options)
-      end
-    end
-
-    context 'when help option is passed' do
-      it 'shows help message and exit for global help option' do
-        expect(Kernel).to receive(:puts) do |msg|
-          expect(msg.to_s).to match(/gitlab-ctl praefect command/)
-        end
-
-        expect { Praefect.parse_options!(%w(praefect -h)) }.to raise_error('Kernel.exit(0)')
-      end
-
-      ['remove-repository', 'track-repository'].each do |cmd|
-        it "shows help message and exit for #{cmd} help option" do
-          expect(Kernel).to receive(:puts) do |msg|
-            expect(msg.to_s).to match(/gitlab-ctl praefect #{cmd}/)
-          end
-
-          expect { Praefect.parse_options!(%W(praefect #{cmd} -h)) }.to raise_error('Kernel.exit(0)')
-        end
-      end
-    end
-  end
-
-  describe '#execute' do
-    let(:repository_options) { { virtual_storage_name: 'storage-name', repository_relative_path: 'repository-path' } }
-
-    context 'when package is not installed correctly' do
-      it 'aborts the execution if a path does not exit' do
-        allow(File).to receive(:exists?).twice.and_return(false)
-        expect(Kernel).to receive(:abort).and_raise('aborted')
-
-        expect { Praefect.execute({}) }.to raise_error('aborted')
-      end
-    end
-
-    shared_examples 'executes the command' do
-      it 'successfully executes' do
-        allow(File).to receive(:exist?).and_return(true)
-        expect(Kernel).not_to receive(:abort)
-        args = [
-          Praefect::EXEC_PATH, '-config', 'dir/config.toml', command,
-        ]
-
-        args += command_args
-
-        expect(Kernel).to receive(:system).with(*args).and_return('result!')
-        expect(Kernel).not_to receive(:exit!)
-
-        common_options = {
-          command: command,
-          dir: 'dir'
-        }
-
-        Praefect.execute(common_options.merge(command_options))
-      end
-    end
-
-    context 'check command' do
-      let(:command) { 'check' }
-      let(:command_args) { [] }
-      let(:command_options) { {} }
-
-      it_behaves_like 'executes the command'
-    end
-
-    context 'remove-repository command' do
-      let(:command) { 'list-untracked-repositories' }
-      let(:command_args) { [] }
-      let(:command_options) { {} }
-
-      it_behaves_like 'executes the command'
-    end
-
-    context 'repository commands' do
-      let(:command_args) { ['-virtual-storage', 'storage-name', '-repository', 'repository-path'] }
-      let(:command_options) { repository_options }
-
-      context 'remove-repository command' do
-        let(:command) { 'remove-repository' }
-
-        it_behaves_like 'executes the command'
-      end
-
-      context 'track-repository command' do
-        let(:command) { 'track-repository' }
-        let(:command_args) { ['-virtual-storage', 'storage-name', '-repository', 'repository-path', '-authoritative-storage', 'storage-1'] }
-        let(:command_options) { repository_options.merge(authoritative_storage: 'storage-1') }
-
-        it_behaves_like 'executes the command'
-
-        context 'with replicate-immediately' do
-          let(:command_args) do
-            ['-virtual-storage', 'storage-name',
-             '-repository', 'repository-path',
-             '-authoritative-storage', 'storage-1',
-             '-replicate-immediately']
-          end
-          let(:command_options) { repository_options.merge(authoritative_storage: 'storage-1', replicate_immediately: true) }
-
-          it_behaves_like 'executes the command'
-        end
-      end
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands/lib/upgrade_check_spec.rb b/spec/gitlab-ctl-commands/lib/upgrade_check_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2xpYi91cGdyYWRlX2NoZWNrX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands/lib/upgrade_check_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'chef_helper'
-
-$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
-
-require 'gitlab_ctl/upgrade_check'
-
-RSpec.describe GitlabCtl::UpgradeCheck do
-  let(:latest) { '13.1.3' }
-  let(:previous_major) { '12.5.10' }
-  let(:previous_major_latest) { '12.10.13' }
-  let(:current_major) { '13.0.8' }
-  let(:old) { '11.5.11' }
-
-  context 'not an upgrade' do
-    it 'returns true' do
-      expect(described_class.valid?(nil, latest)).to be true
-    end
-  end
-
-  context 'valid upgrade paths' do
-    it 'returns true for an upgrade from the current major version' do
-      expect(described_class.valid?(current_major, latest)).to be true
-    end
-  end
-
-  context 'invalid upgrade paths' do
-    it 'returns false for an upgrade from 11.5 to the latest' do
-      expect(described_class.valid?(old, latest)).to be false
-    end
-
-    it 'returns false for an upgrade from 12.5 to the latest' do
-      expect(described_class.valid?(previous_major, latest)).to be false
-    end
-
-    it 'returns false upgrading from previous_major_latest to the latest' do
-      expect(described_class.valid?(previous_major_latest, latest)).to be false
-    end
-  end
-end
diff --git a/spec/gitlab-ctl-commands/lib/util_spec.rb b/spec/gitlab-ctl-commands/lib/util_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2xpYi91dGlsX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/gitlab-ctl-commands/lib/util_spec.rb
+++ /dev/null
@@ -1,262 +0,0 @@
-require 'spec_helper'
-
-$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
-
-require 'gitlab_ctl'
-
-RSpec.describe GitlabCtl::Util do
-  context 'when there is no TTY available' do
-    before do
-      allow(STDIN).to receive(:tty?).and_return(false)
-    end
-
-    it 'asks for a database password in a non-interactive mode' do
-      allow(STDIN).to receive(:gets).and_return('pass')
-
-      expect(described_class.get_password).to eq('pass')
-    end
-
-    it 'strips a new line character from the password' do
-      allow(STDIN).to receive(:tty?).and_return(false)
-      allow(STDIN).to receive(:gets).and_return("mypass\n")
-
-      expect(described_class.get_password).to eq('mypass')
-    end
-  end
-
-  context 'when there is TTY available' do
-    before do
-      allow(STDIN).to receive(:tty?).and_return(true)
-    end
-
-    it 'asks for confirmation' do
-      expect(STDIN).to receive(:getpass).twice.and_return("mypass")
-
-      expect(described_class.get_password(do_confirm: true)).to eq('mypass')
-    end
-
-    it 'skips confirmation' do
-      expect(STDIN).to receive(:getpass).and_return("mypass")
-
-      expect(described_class.get_password(do_confirm: false)).to eq('mypass')
-    end
-  end
-
-  describe '#roles' do
-    let(:fake_base_path) { '/foo' }
-    let(:fake_hostname) { 'fakehost.fakedomain' }
-    let(:fake_node_file) { "#{fake_base_path}/embedded/nodes/#{fake_hostname}.json" }
-
-    before do
-      hostname = double('hostname')
-      allow(hostname).to receive(:stdout).and_return(fake_hostname)
-      allow(GitlabCtl::Util).to receive(:run_command).with('hostname -f').and_return(hostname)
-      allow(File).to receive(:exist?).with(fake_node_file).and_return(true)
-    end
-
-    it 'returns an empty list when no roles are defined' do
-      empty_node = {
-        normal: {
-          roles: {}
-        }
-      }
-      allow(File).to receive(:read).with(fake_node_file).and_return(empty_node.to_json)
-      expect(described_class.roles(fake_base_path)).to eq([])
-    end
-
-    it 'returns a list of roles that are defined' do
-      roled_node = {
-        normal: {
-          roles: {
-            role_one: {
-              enable: true
-            },
-            role_two: {
-              enable: false
-            },
-            role_three: {
-              enable: true
-            }
-          }
-        }
-      }
-      allow(File).to receive(:read).with(fake_node_file).and_return(roled_node.to_json)
-      expect(described_class.roles(fake_base_path)).to eq(%w[role_one role_three])
-    end
-
-    it 'when no roles are defined' do
-      roled_node = {}
-      allow(File).to receive(:read).with(fake_node_file).and_return(roled_node.to_json)
-
-      expect(described_class.roles(fake_base_path)).to eq([])
-    end
-
-    it 'when roles is not a Hash' do
-      roled_node = { roles: "test" }
-      allow(File).to receive(:read).with(fake_node_file).and_return(roled_node.to_json)
-
-      expect(described_class.roles(fake_base_path)).to eq([])
-    end
-  end
-
-  describe '#parse_json_file' do
-    it 'fails on malformed JSON file' do
-      malformed_json = <<~MSG
-      {
-      'foo': 'bar'
-      MSG
-      allow(File).to receive(:read).with('/tmp/foo').and_return(malformed_json)
-
-      expect { GitlabCtl::Util.parse_json_file('/tmp/foo') }.to raise_error(GitlabCtl::Errors::NodeError, "Error reading /tmp/foo, has reconfigure been run yet?")
-    end
-
-    it 'do not fail on empty json file' do
-      allow(File).to receive(:read).with('/tmp/foo').and_return('{}')
-
-      expect(GitlabCtl::Util.parse_json_file('/tmp/foo')).to eq({})
-    end
-
-    it 'fails on incomplete but valid node attribute file' do
-      incomplete_node_attributes = <<~MSG
-      {
-        "name": "a-random-server"
-      }
-      MSG
-      allow(File).to receive(:read).with('/opt/gitlab/embedded/nodes/12345.json').and_return(incomplete_node_attributes)
-
-      expect { GitlabCtl::Util.parse_json_file('/opt/gitlab/embedded/nodes/12345.json') }.to raise_error(GitlabCtl::Errors::NodeError, "Attributes not found in /opt/gitlab/embedded/nodes/12345.json, has reconfigure been run yet?")
-    end
-  end
-
-  describe '#parse_duration' do
-    it 'should raise error for nil, empty, or malformed inputs' do
-      expect { GitlabCtl::Util.parse_duration(nil) }.to raise_error ArgumentError, 'invalid value for duration: ``'
-      expect { GitlabCtl::Util.parse_duration('') }.to raise_error ArgumentError, 'invalid value for duration: ``'
-      expect { GitlabCtl::Util.parse_duration('foo') }.to raise_error ArgumentError, 'invalid value for duration: `foo`'
-      expect { GitlabCtl::Util.parse_duration('123foo') }.to raise_error ArgumentError, 'invalid value for duration: `123foo`'
-      expect { GitlabCtl::Util.parse_duration('foo123') }.to raise_error ArgumentError, 'invalid value for duration: `foo123`'
-    end
-
-    it 'should parse unformatted inputs into milliseconds' do
-      expect(GitlabCtl::Util.parse_duration('123')).to eq(123)
-      expect(GitlabCtl::Util.parse_duration('123.456')).to eq(123)
-    end
-
-    it 'should recognize and parse different duration units' do
-      expect(GitlabCtl::Util.parse_duration('123.456ms')).to eq(123)
-      expect(GitlabCtl::Util.parse_duration('123.456s')).to eq(123.456 * 1000)
-      expect(GitlabCtl::Util.parse_duration('123.456m')).to eq(123.456 * 1000 * 60)
-      expect(GitlabCtl::Util.parse_duration('123.456h')).to eq(123.456 * 1000 * 60 * 60)
-      expect(GitlabCtl::Util.parse_duration('123.456d')).to eq(123.456 * 1000 * 60 * 60 * 24)
-    end
-
-    it 'should parse mixed unit inputs in any order' do
-      expect(GitlabCtl::Util.parse_duration('1.1d2.2h3.3m4.4s5.5ms')).to eq(
-        1.1 * 1000 * 60 * 60 * 24 +
-        2.2 * 1000 * 60 * 60 +
-        3.3 * 1000 * 60 +
-        4.4 * 1000 +
-        5
-      )
-      expect(GitlabCtl::Util.parse_duration('5.5ms4.4s3.3m2.2h1.1d')).to eq(
-        1.1 * 1000 * 60 * 60 * 24 +
-        2.2 * 1000 * 60 * 60 +
-        3.3 * 1000 * 60 +
-        4.4 * 1000 +
-        5
-      )
-    end
-
-    it 'should break and return when input is partially valid' do
-      expect(GitlabCtl::Util.parse_duration('1h2m3foo')).to eq(
-        1 * 1000 * 60 * 60 +
-        2 * 1000 * 60
-      )
-      expect(GitlabCtl::Util.parse_duration('1h2m3')).to eq(
-        1 * 1000 * 60 * 60 +
-        2 * 1000 * 60
-      )
-    end
-  end
-
-  describe '#chef_run' do
-    it 'should accept and use custom log files' do
-      allow(described_class).to receive(:run_command).and_return('success')
-      expect(described_class).to receive(:run_command).with(/-L log_ago_and_far_away/)
-      described_class.chef_run('config', 'attributes', 'log_ago_and_far_away')
-    end
-
-    it 'should not pass the log file path unless set' do
-      allow(described_class).to receive(:run_command).and_return('success')
-      expect(described_class).not_to receive(:run_command).with(/ -L /)
-      described_class.chef_run('config', 'attributes')
-    end
-  end
-
-  describe '#get_node_attributes' do
-    context 'when node file is missing' do
-      before do
-        allow(File).to receive(:exist?).with(%r{/opt/gitlab/embedded/nodes.*json}).and_return(false)
-        allow(Dir).to receive(:glob?).with(%r{/opt/gitlab/embedded/}).and_return([])
-      end
-
-      it 'raises an error' do
-        expect { GitlabCtl::Util.get_node_attributes }.to raise_error(GitlabCtl::Errors::NodeError, "Node attributes JSON file not found in /opt/gitlab/embedded/nodes, has reconfigure been run yet?")
-      end
-    end
-  end
-
-  describe "#public_attributes_missing?" do
-    context 'when the public attributes file is missing' do
-      before do
-        allow(File).to receive(:exist?).with(%r{/var/opt/gitlab/public_attributes.json}).and_return(false)
-      end
-
-      it 'returns true' do
-        expect(GitlabCtl::Util.public_attributes_missing?).to be true
-      end
-    end
-  end
-
-  describe "#public_attributes_broken?" do
-    context 'when the public attributes file is broken' do
-      before do
-        allow(File).to receive(:exist?).with(%r{/var/opt/gitlab/public_attributes.json}).and_return(true)
-      end
-
-      it 'returns true when the attribute is missing' do
-        allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({})
-        expect(GitlabCtl::Util.public_attributes_broken?).to be true
-      end
-
-      it 'returns false by default when the gitlab attribute is found' do
-        allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ "gitlab" => "hithere" })
-        expect(GitlabCtl::Util.public_attributes_broken?).to be false
-      end
-
-      context 'when using an alternate key to check for broken' do
-        it 'returns true when the key is missing' do
-          allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({})
-          expect(GitlabCtl::Util.public_attributes_broken?('funky')).to be true
-        end
-
-        it 'returns false when the key is present' do
-          allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ "funky" => "llama" })
-          expect(GitlabCtl::Util.public_attributes_broken?('funky')).to be false
-        end
-      end
-    end
-  end
-
-  describe '#get_public_node_attributes' do
-    context 'when node file is missing' do
-      before do
-        allow(GitlabCtl::Util).to receive(:public_attributes_missing?).and_return(true)
-      end
-
-      it 'returns empty hash' do
-        expect(GitlabCtl::Util.get_public_node_attributes).to eq({})
-      end
-    end
-  end
-end
diff --git a/spec/libraries/config_mash_spec.rb b/spec/libraries/config_mash_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvY29uZmlnX21hc2hfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/libraries/config_mash_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-require 'spec_helper'
-require_relative '../../files/gitlab-cookbooks/package/libraries/config_mash'
-
-RSpec.describe Gitlab::ConfigMash do
-  subject(:mash) { described_class.new }
-
-  describe '.auto_vivify?' do
-    subject { described_class }
-
-    it 'returns true when inside auto_vivify block' do
-      subject.auto_vivify do
-        expect(subject.auto_vivify?).to be_truthy
-      end
-    end
-
-    it 'returns false when not in auto_vivify block' do
-      expect(subject.auto_vivify?).to be_falsey
-    end
-  end
-
-  describe '#[]' do
-    it 'behaves like a normal Mash by default' do
-      mash['param1'] = 'value1'
-      expect(mash['param1']).to eq 'value1'
-      expect(mash['param2']).to be_nil
-      expect { mash['param2']['nested'] }.to raise_error(NoMethodError, /nil:NilClass/)
-    end
-
-    it 'allows nested undefined reads when in an auto-vivify block' do
-      mash['param1'] = 'value1'
-      expect(mash['param1']).to eq 'value1'
-      expect(mash['param2']).to be_nil
-
-      Gitlab::ConfigMash.auto_vivify do
-        expect(mash['param2']).not_to be_nil
-        expect { mash['param2']['nested'] }.not_to raise_error
-        mash['param3']['enable'] = true
-        expect(mash['param3']['enable']).to eq true
-      end
-
-      # confirm it is turned off after the block
-      expect(mash['param4']).to be_nil
-    end
-  end
-
-  describe 'convert_value' do
-    it 'returns passed value when its a ConfigMash' do
-      another_mash = Gitlab::ConfigMash.new
-
-      converted = mash.convert_value(another_mash)
-
-      expect(converted).to be_a(Gitlab::ConfigMash)
-      expect(converted).to eq(another_mash)
-    end
-
-    it 'returns a ConfigMash with the same data when passed a Hash' do
-      hash = { 'key' => 'value' }
-
-      converted = mash.convert_value(hash)
-
-      expect(converted).to be_a(Gitlab::ConfigMash)
-      expect(converted.keys).to match_array('key')
-      expect(converted.values).to match_array('value')
-    end
-  end
-end
diff --git a/spec/libraries/deprecations_spec.rb b/spec/libraries/deprecations_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvZGVwcmVjYXRpb25zX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/libraries/deprecations_spec.rb
+++ /dev/null
@@ -1,222 +0,0 @@
-require 'spec_helper'
-require_relative '../../files/gitlab-cookbooks/package/libraries/deprecations'
-
-RSpec.describe Gitlab::Deprecations do
-  let(:valid_config) { { gitlab: { nginx: { listen_addresses: "SomeRandomString" } } } }
-  let(:invalid_config) do
-    {
-      "gitlab" => {
-        "nginx" => {
-          "listen_address" => "SomeRandomString"
-        },
-        "gitlab-rails" => {
-          "stuck_ci_builds_worker_cron" => "5 * * * *"
-        }
-      },
-      "mattermost" => {
-        "system_read_timeout" => 50,
-        "log_file_directory" => "/my/random/path"
-      },
-      "monitoring" => {
-        "gitlab-monitor" => {
-          "enable" => false
-        }
-      }
-    }
-  end
-
-  let(:conf1) do
-    {
-      config_keys: %w(gitlab nginx listen_address),
-      deprecation: '8.10',
-      removal: '11.0',
-      note: "Use nginx['listen_addresses'] instead."
-    }
-  end
-
-  let(:conf2) do
-    {
-      config_keys: %w(gitlab gitlab-rails stuck_ci_builds_worker_cron),
-      deprecation: '9.0',
-      removal: '12.0',
-      note: "Use gitlab_rails['stuck_ci_jobs_worker_cron'] instead."
-    }
-  end
-
-  let(:conf3) do
-    {
-      config_keys: %w(gitlab gitlab-shell git_data_directories),
-      deprecation: '8.10',
-      removal: '11.0',
-      note: "Use git_data_dirs instead."
-    }
-  end
-
-  let(:conf4) do
-    {
-      config_keys: %w(monitoring gitlab-monitor enable),
-      deprecation: '12.0',
-      removal: '13.0',
-      note: "Use gitlab_exporter['enable'] instead."
-    }
-  end
-
-  let(:deprecation_list) do
-    [
-      conf1,
-      conf2,
-      conf3,
-      conf4,
-    ]
-  end
-
-  before(:each) do
-    allow(Gitlab::Deprecations).to receive(:list).and_return(deprecation_list)
-  end
-
-  describe '.applicable_deprecations' do
-    it 'detects valid configuration' do
-      expect(described_class.applicable_deprecations("11.0", valid_config, :deprecation)).to eq([])
-    end
-
-    it 'distinguishes from deprecated and removed configuration' do
-      expect(described_class.applicable_deprecations("11.0", invalid_config, :deprecation)).to include(conf1)
-      expect(described_class.applicable_deprecations("11.0", invalid_config, :deprecation)).to include(conf2)
-      expect(described_class.applicable_deprecations("12.0", invalid_config, :deprecation)).to include(conf1)
-      expect(described_class.applicable_deprecations("12.0", invalid_config, :deprecation)).to include(conf2)
-
-      expect(described_class.applicable_deprecations("11.0", invalid_config, :removal)).not_to include(conf2)
-      expect(described_class.applicable_deprecations("12.0", invalid_config, :removal)).to include(conf2)
-    end
-
-    it 'also detects deprecated falsey values' do
-      expect(described_class.applicable_deprecations("12.0", invalid_config, :deprecation)).to include(conf4)
-    end
-  end
-
-  describe '.check_config' do
-    it 'detects valid_config configuration' do
-      expect(described_class.check_config("11.0", valid_config)).to eq([])
-    end
-
-    it 'detects deprecated configuration for specified version and ignores not yet deprecated ones' do
-      message_1 = "* nginx['listen_address'] has been deprecated since 8.10 and was removed in 11.0. Use nginx['listen_addresses'] instead."
-      message_2 = "* gitlab_rails['stuck_ci_builds_worker_cron'] has been deprecated since 9.0 and was removed in 12.0. Use gitlab_rails['stuck_ci_jobs_worker_cron'] instead."
-      message_3 = "* gitlab_monitor['enable'] has been deprecated since 12.0 and will be removed in 13.0. Use gitlab_exporter['enable'] instead."
-
-      expect(described_class.check_config("11.0", invalid_config)).to include(message_1)
-      expect(described_class.check_config("11.0", invalid_config)).not_to include(message_2)
-      expect(described_class.check_config("12.0", invalid_config)).to include(message_2)
-      expect(described_class.check_config("12.0", invalid_config, :deprecation)).to include(message_3)
-    end
-  end
-
-  describe '.identify_deprecated_config' do
-    it 'detects deprecations correctly from list of supported keys' do
-      mattermost_supported_keys = %w(log_file_directory)
-      output = [
-        {
-          config_keys: %w(mattermost system_read_timeout),
-          deprecation: '10.2',
-          removal: '11.0',
-          note: nil
-        }
-      ]
-      expect(described_class.identify_deprecated_config(invalid_config, ["mattermost"], mattermost_supported_keys, "10.2", "11.0")).to eq(output)
-    end
-  end
-
-  describe '.deprecate_only_if_value' do
-    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-    before do
-      allow(Gitlab).to receive(:[]).and_call_original
-    end
-
-    context 'during removals' do
-      context 'when deprecated config is not set' do
-        it 'does not raise warning' do
-          expect(described_class.deprecate_only_if_value('14.0', chef_run.node.normal, :removal, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq([])
-        end
-      end
-
-      context 'when deprecated config is set to an acceptable value' do
-        before do
-          stub_gitlab_rb(
-            puma: { enable: false }
-          )
-        end
-        it 'does not raise warning' do
-          expect(described_class.deprecate_only_if_value('14.0', chef_run.node.normal, :removal, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq([])
-        end
-      end
-
-      context 'when deprecated config is set to an unacceptable value' do
-        before do
-          stub_gitlab_rb(
-            puma: { enable: true }
-          )
-        end
-        it 'raises warning' do
-          expect(described_class.deprecate_only_if_value('14.0', chef_run.node.normal, :removal, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq(["* puma[enable] has been deprecated since 13.10 and was removed in 14.0."])
-        end
-      end
-    end
-
-    context 'during deprecations' do
-      context 'when deprecated config is not set' do
-        it 'does not raise warning' do
-          expect(described_class.deprecate_only_if_value('13.12', chef_run.node.normal, :deprecation, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq([])
-        end
-      end
-
-      context 'when deprecated config is set to an acceptable value' do
-        before do
-          stub_gitlab_rb(
-            puma: { enable: false }
-          )
-        end
-        it 'raises warning' do
-          expect(described_class.deprecate_only_if_value('13.12', chef_run.node.normal, :deprecation, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq(["* puma[enable] has been deprecated since 13.10 and will be removed in 14.0."])
-        end
-      end
-
-      context 'when deprecated config is set to an unacceptable value' do
-        before do
-          stub_gitlab_rb(
-            puma: { enable: true }
-          )
-        end
-        it 'raises warning' do
-          expect(described_class.deprecate_only_if_value('13.12', chef_run.node.normal, :deprecation, ['gitlab', 'puma'], 'enable', true, '13.10', '14.0')).to eq(["* puma[enable] has been deprecated since 13.10 and will be removed in 14.0."])
-        end
-      end
-    end
-  end
-
-  describe 'NodeAttribute' do
-    before do
-      Gitlab::Deprecations::NodeAttribute.log_deprecations = true
-    end
-
-    after do
-      Gitlab::Deprecations::NodeAttribute.log_deprecations = false
-    end
-
-    it 'Logs deprecations for passed variables and proxies to new object' do
-      config = { 'monitoring' => { 'test' => 'test-value' } }
-      config['prometheus'] = Gitlab::Deprecations::NodeAttribute.new(config['monitoring'], "config['prometheus']", "config['monitoring']")
-
-      expect(config['prometheus']['test']).to eq('test-value')
-      expect_logged_deprecation(/Accessing config\['prometheus'\] is deprecated/)
-    end
-
-    it 'Logs deprecations for passed variables and proxies to new Proc if provided' do
-      config = { 'monitoring' => { 'test' => 'test-value' } }
-      config['prometheus'] = Gitlab::Deprecations::NodeAttribute.new(proc { config['monitoring'] }, "config['prometheus']", "config['monitoring']")
-
-      expect(config['prometheus']['test']).to eq('test-value')
-      expect_logged_deprecation(/Accessing config\['prometheus'\] is deprecated/)
-    end
-  end
-end
diff --git a/spec/libraries/gitaly_spec.rb b/spec/libraries/gitaly_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvZ2l0YWx5X3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/libraries/gitaly_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe Gitaly do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-  before { allow(Gitlab).to receive(:[]).and_call_original }
-
-  describe 'by default' do
-    it 'provides settings needed for gitaly to run' do
-      expect(chef_run.node['gitaly']['env']).to include(
-        'HOME' => '/var/opt/gitlab',
-        'PATH' => '/opt/gitlab/bin:/opt/gitlab/embedded/bin:/bin:/usr/bin'
-      )
-    end
-
-    it 'does not include known settings in the environment' do
-      expect(chef_run.node['gitaly']['env']).not_to include('GITALY_ENABLE')
-    end
-  end
-end
diff --git a/spec/libraries/gitlab_spec.rb b/spec/libraries/gitlab_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvZ2l0bGFiX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/libraries/gitlab_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'spec_helper'
-require_relative '../../files/gitlab-cookbooks/package/libraries/config/gitlab'
-
-RSpec.describe Gitlab do
-  context 'when using an attribute_block' do
-    it 'sets top level attributes to the provided root' do
-      Gitlab.attribute_block('gitlab') do
-        expect(Gitlab.attribute('test_attribute')[:parent]).to eq 'gitlab'
-      end
-      expect(Gitlab['test_attribute']).not_to be_nil
-      expect(Gitlab.hyphenate_config_keys['gitlab']).to include('test-attribute')
-    end
-  end
-
-  it 'sets top level attributes when no parent is provided' do
-    Gitlab.attribute('test_attribute')
-    expect(Gitlab['test_attribute']).not_to be_nil
-    expect(Gitlab.hyphenate_config_keys).to include('test-attribute')
-  end
-
-  it 'properly defines roles' do
-    role = Gitlab.role('test_node')
-    expect(Gitlab['test_node_role']).not_to be_nil
-    expect(Gitlab.hyphenate_config_keys['roles']).to include('test-node')
-    expect(role).to include(manage_services: true)
-  end
-
-  it 'supports overriding role default configuration' do
-    role = Gitlab.role('test_node', manage_services: false)
-    expect(Gitlab['test_node_role']).not_to be_nil
-    expect(role).to include(manage_services: false)
-  end
-
-  it 'supports overriding attribute default configuration' do
-    attribute = Gitlab.attribute('test_attribute', parent: 'example', priority: 40, enable: false, default: '')
-    expect(Gitlab['test_attribute']).to eq('')
-    expect(attribute).to include(parent: 'example', priority: 40, enable: false)
-  end
-
-  it 'disables ee attributes when EE is not enabled' do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow(Gitlab).to receive(:[]).with('edition').and_return(:ce)
-    expect(Gitlab.ee_attribute('test_attribute')[:ee]).to eq true
-    expect(Gitlab['test_attribute']).not_to be_nil
-    expect(Gitlab.hyphenate_config_keys).not_to include('test-attribute')
-  end
-
-  it 'enables ee attributes when EE is enabled' do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow(Gitlab).to receive(:[]).with('edition').and_return(:ee)
-    expect(Gitlab.ee_attribute('test_attribute')[:ee]).to eq true
-    expect(Gitlab['test_attribute']).not_to be_nil
-    expect(Gitlab.hyphenate_config_keys).to include('test-attribute')
-  end
-
-  it 'sorts attributes by sequence' do
-    Gitlab.attribute('last', priority: 99)
-    Gitlab.attribute('other1')
-    Gitlab.attribute('first', priority: -99)
-    Gitlab.attribute('other2')
-
-    expect(Gitlab.send(:sorted_settings).first[0]).to eq 'first'
-    expect(Gitlab.send(:sorted_settings).last[0]).to eq 'last'
-  end
-
-  it 'filters ee settings when sorting' do
-    Gitlab.attribute('test_attribute1')
-    Gitlab.attribute('test_attribute2', ee: true)
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow(Gitlab).to receive(:[]).with('edition').and_return(:ce)
-    expect(Gitlab.send(:sorted_settings).map(&:first)).to include('test_attribute1')
-    expect(Gitlab.send(:sorted_settings).map(&:first)).not_to include('test_attribute2')
-  end
-
-  it 'allows passing a block to the attribute use method' do
-    attribute = Gitlab.attribute('test_attribute').use { 'test' }
-    expect(attribute.handler).to eq('test')
-  end
-end
diff --git a/spec/libraries/helper_spec.rb b/spec/libraries/helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvaGVscGVyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/libraries/helper_spec.rb
+++ /dev/null
@@ -1,254 +0,0 @@
-require 'chef_helper'
-
-RSpec.shared_examples 'Postgres helpers' do |service_name, service_cmd, edition|
-  let(:chef_run) do
-    ChefSpec::SoloRunner.new do |node|
-      node.normal['gitlab'][service_name]['dir'] = '/fakedir'
-      node.normal['package']['install-dir'] = '/fake/install/dir'
-    end.converge("#{edition}::config")
-  end
-
-  let(:node) { chef_run.node }
-
-  let!(:helper) do
-    described_class.new(node)
-  end
-
-  before do
-    allow(VersionHelper).to receive(:version).with('/opt/gitlab/embedded/bin/psql --version') { 'YYYYYYYY XXXXXXX' }
-  end
-
-  it 'is associated with a valid service' do
-    # this is a validation to make sure we are passing a valid/existing service_name to the shared example
-    expect(node['gitlab'][service_name].to_h).not_to be_empty
-  end
-
-  describe '#version' do
-    it 'returns a valid version' do
-      expect(helper.version).to eq('XXXXXXX')
-    end
-  end
-
-  describe '#database_version' do
-    it 'returns a valid database_version' do
-      allow(File).to receive(:exist?).and_call_original
-      allow(File).to receive(:exist?).with('/fakedir/data/PG_VERSION') { true }
-      allow(File).to receive(:read).and_call_original
-      allow(File).to receive(:read).with('/fakedir/data/PG_VERSION') { '111.222' }
-      allow(Dir).to receive(:glob).with('/fake/install/dir/embedded/postgresql/*') { %w(111.222.18 222.333.11) }
-
-      # We mock this in chef_helper.rb. Override the mock to call the original
-      allow_any_instance_of(described_class).to receive(:database_version).and_call_original
-      expect(helper.database_version).to eq('111.222')
-    end
-  end
-
-  describe '#extension_exists?' do
-    it 'returns whether an extension exists' do
-      expect(helper).to receive(:success?).with("/opt/gitlab/bin/#{service_cmd} -d 'template1' -c 'select name from pg_available_extensions' -A | grep -x myextension")
-      helper.extension_exists?('myextension')
-    end
-  end
-
-  describe '#extension_enabled?' do
-    it 'returns whether an extension exists' do
-      expect(helper).to receive(:success?).with("/opt/gitlab/bin/#{service_cmd} -d 'mydatabase' -c 'select extname from pg_extension' -A | grep -x myextension")
-      helper.extension_enabled?('myextension', 'mydatabase')
-    end
-  end
-
-  describe '#database_exists?' do
-    it 'returns whether a database exists' do
-      expect(helper).to receive(:success?).with("/opt/gitlab/bin/#{service_cmd} -d 'template1' -c 'select datname from pg_database' -A | grep -x mydatabase")
-      helper.database_exists?('mydatabase')
-    end
-  end
-end
-
-RSpec.describe PgHelper do
-  include_examples 'Postgres helpers', 'postgresql', 'gitlab-psql', 'gitlab'
-end
-
-RSpec.describe GeoPgHelper do
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    stub_gitlab_rb(
-      'geo_postgresql' => {
-        'dir' => '/fakedir'
-      }
-    )
-  end
-
-  include_examples 'Postgres helpers', 'geo-postgresql', 'gitlab-geo-psql', 'gitlab-ee'
-end
-
-RSpec.describe OmnibusHelper do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-  let(:node) { chef_run.node }
-  let(:services) do
-    %w(
-      puma
-      sidekiq
-      gitlab-workhorse
-      postgresql
-      redis
-      nginx
-      logrotate
-      prometheus
-      alertmanager
-      grafana
-      node-exporter
-      redis-exporter
-      postgres-exporter
-      gitlab-exporter
-      gitlab-pages
-      gitaly
-    ).freeze
-  end
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow(File).to receive(:symlink?).with(any_args).and_call_original
-  end
-
-  context 'service is currently enabled, bootstrapped and is running' do
-    before do
-      services.each do |service|
-        stub_should_notify?(service, true)
-      end
-      stub_gitlab_rb(nginx: { enable: true })
-    end
-
-    it 'notifies the service' do
-      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).to notify('runit_service[nginx]').to(:restart).delayed
-    end
-  end
-
-  context 'disabling a service that was bootstrapped and is currently running' do
-    before do
-      services.each do |service|
-        stub_should_notify?(service, true)
-      end
-      stub_gitlab_rb(nginx: { enable: false })
-    end
-
-    it 'does not notify the service' do
-      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
-    end
-  end
-
-  context 'enabling a service that was bootstrapped but not currently running' do
-    before do
-      services.each do |service|
-        allow(File).to receive(:symlink?).with("/opt/gitlab/service/#{service}").and_return(true)
-        allow_any_instance_of(OmnibusHelper).to receive(:success?).with("/opt/gitlab/init/#{service} status").and_return(false)
-      end
-      stub_gitlab_rb(nginx: { enable: true })
-    end
-
-    it 'does not notify the service' do
-      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
-    end
-  end
-
-  context 'disabling a service that was bootstrapped but not currently running' do
-    before do
-      services.each do |service|
-        allow(File).to receive(:symlink?).with("/opt/gitlab/service/#{service}").and_return(true)
-        allow_any_instance_of(OmnibusHelper).to receive(:success?).with("/opt/gitlab/init/#{service} status").and_return(false)
-      end
-      stub_gitlab_rb(nginx: { enable: false })
-    end
-
-    it 'does not notify the service' do
-      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
-    end
-  end
-
-  context 'enabling a service that was disabled but currently running' do
-    before do
-      services.each do |service|
-        allow(File).to receive(:symlink?).with("/opt/gitlab/service/#{service}").and_return(false)
-        allow_any_instance_of(OmnibusHelper).to receive(:success?).with("/opt/gitlab/init/#{service} status").and_return(true)
-      end
-      stub_gitlab_rb(nginx: { enable: true })
-    end
-
-    it 'does not notify the service' do
-      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
-    end
-  end
-
-  context 'disabling a service that was disabled but currently running' do
-    before do
-      services.each do |service|
-        allow(File).to receive(:symlink?).with("/opt/gitlab/service/#{service}").and_return(false)
-        allow_any_instance_of(OmnibusHelper).to receive(:success?).with("/opt/gitlab/init/#{service} status").and_return(true)
-      end
-      stub_gitlab_rb(nginx: { enable: false })
-    end
-
-    it 'does not notify the service' do
-      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
-    end
-  end
-
-  context 'enabling a service that was disabled and not currently running' do
-    before do
-      services.each do |service|
-        stub_should_notify?(service, false)
-      end
-      stub_gitlab_rb(nginx: { enable: true })
-    end
-
-    it 'does not notify the service' do
-      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
-    end
-  end
-
-  context 'disabling a service that was disabled and not currently running' do
-    before do
-      services.each do |service|
-        stub_should_notify?(service, false)
-      end
-      stub_gitlab_rb(nginx: { enable: false })
-    end
-
-    it 'does not notify the service' do
-      expect(chef_run.template('/var/opt/gitlab/nginx/conf/gitlab-http.conf')).not_to notify('runit_service[nginx]').to(:restart).delayed
-    end
-  end
-
-  context 'expected_owner?' do
-    let(:oh) { OmnibusHelper.new(chef_run.node) }
-    before do
-      allow_any_instance_of(OmnibusHelper).to receive(:expected_owner?)
-        .and_call_original
-      allow_any_instance_of(OmnibusHelper).to receive(
-        :expected_user?).and_return(false)
-      allow_any_instance_of(OmnibusHelper).to receive(
-        :expected_user?
-      ).with('/tmp/fakefile', 'fakeuser').and_return(true)
-      allow_any_instance_of(OmnibusHelper).to receive(
-        :expected_group?).and_return(false)
-      allow_any_instance_of(OmnibusHelper).to receive(
-        :expected_group?
-      ).with('/tmp/fakefile', 'fakegroup').and_return(true)
-    end
-
-    it 'should return false if the group is wrong' do
-      expect(oh.expected_owner?('/tmp/fakefile', 'fakeuser', 'wronggroup'))
-        .to be false
-    end
-
-    it 'should return false if the user is wrong' do
-      expect(oh.expected_owner?('/tmp/fakefile', 'wronguser', 'fakegroup'))
-        .to be false
-    end
-
-    it 'should return true if user and group is correct' do
-      expect(oh.expected_owner?('/tmp/fakefile', 'fakeuser', 'fakegroup'))
-        .to be true
-    end
-  end
-end
diff --git a/spec/libraries/helpers/base_pg_helper_spec.rb b/spec/libraries/helpers/base_pg_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvaGVscGVycy9iYXNlX3BnX2hlbHBlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/libraries/helpers/base_pg_helper_spec.rb
+++ /dev/null
@@ -1,336 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe BasePgHelper do
-  cached(:chef_run) { converge_config }
-  let(:node) { chef_run.node }
-  subject { described_class.new(node) }
-
-  before do
-    allow(subject).to receive(:service_name) { 'postgresql' }
-    allow(subject).to receive(:service_cmd) { 'gitlab-psql' }
-  end
-
-  context 'when handling ssl configuration' do
-    after do
-      chef_run.node.rm_normal('postgresql', 'dir')
-      chef_run.node.rm_normal('postgresql', 'ssl_cert_file')
-    end
-
-    describe 'ssl_cert_file' do
-      it 'is configured with an absolute path' do
-        absolute_path = "/my/absolute/path"
-        config_path = "MrBoots"
-        chef_run.node.normal['postgresql']['ssl_cert_file'] = absolute_path
-        chef_run.node.normal['postgresql']['dir'] = config_path
-        expect(subject.ssl_cert_file).to eq(absolute_path)
-      end
-
-      it 'is configured with a relative path' do
-        relative_path = "my/relative/path"
-        config_path = "/MrBoots"
-        chef_run.node.normal['postgresql']['ssl_cert_file'] = relative_path
-        chef_run.node.normal['postgresql']['dir'] = config_path
-        expect(subject.ssl_cert_file).to eq(File.join(config_path, 'data', relative_path))
-      end
-    end
-
-    describe 'ssl_key_file' do
-      it 'is configured with an absolute path' do
-        absolute_path = "/my/absolute/path"
-        config_path = "MrBoots"
-        chef_run.node.normal['postgresql']['ssl_key_file'] = absolute_path
-        chef_run.node.normal['postgresql']['dir'] = config_path
-        expect(subject.ssl_key_file).to eq(absolute_path)
-      end
-
-      it 'is configured with a relative path' do
-        relative_path = "my/relative/path"
-        config_path = "/MrBoots"
-        chef_run.node.normal['postgresql']['ssl_key_file'] = relative_path
-        chef_run.node.normal['postgresql']['dir'] = config_path
-        expect(subject.ssl_key_file).to eq(File.join(config_path, 'data', relative_path))
-      end
-    end
-  end
-
-  describe '#database_exists?' do
-    it 'calls out to psql_cmd' do
-      expect(subject).to receive(:psql_cmd).with(
-        [
-          "-d 'template1'",
-          "-c 'select datname from pg_database' -A",
-          '| grep -x example'
-        ])
-      subject.database_exists?('example')
-    end
-
-    context 'database present' do
-      it 'truthy' do
-        allow(subject).to receive(:psql_cmd) { true }
-        expect(subject.database_exists?('example')).to be_truthy
-      end
-
-      it 'falsey' do
-        allow(subject).to receive(:psql_cmd) { false }
-        expect(subject.database_exists?('example')).to be_falsey
-      end
-    end
-  end
-
-  describe '#extension_enabled?' do
-    it 'will check with psql_cmd when database present' do
-      expect(subject).to receive(:psql_cmd).with(
-        [
-          "-d 'database'",
-          "-c 'select extname from pg_extension' -A",
-          '| grep -x extension'
-        ])
-      subject.extension_enabled?('extension', 'database')
-    end
-
-    context 'extension present' do
-      it 'truthy' do
-        allow(subject).to receive(:psql_cmd) { true }
-        expect(subject.extension_enabled?('extension', 'database')).to be_truthy
-      end
-
-      it 'falsey' do
-        allow(subject).to receive(:psql_cmd) { false }
-        expect(subject.extension_enabled?('extension', 'database')).to be_falsey
-      end
-    end
-  end
-
-  describe '#extension_can_be_enabled?' do
-    before do
-      allow(subject).to receive(:is_running?).and_return(true)
-      allow(subject).to receive(:is_standby?).and_return(false)
-      allow(subject).to receive(:extension_exists?).and_return(true)
-      allow(subject).to receive(:database_exists?).and_return(true)
-      allow(subject).to receive(:extension_enabled?).and_return(false)
-    end
-
-    it 'can be enabled' do
-      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_truthy
-    end
-
-    it 'needs to be running' do
-      allow(subject).to receive(:is_running?).and_return(false)
-      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_falsey
-    end
-
-    it 'cannot be done on a slave' do
-      allow(subject).to receive(:is_standby?).and_return(true)
-      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_falsey
-    end
-
-    it 'needs to have the extension available' do
-      allow(subject).to receive(:extension_exists?).and_return(false)
-      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_falsey
-    end
-
-    it 'needs the database to load the extension into' do
-      allow(subject).to receive(:database_exists?).and_return(false)
-      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_falsey
-    end
-
-    it 'should not enable twice' do
-      allow(subject).to receive(:extension_enabled?).and_return(true)
-      expect(subject.extension_can_be_enabled?('extension', 'db')).to be_falsey
-    end
-  end
-
-  describe '#user_options' do
-    before do
-      result = spy('shellout')
-      allow(result).to receive(:stdout).and_return("f|f|t|f\n")
-      allow(subject).to receive(:do_shell_out).and_return(result)
-    end
-
-    it 'returns hash from query' do
-      expect(subject.user_options('')).to eq(
-        {
-          'SUPERUSER' => false,
-          'CREATEDB' => false,
-          'REPLICATION' => true,
-          'BYPASSRLS' => false
-        }
-      )
-    end
-  end
-
-  describe '#user_options_set?' do
-    let(:default_options) do
-      {
-        'SUPERUSER' => false,
-        'CREATEDB' => false,
-        'REPLICATION' => true,
-        'BYPASSRLS' => false
-      }
-    end
-
-    context 'default user options' do
-      before do
-        allow(subject).to receive(:user_options).and_return(default_options)
-      end
-
-      it 'returns true when no options are asked about' do
-        expect(subject.user_options_set?('', [])).to be_truthy
-      end
-
-      it 'returns true when options are set to their defaults' do
-        expect(subject.user_options_set?('', ['NOSUPERUSER'])).to be_truthy
-      end
-
-      it 'returns false when options are set away from their defaults' do
-        expect(subject.user_options_set?('', ['SUPERUSER'])).to be_falsey
-      end
-    end
-
-    context 'modified user' do
-      before do
-        allow(subject).to receive(:user_options).and_return(default_options.merge({ 'SUPERUSER' => true }))
-      end
-
-      it 'returns false when options is not what we expect' do
-        expect(subject.user_options_set?('', ['NOSUPERUSER'])).to be_falsey
-      end
-    end
-  end
-
-  describe '#user_password_match?' do
-    before do
-      # user: gitlab pass: test123
-      allow(subject).to receive(:user_hashed_password) { 'md5b56573ef0d94cff111898c63ec259f3f' }
-    end
-
-    it 'returns true when same password is in plain-text' do
-      expect(subject.user_password_match?('gitlab', 'test123')).to be_truthy
-    end
-
-    it 'returns true when same password is in MD5 format' do
-      expect(subject.user_password_match?('gitlab', 'md5b56573ef0d94cff111898c63ec259f3f')).to be_truthy
-    end
-
-    it 'returns false when wrong password is in plain-text' do
-      expect(subject.user_password_match?('gitlab', 'wrong')).to be_falsey
-    end
-
-    it 'returns false when wrong password is in MD5 format' do
-      expect(subject.user_password_match?('gitlab', 'md5b599de4332636c03a60fca13be1edb5f')).to be_falsey
-    end
-
-    it 'returns false when password is not supplied' do
-      expect(subject.user_password_match?('gitlab', nil)).to be_falsey
-    end
-
-    context 'nil password' do
-      before do
-        # user: gitlab pass: unset
-        allow(subject).to receive(:user_hashed_password) { '' }
-      end
-
-      it 'returns true when the password is nil' do
-        expect(subject.user_password_match?('gitlab', nil)).to be_truthy
-      end
-    end
-  end
-
-  describe '#parse_pghash' do
-    let(:payload) { '{host=127.0.0.1,dbname=gitlabhq_production,port=5432,user=gitlab,"password=foo}bar\"zoo\\cat"}' }
-
-    it 'returns a hash' do
-      expect(subject.parse_pghash(payload)).to be_a(Hash)
-    end
-
-    it 'when content is empty still return a hash' do
-      expect(subject.parse_pghash('')).to be_a(Hash)
-      expect(subject.parse_pghash('{}')).to be_a(Hash)
-    end
-
-    it 'returns hash with expected keys' do
-      hash = subject.parse_pghash(payload)
-
-      expect(hash.keys).to contain_exactly(:host, :dbname, :port, :user, :password)
-    end
-
-    it 'returns hash with expected values' do
-      hash = subject.parse_pghash(payload)
-
-      expect(hash.values).to contain_exactly('127.0.0.1', 'gitlabhq_production', '5432', 'gitlab', 'foo}bar"zoo\cat')
-    end
-  end
-
-  describe '#is_running?' do
-    it 'returns true when postgres is running' do
-      stub_service_success_status('postgresql', true)
-
-      expect(subject.is_running?).to be_truthy
-    end
-
-    it 'returns false when postgres is not running' do
-      stub_service_success_status('postgresql', false)
-
-      expect(subject.is_running?).to be_falsey
-    end
-  end
-
-  describe '#is_managed_and_offline?' do
-    it 'returns true when conditions are met' do
-      chef_run.node.normal['postgresql']['enable'] = true
-      stub_service_failure_status('postgresql', true)
-
-      expect(subject.is_managed_and_offline?).to be_truthy
-    end
-
-    it 'returns false when conditions are not met' do
-      chef_run.node.normal['postgresql']['enable'] = true
-      stub_service_failure_status('postgresql', false)
-
-      expect(subject.is_managed_and_offline?).to be_falsey
-
-      chef_run.node.normal['postgresql']['enable'] = false
-      stub_service_failure_status('postgresql', false)
-
-      expect(subject.is_managed_and_offline?).to be_falsey
-
-      stub_service_failure_status('postgresql', true)
-      expect(subject.is_managed_and_offline?).to be_falsey
-    end
-  end
-
-  describe '#is_standby?' do
-    let(:recovery_files) { %w(recovery.conf recovery.signal standby.signal) }
-
-    it 'returns true for a standby instance' do
-      recovery_files.each do |f|
-        allow(File).to receive(:exist?)
-          .with("/var/opt/gitlab/postgresql/data/#{f}").and_return(true)
-      end
-
-      expect(subject.is_standby?).to be true
-    end
-
-    it 'returns false for a primary instance' do
-      recovery_files.each do |f|
-        allow(File).to receive(:exist?)
-          .with("/var/opt/gitlab/postgresql/data/#{f}").and_return(false)
-      end
-      expect(subject.is_standby?).to be false
-    end
-  end
-
-  context 'when handling connection information' do
-    describe '#connection_info' do
-      it 'raises a not implemented warning' do
-        expect { subject.connection_info }.to raise_error(NotImplementedError)
-      end
-    end
-
-    describe '#build_connection_info' do
-      it 'returns a ConnectionInfo object' do
-        expect(subject.build_connection_info('dbname', 'dbhost', 'port', 'pguser')).to be_a(described_class::ConnectionInfo)
-      end
-    end
-  end
-end
diff --git a/spec/libraries/helpers/gitlab_cluster_spec.rb b/spec/libraries/helpers/gitlab_cluster_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvaGVscGVycy9naXRsYWJfY2x1c3Rlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/libraries/helpers/gitlab_cluster_spec.rb
+++ /dev/null
@@ -1,164 +0,0 @@
-require 'spec_helper'
-require_relative '../../../files/gitlab-cookbooks/package/libraries/gitlab_cluster'
-
-RSpec.describe GitlabCluster, :cluster_config do
-  let(:gitlab_cluster_config_path) { described_class::JSON_FILE }
-  subject { described_class.config }
-
-  describe '#all' do
-    context 'when the cluster configuration file does not exist' do
-      it 'returns an empty hash' do
-        expect(subject.all).to be_empty
-      end
-    end
-
-    context 'when the cluster configuration file exists' do
-      it 'parses the file content' do
-        stub_file_content(gitlab_cluster_config_path, foo: 'bar')
-
-        expect(subject.all).to eq('foo' => 'bar')
-      end
-    end
-  end
-
-  describe '#set' do
-    it 'set the value of a single config option' do
-      subject.set('primary', true)
-
-      expect(subject.all).to eq('primary' => true)
-    end
-
-    it 'sets the value of a nested config option' do
-      subject.set('patroni', 'standby_cluster', 'enable', true)
-
-      expect(subject.all).to eq('patroni' => { 'standby_cluster' => { 'enable' => true } })
-    end
-
-    it 'overrides the key value if it already set' do
-      stub_file_content(gitlab_cluster_config_path, patroni: { standby_cluster: { enable: false } })
-
-      subject.set('patroni', 'standby_cluster', 'enable', true)
-
-      expect(subject.all).to eq('patroni' => { 'standby_cluster' => { 'enable' => true } })
-    end
-  end
-
-  describe '#get' do
-    before do
-      stub_file_content(gitlab_cluster_config_path, primary: false, patroni: { standby_cluster: { enable: true } })
-    end
-
-    it 'get the value of a single config option if the key exists' do
-      expect(subject.get('primary')).to eq(false)
-    end
-
-    it 'get the value of a nested config option if the key exists' do
-      expect(subject.get('patroni', 'standby_cluster', 'enable')).to eq(true)
-    end
-
-    it 'returns nil if the key does not exist' do
-      expect(subject.get('foo', 'bar')).to be_nil
-    end
-  end
-
-  describe '#load_roles!' do
-    before do
-      stub_gitlab_rb(application_role: { enable: true }, geo_primary_role: { enable: nil }, geo_secondary_role: { enable: true })
-    end
-
-    it 'overrides roles defined in the configuration file' do
-      stub_file_content(gitlab_cluster_config_path, secondary: false)
-
-      subject.load_roles!
-
-      expect(Gitlab['application_role']['enable']).to eq(true)
-      expect(Gitlab['geo_secondary_role']['enable']).to eq(false)
-    end
-
-    it 'does not override roles not defined in the configuration file' do
-      stub_file_content(gitlab_cluster_config_path, {})
-
-      subject.load_roles!
-
-      expect(Gitlab['application_role']['enable']).to eq(true)
-      expect(Gitlab['geo_secondary_role']['enable']).to eq(true)
-    end
-
-    it 'prints a warning message for each enabled role defined in the configuration file' do
-      stub_file_content(gitlab_cluster_config_path, primary: true, secondary: false)
-
-      expect(LoggingHelper)
-        .to receive(:warning)
-        .with("The 'geo_primary_role' is defined in #{gitlab_cluster_config_path} as 'true' and overrides the setting in the /etc/gitlab/gitlab.rb")
-
-      expect(LoggingHelper)
-        .to receive(:warning)
-        .with("The 'geo_secondary_role' is defined in #{gitlab_cluster_config_path} as 'false' and overrides the setting in the /etc/gitlab/gitlab.rb")
-        .once
-
-      subject.load_roles!
-    end
-  end
-
-  describe '#write_to_file!' do
-    let(:config_path) { Dir.mktmpdir }
-    let(:gitlab_cluster_config_path) { File.join(config_path, 'gitlab-cluster.json') }
-
-    before do
-      stub_const('GitlabCluster::CONFIG_PATH', config_path)
-      stub_const('GitlabCluster::JSON_FILE', gitlab_cluster_config_path)
-    end
-
-    after do
-      FileUtils.rm_rf(config_path)
-    end
-
-    context 'when the config directory does not exist' do
-      it 'does not create the configuration file' do
-        FileUtils.rm_rf(config_path)
-
-        subject.save
-
-        expect(File.exist?(gitlab_cluster_config_path)).to eq(false)
-      end
-    end
-
-    context 'when the cluster configuration file does not exist' do
-      it 'creates the configuration file' do
-        FileUtils.rm_rf(gitlab_cluster_config_path)
-
-        subject.save
-
-        expect(File.exist?(gitlab_cluster_config_path)).to eq(true)
-        expect(read_file_content(gitlab_cluster_config_path)).to be_empty
-      end
-    end
-
-    context 'when the cluster configuration file exists' do
-      it 'overrides previous settings' do
-        write_file_content(gitlab_cluster_config_path, foo: 'bar', zoo: true)
-        subject.set('zoo', false)
-
-        subject.save
-
-        expect(read_file_content(gitlab_cluster_config_path)).to eq("foo" => "bar", "zoo" => false)
-      end
-    end
-  end
-
-  def stub_file_content(fullpath, content)
-    allow(File).to receive(:exist?).with(fullpath).and_return(true)
-    allow(IO).to receive(:read).with(fullpath).and_return(content.to_json)
-  end
-
-  def read_file_content(fullpath)
-    JSON.parse(File.read(fullpath))
-  end
-
-  def write_file_content(fullpath, content)
-    File.open(fullpath, 'w') do |f|
-      f.write(content.to_json)
-      f.chmod(0600)
-    end
-  end
-end
diff --git a/spec/libraries/helpers/gitlab_workhorse_helper_spec.rb b/spec/libraries/helpers/gitlab_workhorse_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvaGVscGVycy9naXRsYWJfd29ya2hvcnNlX2hlbHBlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/libraries/helpers/gitlab_workhorse_helper_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe GitlabWorkhorseHelper do
-  let(:node) { chef_run.node }
-  subject { described_class.new(node) }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'workhorse is listening on a tcp socket' do
-    cached(:chef_run) { converge_config }
-    let(:tcp_address) { '1.9.8.4' }
-
-    before do
-      stub_gitlab_rb(
-        gitlab_workhorse: {
-          listen_network: 'http',
-          listen_addr: tcp_address
-        }
-      )
-    end
-
-    describe '#unix_socket?' do
-      it 'returns false' do
-        expect(subject.unix_socket?).to be false
-      end
-    end
-  end
-
-  context 'workhorse is listening on a unix socket' do
-    cached(:chef_run) { converge_config }
-    before do
-      stub_gitlab_rb(
-        gitlab_workhorse: {
-          listen_network: 'unix'
-        }
-      )
-    end
-
-    describe '#unix_socket?' do
-      it 'returns true' do
-        expect(subject.unix_socket?).to be true
-      end
-    end
-  end
-end
diff --git a/spec/libraries/helpers/pg_status_helper_spec.rb b/spec/libraries/helpers/pg_status_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvaGVscGVycy9wZ19zdGF0dXNfaGVscGVyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/libraries/helpers/pg_status_helper_spec.rb
+++ /dev/null
@@ -1,133 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe PgStatusHelper do
-  cached(:chef_run) { converge_config }
-  let!(:node) { chef_run.node }
-  let!(:helper) { PgHelper.new(node) }
-  let(:connection_info) { helper.build_connection_info('dbname', 'dbhost', 'port', 'pguser') }
-
-  context 'when checking status' do
-    subject { described_class.new(connection_info, node) }
-
-    describe '#service_checks_exhausted?' do
-      it 'returns true when there are zero remaining service checks' do
-        allow(subject).to receive(:remaining_service_checks).and_return(0)
-        expect(subject.service_checks_exhausted?).to be true
-      end
-
-      it 'returns false when more service checks are allowed' do
-        allow(subject).to receive(:remaining_service_checks).and_return(1)
-        expect(subject.service_checks_exhausted?).to be false
-      end
-    end
-
-    describe '#ready?' do
-      it 'raises no warnings when Postgres is accepting connections' do
-        allow(subject).to receive(:accepting_connections?).and_return(true)
-        expect(subject.ready?).to be true
-      end
-
-      it 'raises a warning when Postgres is not responding' do
-        allow(subject).to receive(:not_responding?).and_return(true)
-        allow(subject).to receive(:remaining_service_checks).and_return(0)
-        expect { subject.ready? }.to raise_error(RuntimeError, 'PostgreSQL did not respond before service checks were exhausted')
-      end
-
-      it 'raises a warning when Postgres gets invalid connection parameters' do
-        allow(subject).to receive(:invalid_connection_parameters?).and_return(true)
-        expect { subject.ready? }.to raise_error(RuntimeError, 'PostgreSQL is not receiving the correct connection parameters')
-      end
-
-      it 'raises a warning when all service checks are exhausted' do
-        allow(subject).to receive(:service_checks_exhausted?).and_return(true)
-        expect { subject.ready? }.to raise_error(RuntimeError, 'Exhausted service checks and database is still not available')
-      end
-    end
-
-    context 'when invoking pg_isready' do
-      using RSpec::Parameterized::TableSyntax
-
-      where(:exit_code, :accepting, :rejecting, :not_responding, :invalid_parameters) do
-        0 | true | false | false | false
-        1 | false | true | false | false
-        2 | false | false | true | false
-        3 | false | false | false | true
-      end
-
-      with_them do
-        before do
-          result = spy('shellout')
-          allow(subject).to receive(:do_shell_out).and_return(result)
-          allow(result).to receive(:exitstatus).and_return(exit_code)
-        end
-
-        describe '#service_state' do
-          it 'returns the expected exit code' do
-            expect(subject.service_state).to eq(exit_code)
-          end
-        end
-
-        describe '#accepting_connections?' do
-          it 'returns the expected boolean' do
-            expect(subject.accepting_connections?).to be accepting
-          end
-        end
-
-        describe '#rejecting_connections?' do
-          it 'returns the expected boolean' do
-            expect(subject.rejecting_connections?).to be rejecting
-          end
-        end
-
-        describe '#not_responding?' do
-          it 'returns the expected boolean' do
-            expect(subject.not_responding?).to be not_responding
-          end
-        end
-
-        describe '#invalid_connection_parameters?' do
-          it 'returns the expected boolean' do
-            expect(subject.invalid_connection_parameters?).to be invalid_parameters
-          end
-        end
-      end
-    end
-  end
-
-  context 'when all checks use configured defaults' do
-    subject { described_class.new(connection_info, node) }
-
-    describe '#maximum_service_checks' do
-      it 'will check 20 times' do
-        expect(subject.maximum_service_checks).to eq(20)
-      end
-    end
-
-    describe '#service_check_interval' do
-      it 'will check every 5 seconds' do
-        expect(subject.service_check_interval).to eq(5)
-      end
-    end
-  end
-
-  context 'when checks are are customized' do
-    let(:the_answer) { 424242 }
-    before do
-      chef_run.node.normal['postgresql']['max_service_checks'] = the_answer
-      chef_run.node.normal['postgresql']['service_check_interval'] = the_answer
-      @subject = described_class.new(connection_info, node)
-    end
-
-    describe '#maximum_service_checks' do
-      it 'will check the configured number of times' do
-        expect(@subject.maximum_service_checks).to eq(the_answer)
-      end
-    end
-
-    describe '#service_check_interval' do
-      it 'will check at a configured interval' do
-        expect(@subject.service_check_interval).to eq(the_answer)
-      end
-    end
-  end
-end
diff --git a/spec/libraries/lets_encrypt_spec.rb b/spec/libraries/lets_encrypt_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvbGV0c19lbmNyeXB0X3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/libraries/lets_encrypt_spec.rb
+++ /dev/null
@@ -1,179 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe LetsEncrypt do
-  subject { ::LetsEncrypt }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context '.parse_variables' do
-    it 'calls to parse_enable' do
-      expect(subject).to receive(:parse_enable)
-
-      subject.parse_variables
-    end
-  end
-
-  context '.parse_enable' do
-    context 'when specifying letsencrypt enabled' do
-      context 'true' do
-        before { stub_gitlab_rb(letsencrypt: { enable: true }) }
-
-        it 'should not call should_auto_enable?' do
-          expect(subject).not_to receive(:should_auto_enable?)
-
-          subject.parse_enable
-        end
-      end
-
-      context 'false' do
-        before { stub_gitlab_rb(letsencrypt: { enable: false }) }
-
-        it 'should not call should_auto_enable?' do
-          expect(subject).not_to receive(:should_auto_enable?)
-
-          subject.parse_enable
-        end
-      end
-
-      context 'unspecified' do
-        it 'should use the value of should_auto_enable' do
-          allow(subject).to receive(:should_auto_enable?).and_return('bananas')
-          subject.parse_enable
-
-          expect(Gitlab['letsencrypt']['enable']).to eq('bananas')
-        end
-      end
-    end
-  end
-
-  context '.should_auto_enable?' do
-    let(:node) { Mash.new(gitlab: { nginx: {} }) }
-
-    before do
-      stub_gitlab_rb(
-        gitlab_rails: {
-          gitlab_https: true
-        },
-        nginx: {
-          ssl_certificate_key: 'example.key',
-          ssl_certificate: 'example.crt'
-        }
-      )
-
-      allow(Gitlab).to receive(:[]).with(:node).and_return(node)
-      allow(File).to receive(:exist?).with('example.key').and_return(false)
-      allow(File).to receive(:exist?).with('example.crt').and_return(false)
-    end
-
-    it 'is true' do
-      expect(subject.should_auto_enable?).to be_truthy
-    end
-
-    it 'is false when not using a https url' do
-      stub_gitlab_rb(gitlab_rails: { gitlab_https: false })
-
-      expect(subject.should_auto_enable?).to be_falsey
-    end
-
-    it 'is false when nginx is not enabled' do
-      stub_gitlab_rb(nginx: { enable: false })
-
-      expect(subject.should_auto_enable?).to be_falsey
-    end
-
-    it 'is false when nginx is disabled by roles' do
-      allow(node['gitlab']['nginx']).to receive(:[]).with('enable').and_return(false)
-
-      expect(subject.should_auto_enable?).to be_falsey
-    end
-
-    it 'is false with explicit nginx.listen_https = false' do
-      stub_gitlab_rb(nginx: { listen_https: false })
-
-      expect(subject.should_auto_enable?).to be_falsey
-    end
-
-    it 'is false with the key present' do
-      allow(File).to receive(:exist?).with('example.key').and_return(true)
-
-      expect(subject.should_auto_enable?).to be_falsey
-    end
-
-    it 'is false with the cert present' do
-      mock_cert = OpenSSL::X509::Certificate.new
-      allow(mock_cert).to receive(:not_after).and_return(Time.now + 600)
-      allow(File).to receive(:exist?).with('example.crt').and_return(true)
-      allow(File).to receive(:read).with('example.crt').and_return(nil)
-      allow(OpenSSL::X509::Certificate).to receive(:new).and_return(mock_cert)
-
-      expect(subject.should_auto_enable?).to be_falsey
-    end
-
-    it 'is true when files present, but we provisioned them before' do
-      stub_gitlab_rb(letsencrypt: { auto_enabled: true })
-
-      allow(File).to receive(:exist?).with('example.key').and_return(true)
-      allow(File).to receive(:exist?).with('example.crt').and_return(true)
-
-      expect(subject.should_auto_enable?).to be_truthy
-    end
-
-    it 'is true when files present, but LE certificate is expired' do
-      mock_cert = OpenSSL::X509::Certificate.new
-      allow(mock_cert).to receive(:not_after).and_return(Time.now - 1)
-      allow(mock_cert).to receive(:issuer).and_return(
-        OpenSSL::X509::Name.parse(%(/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3)))
-      allow(File).to receive(:exist?).with('example.key').and_return(true)
-      allow(File).to receive(:exist?).with('example.crt').and_return(true)
-      allow(File).to receive(:read).with('example.crt').and_return(nil)
-      allow(OpenSSL::X509::Certificate).to receive(:new).and_return(mock_cert)
-
-      expect(subject.should_auto_enable?).to be_truthy
-    end
-
-    it 'is false when files present, but non-LE certificate is expired' do
-      mock_cert = OpenSSL::X509::Certificate.new
-      allow(mock_cert).to receive(:not_after).and_return(Time.now - 1)
-      allow(mock_cert).to receive(:issuer).and_return(
-        OpenSSL::X509::Name.parse('/C=US/O=Example Corporation/CN=Example'))
-      allow(File).to receive(:exist?).with('example.key').and_return(true)
-      allow(File).to receive(:exist?).with('example.crt').and_return(true)
-      allow(File).to receive(:read).with('example.crt').and_return(nil)
-      allow(OpenSSL::X509::Certificate).to receive(:new).and_return(mock_cert)
-
-      expect(subject.should_auto_enable?).to be_falsey
-    end
-  end
-
-  context '.save_auto_enabled' do
-    it 'does nothing if not auto_enabled' do
-      expect(SecretsHelper).not_to receive(:load_gitlab_secrets)
-
-      subject.save_auto_enabled
-    end
-
-    context 'auto_enabled' do
-      before do
-        stub_gitlab_rb(letsencrypt: { auto_enabled: true })
-        allow(SecretsHelper).to receive(:load_gitlab_secrets).and_return({})
-        allow(SecretsHelper).to receive(:write_to_gitlab_secrets)
-      end
-
-      it 'writes when secret is absent' do
-        expect(SecretsHelper).to receive(:write_to_gitlab_secrets)
-
-        subject.save_auto_enabled
-      end
-
-      it 'does not write if secret is already true' do
-        allow(SecretsHelper).to receive(:load_gitlab_secrets)
-          .and_return('letsencrypt' => { 'auto_enabled' => true })
-        expect(SecretsHelper).not_to receive(:write_to_gitlab_secrets)
-
-        subject.save_auto_enabled
-      end
-    end
-  end
-end
diff --git a/spec/libraries/logging_helper_spec.rb b/spec/libraries/logging_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvbG9nZ2luZ19oZWxwZXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/libraries/logging_helper_spec.rb
+++ /dev/null
@@ -1,172 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe LoggingHelper do
-  let(:log) { spy('Chef::Log') }
-
-  before do
-    subject.reset
-    stub_const('Chef::Log', log)
-  end
-
-  context '.messages' do
-    it 'starts with no messages' do
-      expect(subject.messages).to eq([])
-    end
-  end
-
-  context '.log' do
-    it 'records into .messages' do
-      subject.log('one')
-      expect(subject.messages).to contain_exactly(Hash)
-    end
-
-    it 'records message' do
-      subject.log('two')
-      expect(subject.messages).to contain_exactly(hash_including(message: 'two'))
-    end
-
-    it 'records two messages' do
-      subject.log('one')
-      subject.log('two')
-      expect(subject.messages).to contain_exactly(
-        hash_including(message: 'one'),
-        hash_including(message: 'two')
-      )
-    end
-
-    it 'optionally records kind' do
-      subject.log('three', kind: :fake)
-      expect(subject.messages).to contain_exactly(hash_including(kind: :fake))
-    end
-  end
-
-  context '.deprecation' do
-    it 'calls Chef::Log.warn' do
-      subject.deprecation('hello')
-      expect(log).to have_received(:warn).with('hello')
-    end
-
-    it 'adds the kind :deprecation' do
-      subject.deprecation('basic')
-      expect(subject.messages).to contain_exactly(hash_including(kind: :deprecation))
-    end
-  end
-
-  context '.warning' do
-    it 'calls Chef::Log.warn' do
-      subject.warning('hello')
-      expect(log).to have_received(:warn).with('hello')
-    end
-
-    it 'adds the kind :warning' do
-      subject.warning('basic')
-      expect(subject.messages).to contain_exactly(hash_including(kind: :warning))
-    end
-  end
-
-  context '.debug' do
-    it 'calls Chef::Log.debug' do
-      subject.debug('hello')
-      expect(log).to have_received(:debug).with('hello')
-    end
-
-    it 'adds the kind :debug' do
-      subject.debug('basic')
-      expect(subject.messages).to contain_exactly(hash_including(kind: :debug))
-    end
-  end
-
-  context '.note' do
-    it 'adds the kind :note' do
-      subject.note('basic')
-      expect(subject.messages).to contain_exactly(hash_including(kind: :note))
-    end
-  end
-
-  context '.report' do
-    it 'prints nothing if nothing happened' do
-      expect { subject.report }.not_to output.to_stdout
-    end
-
-    it 'prints a removal header, then removal' do
-      subject.removal('one')
-      expect { subject.report }.to output(/\nRemovals:\none/).to_stdout
-    end
-
-    it 'prints a removal header, then removals' do
-      subject.removal('one')
-      subject.removal('two')
-      expect { subject.report }.to output(/\nRemovals:\none\ntwo/).to_stdout
-    end
-
-    it 'prints a deprecation header, then deprecation' do
-      subject.deprecation('one')
-      expect { subject.report }.to output(/\nDeprecations:\none/).to_stdout
-    end
-
-    it 'prints a deprecation header, then deprecations' do
-      subject.deprecation('one')
-      subject.deprecation('two')
-      expect { subject.report }.to output(/\nDeprecations:\none\ntwo/).to_stdout
-    end
-
-    it 'prints a warning header, then warning' do
-      subject.warning('one')
-      expect { subject.report }. to output(/\nWarnings:\none/).to_stdout
-    end
-
-    it 'prints a warning header, then warnings' do
-      subject.warning('one')
-      subject.warning('two')
-      expect { subject.report }.to output(/\nWarnings:\none\ntwo/).to_stdout
-    end
-
-    it 'prints a note header, then note' do
-      subject.note('one')
-      expect { subject.report }. to output(/\nNotes:\none/).to_stdout
-    end
-
-    it 'prints a note header, then notes' do
-      subject.note('one')
-      subject.note('two')
-      expect { subject.report }.to output(/\nNotes:\none\ntwo/).to_stdout
-    end
-
-    it 'prints a removal header, removals, deprecation header, deprecations, warning header, warnings, note header then notes' do
-      subject.removal('one')
-      subject.removal('two')
-      subject.deprecation('three')
-      subject.deprecation('four')
-      subject.warning('five')
-      subject.warning('six')
-      subject.note('seven')
-      subject.note('eight')
-
-      output = <<~EOS
-
-        Removals:
-        one
-        two
-
-
-        Deprecations:
-        three
-        four
-
-
-        Warnings:
-        five
-        six
-
-
-        Notes:
-        seven
-        eight
-
-      EOS
-
-      expect { subject.report }
-        .to output(output).to_stdout
-    end
-  end
-end
diff --git a/spec/libraries/output_helper_spec.rb b/spec/libraries/output_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvb3V0cHV0X2hlbHBlcl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/libraries/output_helper_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-require 'spec_helper'
-require_relative '../../files/gitlab-cookbooks/package/libraries/helpers/output_helper'
-
-RSpec.describe OutputHelper do
-  include OutputHelper
-
-  describe "#quote" do
-    context 'handling nil' do
-      it 'should return nil' do
-        expect(quote(nil)).to eq(nil)
-      end
-    end
-
-    context 'handling numbers' do
-      it 'should cooerce numbers to strings' do
-        result = quote(42)
-        expect(result).to be_instance_of(String)
-        expect(result).to eq('"42"')
-      end
-    end
-
-    context 'roundtripping values via YAML' do
-      values = [
-        "foo",
-        # single quotes
-        "foo'",
-        "'foo",
-        "fo'o",
-        "'foo'",
-        # double quotes
-        'foo"',
-        '"foo',
-        'fo"o',
-        '"foo"',
-        # newlines
-        "foo\n",
-        "\nfoo",
-        "fo\no",
-        "\nfoo\n",
-        # tabs
-        "foo\t",
-        "\tfoo",
-        "fo\to",
-        "\tfoo\t",
-        # spaces
-        "foo ",
-        " foo",
-        "fo o",
-        " foo ",
-        # unicode snowman
-        "foo☃",
-        "☃foo",
-        "fo☃o",
-        "☃foo☃",
-      ]
-      values.each do |value|
-        it "should YAML roundtrip #{value.inspect}" do
-          # create a document with structure { 'value' : quote(value) }
-          yaml_document = "---\nvalue: #{quote(value)}"
-          document = YAML.safe_load(yaml_document)
-          expect(document['value']).to eq(value)
-        end
-      end
-    end
-  end
-end
diff --git a/spec/libraries/pg_version_spec.rb b/spec/libraries/pg_version_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvcGdfdmVyc2lvbl9zcGVjLnJi..0000000000000000000000000000000000000000
--- a/spec/libraries/pg_version_spec.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe PGVersion do
-  context '.parse' do
-    it 'returns a PGVersion class when provided a value' do
-      expect(PGVersion.parse('string')).to be_a(described_class)
-    end
-
-    it 'returns nil when not passed a value' do
-      expect(PGVersion.parse(nil)).to be_nil
-    end
-  end
-
-  context 'postgres 9.6' do
-    it '.valid? validates version strings' do
-      expect(PGVersion.new('9.6.0').valid?).to be true
-      expect(PGVersion.new('9.6').valid?).to be true
-      expect(PGVersion.new('9').valid?).to be false
-      expect(PGVersion.new('9.6.0.0').valid?).to be false
-      expect(PGVersion.new('i9').valid?).to be false
-      expect(PGVersion.new('i9.6').valid?).to be false
-      expect(PGVersion.new('9.6.0i').valid?).to be false
-      expect(PGVersion.new('i9.6.0').valid?).to be false
-    end
-
-    it 'parses the correct version parts from a MAJOR only version' do
-      version = PGVersion.new('9.6')
-
-      expect(version.major).to eq '9.6'
-      expect(version.minor).to be_nil
-    end
-
-    it 'parses the correct major version from a full version' do
-      version = PGVersion.new('9.6.10')
-
-      expect(version.major).to eq '9.6'
-      expect(version.minor).to eq '10'
-    end
-  end
-
-  context 'postgres 10+' do
-    it '.valid? validates version strings' do
-      expect(PGVersion.new('10.0.0').valid?).to be true
-      expect(PGVersion.new('11.0').valid?).to be true
-      expect(PGVersion.new('10').valid?).to be true
-      expect(PGVersion.new('10.0.0.0').valid?).to be false
-      expect(PGVersion.new('i10').valid?).to be false
-      expect(PGVersion.new('i11.0').valid?).to be false
-      expect(PGVersion.new('10.0.0i').valid?).to be false
-      expect(PGVersion.new('i10.0.0').valid?).to be false
-    end
-
-    it 'parses the correct version parts from a MAJOR only version' do
-      version = PGVersion.new('10')
-      version_new = PGVersion.new('12')
-
-      expect(version.major).to eq '10'
-      expect(version.minor).to be_nil
-      expect(version_new.major).to eq '12'
-      expect(version_new.minor).to be_nil
-    end
-
-    it 'parses the correct major version from a full version' do
-      version = PGVersion.new('10.5')
-      version_new = PGVersion.new('12.122')
-
-      expect(version.major).to eq '10'
-      expect(version.minor).to eq '5'
-      expect(version_new.major).to eq '12'
-      expect(version_new.minor).to eq '122'
-    end
-
-    it 'ignores patch version' do
-      version = PGVersion.new('10.5.3')
-      version_new = PGVersion.new('12.122.3')
-
-      expect(version.major).to eq '10'
-      expect(version.minor).to eq '5'
-      expect(version_new.major).to eq '12'
-      expect(version_new.minor).to eq '122'
-    end
-  end
-end
diff --git a/spec/libraries/redis_helper_spec.rb b/spec/libraries/redis_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvcmVkaXNfaGVscGVyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/libraries/redis_helper_spec.rb
+++ /dev/null
@@ -1,251 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe RedisHelper do
-  let(:chef_run) { converge_config }
-  subject { described_class.new(chef_run.node) }
-
-  context '#redis_params' do
-    context 'without sentinels' do
-      before { allow(Gitlab).to receive(:[]).and_call_original }
-
-      it 'returns correct parameters' do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            redis_host: 'redis.example.com',
-            redis_port: 8888,
-            redis_password: 'mypass'
-          }
-        )
-        expect(subject.redis_params).to eq(['redis.example.com', 8888, 'mypass'])
-        expect(subject.redis_params(support_sentinel_groupname: false)).to eq(['redis.example.com', 8888, 'mypass'])
-      end
-    end
-
-    context 'with sentinels' do
-      before { allow(Gitlab).to receive(:[]).and_call_original }
-
-      it 'returns correct parameters' do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            redis_host: 'redis.example.com',
-            redis_port: 8888,
-            redis_password: 'mypass',
-            redis_sentinels: [
-              { 'host' => 'sentinel1.example.com', 'port' => '12345' },
-              { 'host' => 'sentinel2.example.com', 'port' => '12345' }
-            ]
-          },
-          redis: {
-            master_name: 'master_from.redis.master_name',
-            master_password: 'password_from.redis.master_password'
-          }
-        )
-        expect(subject.redis_params).to eq(['master_from.redis.master_name', 6379, 'password_from.redis.master_password'])
-        expect(subject.redis_params(support_sentinel_groupname: false)).to eq(['redis.example.com', 8888, 'mypass'])
-      end
-    end
-  end
-
-  context '#redis_url' do
-    context 'with default configuration' do
-      it 'returns a unix socket' do
-        expect(subject.redis_url.to_s).to eq('unix:/var/opt/gitlab/redis/redis.socket')
-      end
-    end
-
-    context 'with custom configuration' do
-      before { allow(Gitlab).to receive(:[]).and_call_original }
-
-      it 'returns a Redis URL when redis_host is defined' do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            redis_host: 'redis.example.com'
-          }
-        )
-
-        expect(subject.redis_url.to_s).to eq('redis://redis.example.com/')
-      end
-
-      it 'returns a Redis URL with port when a non default port is defined' do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            redis_host: 'redis.example.com',
-            redis_port: 8888
-          }
-        )
-
-        expect(subject.redis_url.to_s).to eq('redis://redis.example.com:8888/')
-      end
-
-      it 'returns a Redis URL with database when specified' do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            redis_host: 'redis.example.com',
-            redis_database: 0
-          }
-        )
-
-        expect(subject.redis_url.to_s).to eq('redis://redis.example.com/0')
-      end
-
-      it 'returns a Redis URL with password when specified' do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            redis_host: 'redis.example.com',
-            redis_password: 'mypass'
-          }
-        )
-
-        expect(subject.redis_url.to_s).to eq('redis://:mypass@redis.example.com/')
-      end
-
-      it 'returns a Redis URL with an encoded password' do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            redis_host: 'redis.example.com',
-            redis_password: '#223'
-          }
-        )
-
-        expect(subject.redis_url.to_s).to eq('redis://:%23223@redis.example.com/')
-      end
-
-      it 'returns a Redis URL with password, port and database when all specified' do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            redis_host: 'redis.example.com',
-            redis_password: 'mypass',
-            redis_database: 0,
-            redis_port: 8888
-          }
-        )
-
-        expect(subject.redis_url.to_s).to eq('redis://:mypass@redis.example.com:8888/0')
-      end
-
-      it 'returns an SSL Redis URL with password, port and database when all specified' do
-        stub_gitlab_rb(
-          gitlab_rails: {
-            redis_host: 'redis.example.com',
-            redis_password: 'mypass',
-            redis_database: 0,
-            redis_port: 8888,
-            redis_ssl: true
-          }
-        )
-
-        expect(subject.redis_url.to_s).to eq('rediss://:mypass@redis.example.com:8888/0')
-      end
-    end
-  end
-
-  describe '#running_version' do
-    let(:redis_cli_output) do
-      <<~MSG
-        # Server
-        redis_version:3.2.12
-        redis_git_sha1:00000000
-        redis_git_dirty:0
-        redis_build_id:e16da30f4a0a7845
-        redis_mode:standalone
-        os:Linux 4.15.0-58-generic x86_64
-      MSG
-    end
-
-    before do
-      # Un-doing the stub added in chef_helper
-      allow_any_instance_of(described_class).to receive(:running_version).and_call_original
-      allow(Gitlab).to receive(:[]).and_call_original
-      allow(VersionHelper).to receive(:version).with(/redis-cli.*INFO/).and_return(redis_cli_output)
-    end
-
-    context 'when redis is not running' do
-      it 'returns nil' do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('redis').and_return(false)
-
-        expect(subject.running_version).to be_nil
-      end
-    end
-
-    context 'when redis is running' do
-      before do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('redis').and_return(true)
-      end
-      context 'over socket' do
-        it 'calls VersionHelper.version with correct arguments' do
-          expect(VersionHelper).to receive(:version).with('/opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket INFO')
-
-          subject.running_version
-        end
-      end
-
-      context 'over TCP' do
-        before do
-          stub_gitlab_rb(
-            redis: {
-              bind: '0.0.0.0',
-              port: 6379
-            }
-          )
-        end
-
-        it 'calls VersionHelper.version with correct arguments' do
-          expect(VersionHelper).to receive(:version).with('/opt/gitlab/embedded/bin/redis-cli -h 0.0.0.0 -p 6379 INFO')
-
-          subject.running_version
-        end
-
-        context 'with a Redis password specified' do
-          before do
-            stub_gitlab_rb(
-              redis: {
-                bind: '0.0.0.0',
-                port: 6379,
-                password: 'toomanysecrets'
-              }
-            )
-          end
-
-          it 'it passes password to the command' do
-            expect(VersionHelper).to receive(:version).with("/opt/gitlab/embedded/bin/redis-cli -h 0.0.0.0 -p 6379 -a 'toomanysecrets' INFO")
-
-            subject.running_version
-          end
-        end
-      end
-
-      it 'parses version from redis-cli output properly' do
-        expect(subject.running_version).to eq('3.2.12')
-      end
-    end
-  end
-
-  describe '#installed_version' do
-    let(:redis_server_output) { 'Redis server v=3.2.12 sha=00000000:0 malloc=jemalloc-4.0.3 bits=64 build=e16da30f4a0a7845' }
-
-    before do
-      # Un-doing the stub added in chef_helper
-      allow_any_instance_of(described_class).to receive(:installed_version).and_call_original
-      allow(Gitlab).to receive(:[]).and_call_original
-      allow(VersionHelper).to receive(:version).with(/redis-server --version/).and_return(redis_server_output)
-    end
-
-    context 'when redis is not running' do
-      it 'returns nil' do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('redis').and_return(false)
-
-        expect(subject.installed_version).to be_nil
-      end
-    end
-
-    context 'when redis is running' do
-      before do
-        allow_any_instance_of(OmnibusHelper).to receive(:service_up?).with('redis').and_return(true)
-      end
-
-      it 'parses redis-server output properly' do
-        expect(subject.installed_version).to eq('3.2.12')
-      end
-    end
-  end
-end
diff --git a/spec/libraries/redis_spec.rb b/spec/libraries/redis_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvcmVkaXNfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/libraries/redis_spec.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-# This spec is to test the Redis helper and whether the values parsed
-# are the ones we expect
-require 'chef_helper'
-
-RSpec.describe 'Redis' do
-  let(:chef_run) { converge_config(is_ee: true) }
-  let(:node) { chef_run.node }
-  subject { ::Redis }
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context '.parse_variables' do
-    it 'delegates to parse_redis_settings' do
-      expect(subject).to receive(:parse_redis_settings)
-
-      subject.parse_variables
-    end
-  end
-
-  context '.parse_redis_settings' do
-    context 'when no customization is made' do
-      it 'keeps unixsocket' do
-        expect(node['gitlab']['gitlab-rails']['unixsocket']).not_to eq false
-
-        subject.parse_redis_settings
-      end
-    end
-
-    context 'within redis host and port synchronization with gitlab_rails' do
-      let(:redis_host) { '1.2.3.4' }
-      let(:redis_port) { 6370 }
-
-      context 'when not using sentinels' do
-        before do
-          stub_gitlab_rb(
-            redis: {
-              bind: redis_host,
-              port: redis_port
-            }
-          )
-        end
-
-        it 'disables unix socket when redis tcp params are defined' do
-          expect(node['redis']['unixsocket']).to eq false
-
-          subject.parse_redis_settings
-        end
-
-        it 'expects redis_host to match bind value from redis' do
-          expect(node['gitlab']['gitlab-rails']['redis_host']).to eq redis_host
-
-          subject.parse_redis_settings
-        end
-
-        it 'expects redis_port to match port value from redis' do
-          expect(node['gitlab']['gitlab-rails']['redis_port']).to eq redis_port
-
-          subject.parse_redis_settings
-        end
-      end
-
-      context 'when using sentinels' do
-        let(:master_name) { 'gitlabredis' }
-        let(:master_pass) { 'PASSWORD' }
-
-        before do
-          stub_gitlab_rb(
-            redis: {
-              bind: redis_host,
-              port: redis_port,
-              master_name: master_name,
-              master_password: master_pass
-            },
-            gitlab_rails: {
-              redis_sentinels: [
-                { host: '1.2.3.4', port: '26379' }
-              ]
-            }
-          )
-        end
-
-        it 'disables unix socket when sentinel params are defined' do
-          expect(node['redis']['unixsocket']).to eq false
-
-          subject.parse_redis_settings
-        end
-      end
-
-      context 'when with redis_replica_role enabled' do
-        before do
-          stub_gitlab_rb(
-            redis_replica_role: {
-              enable: true
-            },
-            redis: {
-              master_ip: '10.0.0.0',
-              master_port: 6379,
-              master_password: 'PASSWORD'
-            }
-          )
-        end
-
-        it 'defined redis master as false' do
-          expect(node['redis']['master']).to eq false
-        end
-      end
-    end
-
-    context 'within redis password and master_password' do
-      let(:redis_password) { 'PASSWORD' }
-
-      context 'when master_role is enabled' do
-        before do
-          stub_gitlab_rb(
-            redis_master_role: {
-              enable: true
-            },
-            redis: {
-              password: redis_password,
-              master_ip: '10.0.0.0'
-            }
-          )
-        end
-
-        it 'master_password is autofilled based on redis current password' do
-          expect(node['redis']['master_password']).to eq redis_password
-        end
-      end
-
-      context 'when redis is a replica' do
-        before do
-          stub_gitlab_rb(
-            redis: {
-              master: false,
-              password: redis_password,
-              master_ip: '10.0.0.0'
-            }
-          )
-        end
-
-        it 'master_password is autofilled based on redis current password' do
-          expect(node['redis']['master_password']).to eq redis_password
-        end
-      end
-
-      context 'when sentinel is enabled' do
-        before do
-          stub_gitlab_rb(
-            sentinel: {
-              enable: true
-            },
-            redis: {
-              password: redis_password,
-              master_ip: '10.0.0.0'
-            }
-          )
-        end
-
-        it 'master_password is autofilled based on redis current password' do
-          expect(node['redis']['master_password']).to eq redis_password
-        end
-
-        context 'announce_ip is defined' do
-          let(:redis_port) { 6379 }
-          let(:redis_announce_ip) { '10.10.10.10' }
-          before do
-            stub_gitlab_rb(
-              redis_sentinel_role: {
-                enable: true,
-              },
-              redis: {
-                master_password: redis_password,
-                master_ip: '10.0.0.0',
-                port: redis_port,
-                announce_ip: redis_announce_ip
-              }
-            )
-          end
-
-          it 'Redis announce_port is autofilled based on redis current port' do
-            expect(node['redis']['announce_port']).to eq redis_port
-
-            subject.parse_redis_settings
-          end
-        end
-      end
-
-      context 'when both password and master_password are present' do
-        let(:master_password) { 'anotherPASSWORD' }
-        before do
-          stub_gitlab_rb(
-            redis_replica_role: {
-              enable: true
-            },
-            redis: {
-              password: redis_password,
-              master_ip: '10.0.0.0',
-              master_password: master_password
-            }
-          )
-        end
-
-        it 'keeps user specified master_password' do
-          expect(node['redis']['master_password']).to eq master_password
-        end
-      end
-    end
-  end
-end
diff --git a/spec/libraries/redis_uri_spec.rb b/spec/libraries/redis_uri_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvcmVkaXNfdXJpX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/libraries/redis_uri_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# This spec is to test the Redis helper and whether the values parsed
-# are the ones we expect
-
-require 'spec_helper'
-require_relative '../../files/gitlab-cookbooks/gitlab/libraries/redis_uri.rb'
-
-RSpec.describe URI::Redis do
-  subject { URI('redis://localhost') }
-
-  it { is_expected.to be_a(described_class) }
-
-  context '.parse' do
-    it 'delegates to URI.parse' do
-      expect(URI).to receive(:parse).with('redis://localhost')
-
-      described_class.parse('redis://localhost')
-    end
-  end
-
-  context 'port' do
-    it 'has a default port' do
-      expect(subject.default_port).to eq 6379
-    end
-
-    it 'outputs default port when none is defined' do
-      expect(subject.port).to eq subject.default_port
-    end
-
-    it 'does not include port when it is default port' do
-      expect(subject.to_s).to eq 'redis://localhost'
-    end
-
-    it 'includes port when it is different than default' do
-      subject.port = 6378
-      expect(subject.to_s).to eq 'redis://localhost:6378'
-    end
-  end
-
-  context 'with password' do
-    before { subject.password = 'password' }
-
-    it 'allows password to be defined' do
-      expect(subject.password).to eq 'password'
-    end
-
-    it 'renders url with password' do
-      expect(subject.to_s).to eq 'redis://:password@localhost'
-    end
-  end
-
-  context 'with non-alphanumeric password' do
-    let(:password) { "&onBsidv6#XeKFd}=BDDyRrv" }
-    let(:escaped) { CGI.escape(password) }
-
-    it 'rejects unencoded passwords' do
-      expect { subject.password = password }.to raise_error(URI::InvalidComponentError)
-    end
-
-    it 'allows encoded passwords' do
-      subject.password = escaped
-
-      expect(subject.password).to eq(escaped)
-    end
-
-    it 'renders url with escaped password' do
-      subject.password = escaped
-
-      expect(subject.to_s).to eq "redis://:#{escaped}@localhost"
-    end
-  end
-
-  context 'without password' do
-    it 'renders url without authentication division characters' do
-      expect(subject.to_s).to eq 'redis://localhost'
-    end
-  end
-end
diff --git a/spec/libraries/registry_spec.rb b/spec/libraries/registry_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvcmVnaXN0cnlfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/libraries/registry_spec.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'chef_helper'
-require_relative '../../files/gitlab-cookbooks/gitlab/libraries/registry'
-
-RSpec.describe Registry do
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context 'registry is disabled' do
-    before do
-      stub_gitlab_rb(
-        registry: {
-          enabled: false
-        }
-      )
-    end
-
-    it 'should return false' do
-      expect(described_class.auto_enable).to be_falsey
-    end
-  end
-
-  context 'registry_external_url is set' do
-    before do
-      stub_gitlab_rb(
-        registry_external_url: 'https://registry.example.com'
-      )
-    end
-
-    it 'should return false' do
-      expect(described_class.auto_enable).to be_falsey
-    end
-  end
-
-  context 'lets encrypt is not enabled' do
-    before do
-      stub_gitlab_rb(
-        letsencrypt: {
-          enable: false
-        }
-      )
-    end
-
-    it 'should return false' do
-      expect(described_class.auto_enable).to be_falsey
-    end
-  end
-
-  context 'external_url is a relative url' do
-    before do
-      stub_gitlab_rb(
-        external_url: 'https://registry.example.com/path'
-      )
-    end
-
-    it 'should return false' do
-      expect(described_class.auto_enable).to be_falsey
-    end
-  end
-end
diff --git a/spec/libraries/roles_spec.rb b/spec/libraries/roles_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvcm9sZXNfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/libraries/roles_spec.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe 'GitLabRoles' do
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow(Services).to receive(:enable_group).and_call_original
-  end
-
-  after do
-    # Disable all roles after each test to clean the environment
-    Gitlab.available_roles.each { |name, _value| Gitlab["#{name}_role"]['enable'] = false }
-  end
-
-  describe 'roles config array' do
-    it 'enables roles listed in the roles array' do
-      stub_gitlab_rb(roles: %w(application_role geo_primary_role))
-
-      Gitlab.load_roles
-
-      expect(Gitlab['application_role']['enable']).to be true
-      expect(Gitlab['geo_primary_role']['enable']).to be true
-    end
-
-    it 'supports providing a single role as a string' do
-      stub_gitlab_rb(roles: 'application_role')
-
-      Gitlab.load_roles
-
-      expect(Gitlab['application_role']['enable']).to be true
-    end
-
-    it 'handles users specifying hyphens instead of underscores' do
-      stub_gitlab_rb(roles: ['geo-primary-role'])
-
-      Gitlab.load_roles
-
-      expect(Gitlab['geo_primary_role']['enable']).to be true
-    end
-
-    it 'throws errors when an invalid role is used' do
-      stub_gitlab_rb(roles: ['some_invalid_role'])
-
-      expect { Gitlab.load_roles }.to raise_error(RuntimeError, /invalid roles have been set/)
-    end
-  end
-
-  describe 'DefaultRole' do
-    before do
-      allow(DefaultRole).to receive(:load_role).and_call_original
-      allow(GeoPrimaryRole).to receive(:load_role).and_call_original
-    end
-
-    it 'enables the default services when no other roles are active' do
-      Gitlab.load_roles
-
-      expect(Services).to have_received(:enable_group).with(Services::DEFAULT_GROUP, anything).once
-    end
-
-    it 'enables the default services when no "service managed" roles are active' do
-      stub_gitlab_rb(geo_primary_role: { enable: true })
-
-      Gitlab.load_roles
-
-      expect(Services).to have_received(:enable_group).with(Services::DEFAULT_GROUP, anything).once
-      expect(GeoPrimaryRole).to have_received(:load_role)
-    end
-
-    it 'leaves the default services disabled when another role is active' do
-      stub_gitlab_rb(application_role: { enable: true })
-
-      Gitlab.load_roles
-
-      expect(DefaultRole).to have_received(:load_role)
-      expect(Services).not_to have_received(:enable_group).with(Services::DEFAULT_GROUP, anything)
-    end
-
-    it 'leaves rails roles disabled when requested to maintain backward compat' do
-      stub_gitlab_rb(gitlab_rails: { enable: false })
-
-      Gitlab.load_roles
-
-      expect(Services).to have_received(:enable_group).with(Services::DEFAULT_GROUP, hash_including(except: ['rails'])).once
-    end
-  end
-
-  describe 'ApplicationRole' do
-    before do
-      allow(ApplicationRole).to receive(:load_role).and_call_original
-    end
-
-    it 'enables the rails services' do
-      stub_gitlab_rb(application_role: { enable: true })
-
-      Gitlab.load_roles
-
-      expect(ApplicationRole).to have_received(:load_role)
-      expect(Gitlab['gitlab_rails']['enable']).to eq true
-      expect(Services).to have_received(:enable_group).with('rails').once
-    end
-  end
-
-  describe 'MonitoringRole' do
-    before do
-      allow(MonitoringRole).to receive(:load_role).and_call_original
-    end
-
-    it 'enables the monitoring services' do
-      stub_gitlab_rb(roles: %w(monitoring_role))
-
-      Gitlab.load_roles
-
-      expect(MonitoringRole).to have_received(:load_role)
-      expect(Services).to have_received(:enable_group).with('monitoring_role').once
-    end
-  end
-end
diff --git a/spec/libraries/sentinel_helper_spec.rb b/spec/libraries/sentinel_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvc2VudGluZWxfaGVscGVyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/libraries/sentinel_helper_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe SentinelHelper do
-  let(:chef_run) { converge_config(is_ee: true) }
-  subject { described_class.new(chef_run.node) }
-  before { allow(Gitlab).to receive(:[]).and_call_original }
-
-  context '#myid' do
-    context 'when retrieving from config' do
-      it 'fails when myid is not 40 hex-characters long' do
-        stub_gitlab_rb(
-          sentinel: {
-            myid: 'wrongid'
-          }
-        )
-
-        expect { subject.myid }.to raise_error RuntimeError
-      end
-
-      it 'works when myid is 40 hex-characters long' do
-        stub_gitlab_rb(
-          sentinel: {
-            myid: '1234567890abcdef1234567890abcdef12345678'
-          }
-        )
-
-        expect { subject.myid }.not_to raise_error
-      end
-    end
-
-    context 'when no config is defined' do
-      let(:myid) { 'abcdef1234567890abcdef1234567890abcdef1' }
-
-      it 'generates a random myid' do
-        expect(subject.myid).not_to be_empty
-      end
-
-      it 'persist generated value into JSON file' do
-        allow(subject).to receive(:generate_myid).at_least(:once) { myid }
-
-        expect(subject).to receive(:save_to_file).with({ 'myid' => myid })
-        subject.myid
-      end
-    end
-  end
-end
diff --git a/spec/libraries/sentinel_spec.rb b/spec/libraries/sentinel_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvc2VudGluZWxfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/libraries/sentinel_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-# This spec is to test the Sentinel helper and whether the values parsed
-# are the ones we expect
-require 'chef_helper'
-
-RSpec.describe 'Sentinel' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::config') }
-  let(:node) { chef_run.node }
-  subject { ::Sentinel }
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context '.parse_variables' do
-    context 'When sentinel is enabled' do
-      before do
-        stub_gitlab_rb(
-          redis_sentinel_role: {
-            enable: true,
-          }
-        )
-      end
-      it 'delegates to parse_sentinel_settings' do
-        expect(subject).to receive(:parse_sentinel_settings)
-
-        subject.parse_variables
-      end
-
-      context 'when redis announce_ip is defined' do
-        let(:redis_master_ip) { '1.1.1.1' }
-        let(:redis_announce_ip) { '10.10.10.10' }
-        before do
-          stub_gitlab_rb(
-            redis: {
-              master_ip: redis_master_ip,
-              announce_ip: redis_announce_ip
-            }
-          )
-        end
-
-        it 'Sentinel announce_ip is autofilled based on redis announce_ip' do
-          expect(node['gitlab']['sentinel']['announce_ip']).to eq redis_announce_ip
-
-          subject.parse_sentinel_settings
-        end
-      end
-
-      context 'when redis announce_port is defined' do
-        let(:redis_announce_port) { 6370 }
-        let(:sentinel_port) { 26370 }
-        before do
-          stub_gitlab_rb(
-            sentinel: {
-              port: sentinel_port
-            },
-            redis: {
-              announce_port: redis_announce_port
-            }
-          )
-        end
-        it 'Sentinel announce_port is autofilled based on sentinel port' do
-          expect(node['gitlab']['sentinel']['announce_port']).to eq sentinel_port
-
-          subject.parse_sentinel_settings
-        end
-      end
-    end
-  end
-end
diff --git a/spec/libraries/services_spec.rb b/spec/libraries/services_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvc2VydmljZXNfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/libraries/services_spec.rb
+++ /dev/null
@@ -1,316 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe Services do
-  before { allow(Gitlab).to receive(:[]).and_call_original }
-
-  describe 'when using the gitlab cookbook' do
-    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
-
-    it 'returns the gitlab service list' do
-      chef_run
-      expect(Services.service_list).to have_key('puma')
-      expect(Services.service_list).not_to have_key('sentinel')
-    end
-  end
-
-  describe 'when using the gitlab-ee cookbook' do
-    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-    it 'returns the gitlab service list including gitlab-ee items' do
-      chef_run
-      expect(Services.service_list).to have_key('puma')
-      expect(Services.service_list).to have_key('sentinel')
-    end
-  end
-
-  it 'uses the default template when populating service information' do
-    expect(Services::Config.send(:service, ['test_service'])).to eq({ groups: [] })
-  end
-
-  describe 'service' do
-    let(:runner) { ChefSpec::SoloRunner.new }
-    let(:chef_run) { runner.converge('gitlab::config') }
-    let(:node) { runner.node }
-
-    before { Services.add_services('gitlab', Services::BaseServices.list) }
-
-    context 'when using user values that conflict with service settings' do
-      let!(:runner) { ChefSpec::SoloRunner.new { |node| Gitlab[:node] = node } }
-
-      it 'node service settings are overridden by gitlab.rb changes' do
-        stub_gitlab_rb(redis: { enable: true }, mattermost: { enable: false })
-        Services.disable('redis')
-        Services.enable('mattermost')
-        runner.converge('gitlab::config')
-
-        expect(node['redis']['enable']).to be true
-        expect(node['mattermost']['enable']).to be false
-      end
-    end
-
-    context 'when enable/disable is passed a single service' do
-      it 'sets the correct values' do
-        chef_run
-        Services.disable('redis')
-        expect(node['redis']['enable']).to be false
-
-        Services.enable('mattermost')
-        expect(node['mattermost']['enable']).to be true
-      end
-
-      it 'supports exceptions' do
-        chef_run
-        Services.disable('mattermost')
-        Services.enable('mattermost', except: 'mattermost')
-        expect(node['mattermost']['enable']).to be false
-
-        Services.enable('redis')
-        Services.disable('redis', except: 'redis')
-        expect(node['redis']['enable']).to be true
-      end
-    end
-
-    context 'when enable/disable is passed multiple services' do
-      before { chef_run }
-
-      it 'sets the correct values' do
-        Services.disable('redis', 'postgresql', 'gitaly')
-        expect(node['redis']['enable']).to be false
-        expect(node['postgresql']['enable']).to be false
-        expect(node['gitaly']['enable']).to be false
-
-        Services.enable('mattermost', 'registry', 'mailroom')
-        expect(node['mattermost']['enable']).to be true
-        expect(node['registry']['enable']).to be true
-        expect(node['gitlab']['mailroom']['enable']).to be true
-      end
-
-      it 'supports single exceptions' do
-        Services.disable('registry')
-        Services.enable('mattermost', 'registry', 'mailroom', except: 'registry')
-        expect(node['mattermost']['enable']).to be true
-        expect(node['registry']['enable']).to be false
-        expect(node['gitlab']['mailroom']['enable']).to be true
-
-        Services.enable('postgresql')
-        Services.disable('redis', 'postgresql', 'gitaly', except: 'postgresql')
-        expect(node['redis']['enable']).to be false
-        expect(node['postgresql']['enable']).to be true
-        expect(node['gitaly']['enable']).to be false
-      end
-
-      it 'supports multiple exceptions' do
-        Services.disable('registry', 'mailroom')
-        Services.enable('mattermost', 'registry', 'mailroom', except: %w(registry mailroom))
-        expect(node['mattermost']['enable']).to be true
-        expect(node['registry']['enable']).to be false
-        expect(node['gitlab']['mailroom']['enable']).to be false
-
-        Services.enable('postgresql', 'gitaly')
-        Services.disable('redis', 'postgresql', 'gitaly', except: %w(postgresql gitaly))
-        expect(node['redis']['enable']).to be false
-        expect(node['postgresql']['enable']).to be true
-        expect(node['gitaly']['enable']).to be true
-      end
-
-      it 'ignores disable on system services' do
-        Services.disable('node_exporter')
-        expect(node['monitoring']['node-exporter']['enable']).to be true
-      end
-
-      it 'allows forced disable on system services' do
-        Services.disable('node_exporter', include_system: true)
-        expect(node['monitoring']['node-exporter']['enable']).to be false
-      end
-    end
-
-    context 'when passed single exception' do
-      before { chef_run }
-
-      it 'enables all others' do
-        Services.disable('registry')
-        Services.enable(Services::ALL_SERVICES, except: 'registry')
-        expect(node['redis']['enable']).to be true
-        expect(node['postgresql']['enable']).to be true
-        expect(node['mattermost']['enable']).to be true
-        expect(node['registry']['enable']).to be false
-      end
-
-      it 'disables all others' do
-        Services.enable('redis')
-        Services.disable(Services::ALL_SERVICES, except: 'redis')
-        expect(node['redis']['enable']).to be true
-        expect(node['postgresql']['enable']).to be false
-        expect(node['mattermost']['enable']).to be false
-        expect(node['registry']['enable']).to be false
-      end
-    end
-
-    context 'when passed multiple exceptions' do
-      before { chef_run }
-
-      it 'enables all others' do
-        Services.disable('registry', 'mailroom')
-        Services.enable(Services::ALL_SERVICES, except: %w(registry mailroom))
-        expect(node['redis']['enable']).to be true
-        expect(node['postgresql']['enable']).to be true
-        expect(node['gitaly']['enable']).to be true
-        expect(node['mattermost']['enable']).to be true
-        expect(node['gitlab']['mailroom']['enable']).to be false
-        expect(node['registry']['enable']).to be false
-      end
-
-      it 'disables all others' do
-        Services.enable('postgresql', 'gitaly')
-        Services.disable(Services::ALL_SERVICES, except: %w(postgresql gitaly))
-        expect(node['redis']['enable']).to be false
-        expect(node['postgresql']['enable']).to be true
-        expect(node['gitaly']['enable']).to be true
-        expect(node['mattermost']['enable']).to be false
-        expect(node['gitlab']['mailroom']['enable']).to be false
-        expect(node['registry']['enable']).to be false
-      end
-    end
-  end
-
-  describe 'group' do
-    let(:runner) { ChefSpec::SoloRunner.new }
-    let(:chef_run) { runner.converge('gitlab::config') }
-    let(:node) { runner.node }
-
-    before { Services.add_services('gitlab', Services::BaseServices.list) }
-
-    context 'when using user values that conflict with service settings' do
-      cached!(:runner) { ChefSpec::SoloRunner.new { |node| Gitlab[:node] = node } }
-
-      it 'node service settings are overridden by gitlab.rb changes' do
-        stub_gitlab_rb(redis: { enable: true }, postgresql: { enable: false })
-        Services.disable_group('redis')
-        Services.enable_group('postgres')
-        runner.converge('gitlab::config')
-
-        expect(node['redis']['enable']).to be true
-        expect(node['mattermost']['enable']).to be false
-      end
-    end
-
-    context 'when enable_group/disable_group is passed a single group' do
-      before { chef_run }
-
-      it 'sets the correct values' do
-        Services.disable_group('redis')
-        expect(node['redis']['enable']).to be false
-        expect(node['monitoring']['redis-exporter']['enable']).to be false
-
-        Services.enable_group('rails')
-        expect(node['gitlab']['puma']['enable']).to be true
-        expect(node['monitoring']['gitlab-exporter']['enable']).to be true
-      end
-
-      it 'supports exceptions' do
-        Services.disable_group('monitoring')
-        Services.enable_group('rails', except: 'monitoring')
-        expect(node['monitoring']['gitlab-exporter']['enable']).to be false
-        expect(node['gitlab']['puma']['enable']).to be true
-
-        Services.enable_group('monitoring')
-        Services.disable_group('redis', except: 'monitoring')
-        expect(node['redis']['enable']).to be false
-        expect(node['monitoring']['redis-exporter']['enable']).to be true
-      end
-    end
-
-    context 'when enable/disable is passed multiple groups' do
-      before { chef_run }
-      it 'sets the correct values' do
-        Services.disable_group('redis', 'postgres')
-        expect(node['redis']['enable']).to be false
-        expect(node['postgresql']['enable']).to be false
-
-        Services.enable_group('rails', 'monitoring')
-        expect(node['monitoring']['redis-exporter']['enable']).to be true
-        expect(node['gitlab']['puma']['enable']).to be true
-      end
-
-      it 'supports single exceptions' do
-        Services.disable_group('monitoring')
-        Services.enable_group('redis', 'rails', except: 'monitoring')
-        expect(node['redis']['enable']).to be true
-        expect(node['gitlab']['puma']['enable']).to be true
-        expect(node['monitoring']['gitlab-exporter']['enable']).to be false
-        expect(node['monitoring']['redis-exporter']['enable']).to be false
-
-        Services.enable_group('postgres')
-        Services.disable_group('redis', 'monitoring', except: 'postgres')
-        expect(node['redis']['enable']).to be false
-        expect(node['monitoring']['postgres-exporter']['enable']).to be true
-        expect(node['monitoring']['prometheus']['enable']).to be false
-      end
-
-      it 'supports multiple exceptions' do
-        Services.disable_group('redis', Services::SYSTEM_GROUP, include_system: true)
-        Services.enable_group('rails', 'monitoring', except: ['redis', Services::SYSTEM_GROUP])
-        expect(node['monitoring']['redis-exporter']['enable']).to be false
-        expect(node['monitoring']['node-exporter']['enable']).to be false
-        expect(node['gitlab']['puma']['enable']).to be true
-
-        Services.enable_group('sidekiq', 'monitoring')
-        Services.disable_group('rails', 'postgres', except: %w(sidekiq monitoring))
-        expect(node['gitlab']['gitlab-workhorse']['enable']).to be false
-        expect(node['gitlab']['sidekiq']['enable']).to be true
-        expect(node['postgresql']['enable']).to be false
-        expect(node['monitoring']['postgres-exporter']['enable']).to be true
-      end
-
-      it 'ignores disable on system services' do
-        Services.enable_group(Services::SYSTEM_GROUP)
-        Services.disable_group(Services::SYSTEM_GROUP)
-        expect(node['logrotate']['enable']).to be true
-      end
-
-      it 'allows forced disable on system services' do
-        Services.enable_group(Services::SYSTEM_GROUP)
-        Services.disable_group(Services::SYSTEM_GROUP, include_system: true)
-        expect(node['logrotate']['enable']).to be false
-      end
-    end
-
-    context 'when passed single exception' do
-      before { chef_run }
-
-      it 'enables all others' do
-        Services.disable_group('monitoring')
-        Services.enable_group(Services::ALL_GROUPS, except: 'monitoring')
-        expect(node['gitlab']['puma']['enable']).to be true
-        expect(node['monitoring']['gitlab-exporter']['enable']).to be false
-      end
-
-      it 'disables all others' do
-        Services.enable_group('monitoring')
-        Services.disable_group(Services::ALL_GROUPS, except: 'monitoring')
-        expect(node['monitoring']['postgres-exporter']['enable']).to be true
-        expect(node['postgresql']['enable']).to be false
-      end
-    end
-
-    context 'when passed multiple exceptions' do
-      before { chef_run }
-
-      it 'enables all others' do
-        Services.disable_group('redis', 'rails')
-        Services.enable_group(Services::ALL_GROUPS, except: %w(redis rails))
-        expect(node['gitlab']['puma']['enable']).to be false
-        expect(node['monitoring']['node-exporter']['enable']).to be true
-        expect(node['monitoring']['redis-exporter']['enable']).to be false
-      end
-
-      it 'disables all others' do
-        Services.enable_group('redis', 'rails')
-        Services.disable_group(Services::ALL_GROUPS, except: %w(redis rails))
-        expect(node['monitoring']['prometheus']['enable']).to be false
-        expect(node['redis']['enable']).to be true
-        expect(node['gitlab']['sidekiq']['enable']).to be true
-      end
-    end
-  end
-end
diff --git a/spec/libraries/storage_directory_helper_spec.rb b/spec/libraries/storage_directory_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvc3RvcmFnZV9kaXJlY3RvcnlfaGVscGVyX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/libraries/storage_directory_helper_spec.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-require 'chef_helper'
-
-RSpec.describe StorageDirectoryHelper do
-  let(:success_shell) do
-    shell = instance_double(Mixlib::ShellOut)
-    allow(shell).to receive(:exitstatus).and_return(0)
-    shell
-  end
-
-  let(:fail_shell) do
-    shell = instance_double(Mixlib::ShellOut)
-    allow(shell).to receive(:exitstatus).and_return(1)
-    shell
-  end
-
-  before { allow(Gitlab).to receive(:[]).and_call_original }
-
-  describe :validate do
-    context 'owner provided' do
-      subject { ::StorageDirectoryHelper.new('git', nil, nil) }
-
-      it 'checks directory and owner and succeeds' do
-        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
-        expect(subject).to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args).and_return(success_shell)
-        expect(subject.validate('/tmp/validate')).to eq(true)
-      end
-
-      it 'fails when path is not a directory' do
-        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(fail_shell)
-        expect(subject).not_to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args)
-        expect(subject.validate('/tmp/validate')).to eq(false)
-      end
-
-      it 'fails when owner does not match' do
-        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
-        expect(subject).to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args).and_return(fail_shell)
-        expect(subject.validate('/tmp/validate')).to eq(false)
-      end
-    end
-
-    context 'owner and group provided' do
-      subject { ::StorageDirectoryHelper.new('git', 'root', nil) }
-
-      it 'checks directory, owner and group and succeeds' do
-        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
-        expect(subject).to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%U:%G' $(readlink -f /tmp/validate))\" = 'git:root' ]", any_args).and_return(success_shell)
-        expect(subject.validate('/tmp/validate')).to eq(true)
-      end
-
-      it 'fails when path is not a directory' do
-        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(fail_shell)
-        expect(subject).not_to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%U:%G' $(readlink -f /tmp/validate))\" = 'git:root' ]", any_args)
-        expect(subject.validate('/tmp/validate')).to eq(false)
-      end
-
-      it 'fails when group does not match' do
-        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
-        expect(subject).to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%U:%G' $(readlink -f /tmp/validate))\" = 'git:root' ]", any_args).and_return(fail_shell)
-        expect(subject.validate('/tmp/validate')).to eq(false)
-      end
-    end
-
-    context 'owner and permission mode provided' do
-      subject { ::StorageDirectoryHelper.new('git', nil, '700') }
-
-      it 'checks directory, owner and permissions and succeeds' do
-        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
-        expect(subject).to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args).and_return(success_shell)
-        expect(subject).to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%04a' $(readlink -f /tmp/validate) | grep -o '...$')\" = '700' ]", any_args).and_return(success_shell)
-        expect(subject.validate('/tmp/validate')).to eq(true)
-      end
-
-      it 'fails when path is not a directory' do
-        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(fail_shell)
-        expect(subject).not_to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%U:%G' $(readlink -f /tmp/validate))\" = 'git:root' ]", any_args)
-        expect(subject).not_to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%04a' $(readlink -f /tmp/validate) | grep -o '...$')\" = '700' ]", any_args)
-        expect(subject.validate('/tmp/validate')).to eq(false)
-      end
-
-      it 'fails when owner does not match' do
-        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
-        expect(subject).to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args).and_return(fail_shell)
-        expect(subject).not_to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%04a' $(readlink -f /tmp/validate) | grep -o '...$')\" = '700' ]", any_args)
-        expect(subject.validate('/tmp/validate')).to eq(false)
-      end
-
-      it 'fails when permissions do not match' do
-        expect(subject).to receive(:run_command).with("set -x && [ -d \"/tmp/validate\" ]", any_args).and_return(success_shell)
-        expect(subject).to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%U' $(readlink -f /tmp/validate))\" = 'git' ]", any_args).and_return(success_shell)
-        expect(subject).to receive(:run_command)
-          .with("set -x && [ \"$(stat --printf='%04a' $(readlink -f /tmp/validate) | grep -o '...$')\" = '700' ]", any_args).and_return(fail_shell)
-        expect(subject.validate('/tmp/validate')).to eq(false)
-      end
-    end
-  end
-end
diff --git a/spec/libraries/systemd_helper_spec.rb b/spec/libraries/systemd_helper_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvc3lzdGVtZF9oZWxwZXJfc3BlYy5yYg==..0000000000000000000000000000000000000000
--- a/spec/libraries/systemd_helper_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'spec_helper'
-require_relative '../../files/gitlab-cookbooks/package/libraries/helpers/systemd_helper'
-
-RSpec.describe SystemdHelper do
-  describe '#systemd_version' do
-    context 'when systemctl returns valid output' do
-      before do
-        valid_output = <<~OUTPUT
-          systemd 242 (242)
-          +PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT
-        OUTPUT
-
-        allow(IO).to receive(:popen).with(%w[systemctl --version]).and_return(valid_output)
-      end
-
-      it 'returns correct version' do
-        expect(described_class.systemd_version).to eq(242)
-      end
-    end
-
-    context 'when systemctl returns invalid output' do
-      before do
-        invalid_output = <<~OUTPUT
-          some invalid output
-        OUTPUT
-
-        allow(IO).to receive(:popen).with(%w[systemctl --version]).and_return(invalid_output)
-      end
-
-      it 'returns an insanely low version' do
-        expect(described_class.systemd_version).to eq(-999)
-      end
-    end
-  end
-end
diff --git a/spec/libraries/workhorse_spec.rb b/spec/libraries/workhorse_spec.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9saWJyYXJpZXMvd29ya2hvcnNlX3NwZWMucmI=..0000000000000000000000000000000000000000
--- a/spec/libraries/workhorse_spec.rb
+++ /dev/null
@@ -1,125 +0,0 @@
-# This spec is to test the Workhorse library and whether the values parsed
-# are the ones we expect
-require 'chef_helper'
-
-RSpec.describe 'GitlabWorkhorse' do
-  let(:node) { chef_run.node }
-  let(:user_socket) { '/where/is/my/ten/mm/socket_now' }
-  let(:user_sockets_directory) { '/where/is/my/ten/mm/sockets' }
-  let(:default_sockets_directory) { '/var/opt/gitlab/gitlab-workhorse/sockets' }
-  let(:default_socket) { '/var/opt/gitlab/gitlab-workhorse/sockets/socket' }
-  let(:tcp_listen_address) { '1.9.8.4' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-  end
-
-  context '.parse_variables' do
-    context 'listening on a tcp socket' do
-      let(:chef_run) { converge_config }
-
-      before do
-        stub_gitlab_rb(
-          gitlab_workhorse: {
-            listen_network: 'http',
-            listen_addr: tcp_listen_address
-          }
-        )
-      end
-
-      it 'uses the user configured TCP listen address' do
-        expect(node['gitlab']['gitlab-workhorse']['listen_addr']).to eq(tcp_listen_address)
-      end
-
-      it 'keeps the sockets_directory as nil' do
-        expect(node['gitlab']['gitlab-workhorse']['sockets_directory']).to eq(nil)
-      end
-    end
-
-    context 'listening on a unix socket' do
-      context 'using default configuration' do
-        let(:chef_run) { converge_config }
-
-        before do
-          stub_gitlab_rb(
-            gitlab_workhorse: {
-              listen_network: 'unix'
-            }
-          )
-        end
-
-        it 'uses the default sockets directory' do
-          expect(node['gitlab']['gitlab-workhorse']['sockets_directory']).to eq(default_sockets_directory)
-        end
-
-        it 'uses the default socket file path' do
-          expect(node['gitlab']['gitlab-workhorse']['listen_addr']).to eq(default_socket)
-        end
-      end
-
-      context 'only listen_addr is set' do
-        let(:chef_run) { converge_config }
-
-        before do
-          stub_gitlab_rb(
-            gitlab_workhorse: {
-              listen_network: 'unix',
-              listen_addr: user_socket
-            }
-          )
-        end
-
-        it 'uses the user configured listen address' do
-          expect(node['gitlab']['gitlab-workhorse']['listen_addr']).to eq(user_socket)
-        end
-
-        it 'keeps the sockets_directory as nil' do
-          expect(node['gitlab']['gitlab-workhorse']['sockets_directory']).to eq(nil)
-        end
-      end
-
-      context 'only sockets_directory is set' do
-        let(:chef_run) { converge_config }
-
-        before do
-          stub_gitlab_rb(
-            gitlab_workhorse: {
-              listen_network: 'unix',
-              sockets_directory: user_sockets_directory
-            }
-          )
-        end
-
-        it 'uses the user configured sockets directory' do
-          expect(node['gitlab']['gitlab-workhorse']['sockets_directory']).to eq(user_sockets_directory)
-        end
-
-        it 'creates a socket named socket in the user configured sockets directory' do
-          expect(node['gitlab']['gitlab-workhorse']['listen_addr']).to eq("#{user_sockets_directory}/socket")
-        end
-      end
-
-      context 'listen_addr and sockets_directory are both set' do
-        let(:chef_run) { converge_config }
-
-        before do
-          stub_gitlab_rb(
-            gitlab_workhorse: {
-              listen_network: 'unix',
-              listen_addr: user_socket,
-              sockets_directory: user_sockets_directory
-            }
-          )
-        end
-
-        it 'uses the user configured sockets directory' do
-          expect(node['gitlab']['gitlab-workhorse']['sockets_directory']).to eq(user_sockets_directory)
-        end
-
-        it 'creates a socket matching the configured listen_addr' do
-          expect(node['gitlab']['gitlab-workhorse']['listen_addr']).to eq(user_socket)
-        end
-      end
-    end
-  end
-end
diff --git a/spec/support/macros.rb b/spec/support/macros.rb
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L21hY3Jvcy5yYg==..120b23950e6893931eef61fecada8c9387ee8623_c3BlYy9zdXBwb3J0L21hY3Jvcy5yYg== 100644
--- a/spec/support/macros.rb
+++ b/spec/support/macros.rb
@@ -99,7 +99,7 @@
     # Return the full path for the spec fixtures folder
     # @return [String] full path
     def fixture_path
-      File.join(__dir__, '../fixtures')
+      File.join(__dir__, '../chef/fixtures')
     end
 
     def get_rendered_toml(chef_run, path)
diff --git a/spec/support/shared_context/ctl_shared_context.rb b/spec/support/shared_context/ctl_shared_context.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L3NoYXJlZF9jb250ZXh0L2N0bF9zaGFyZWRfY29udGV4dC5yYg==..0000000000000000000000000000000000000000
--- a/spec/support/shared_context/ctl_shared_context.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'spec_helper'
-require 'omnibus-ctl'
-
-RSpec.shared_context 'ctl' do
-  let(:ctl) { Omnibus::Ctl.new('testing-ctl') }
-
-  before do
-    allow_any_instance_of(Omnibus::Ctl).to receive(:require).and_call_original
-
-    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
-      "/opt/testing-ctl/embedded/cookbooks/package/libraries/gitlab_cluster"
-    ) do
-      require_relative("../../../files/gitlab-cookbooks/package/libraries/gitlab_cluster")
-    end
-
-    allow_any_instance_of(Omnibus::Ctl).to receive(:require).with(
-      "/opt/testing-ctl/embedded/service/omnibus-ctl-ee/lib/geo/#{command_script}"
-    ) do
-      require_relative("../../../files/gitlab-ctl-commands-ee/lib/geo/#{command_script}")
-    end
-
-    ctl.load_file("files/gitlab-ctl-commands-ee/#{command_script}.rb")
-  end
-end
diff --git a/spec/support/shared_context/gitlab_rails_shared_context.rb b/spec/support/shared_context/gitlab_rails_shared_context.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L3NoYXJlZF9jb250ZXh0L2dpdGxhYl9yYWlsc19zaGFyZWRfY29udGV4dC5yYg==..0000000000000000000000000000000000000000
--- a/spec/support/shared_context/gitlab_rails_shared_context.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require 'chef_helper'
-
-RSpec.shared_context 'gitlab-rails' do
-  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: 'templatesymlink').converge('gitlab::default') }
-  let(:gitlab_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/gitlab.yml') }
-  let(:gitlab_yml_file_content) { ChefSpec::Renderer.new(chef_run, gitlab_yml_template).content }
-  let(:gitlab_yml) { YAML.safe_load(gitlab_yml_file_content, [], [], true, symbolize_names: true) }
-  let(:config_dir) { '/var/opt/gitlab/gitlab-rails/etc/' }
-
-  before do
-    allow(Gitlab).to receive(:[]).and_call_original
-    allow(File).to receive(:symlink?).and_call_original
-  end
-end
diff --git a/spec/support/shared_context/object_store_shared_context.rb b/spec/support/shared_context/object_store_shared_context.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L3NoYXJlZF9jb250ZXh0L29iamVjdF9zdG9yZV9zaGFyZWRfY29udGV4dC5yYg==..0000000000000000000000000000000000000000
--- a/spec/support/shared_context/object_store_shared_context.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require 'spec_helper'
-require 'omnibus-ctl'
-
-RSpec.shared_context 'object storage config' do
-  let(:object_config) do
-    {
-      artifacts: { bucket: 'artifacts' },
-      lfs: { bucket: 'lfs-objects' },
-      dependency_proxy: { bucket: 'dependency_proxy' },
-      external_diffs: { bucket: 'external_diffs' },
-      packages: { bucket: 'packages' },
-      terraform_state: { enabled: false, bucket: 'terraform' },
-      ci_secure_files: { bucket: 'ci_secure_files' },
-      uploads: { bucket: 'uploads' },
-      pages: { bucket: 'pages' }
-    }
-  end
-  let(:aws_connection_hash) do
-    {
-      'provider' => 'AWS',
-      'region' => 'eu-west-1',
-      'aws_access_key_id' => 'AKIAKIAKI',
-      'aws_secret_access_key' => 'secret123'
-    }
-  end
-  let(:aws_storage_options_hash) do
-    {
-      'server_side_encryption' => 'AES256',
-      'server_side_encryption_kms_key_id' => 'arn:aws:12345'
-    }
-  end
-  let(:azure_connection_hash) do
-    {
-      'provider' => 'AzureRM',
-      'azure_storage_account_name' => 'testaccount',
-      'azure_storage_access_key' => '1234abcd'
-    }
-  end
-
-  let(:aws_connection_data) { JSON.parse(aws_connection_hash.to_json, symbolize_names: true) }
-  let(:aws_storage_options) { JSON.parse(aws_storage_options_hash.to_json, symbolize_names: true) }
-end
diff --git a/spec/support/shared_examples/consul_service_name.rb b/spec/support/shared_examples/consul_service_name.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L3NoYXJlZF9leGFtcGxlcy9jb25zdWxfc2VydmljZV9uYW1lLnJi..0000000000000000000000000000000000000000
--- a/spec/support/shared_examples/consul_service_name.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-shared_examples 'consul service discovery' do |gitlab_rb_key, service_name|
-  let(:gitlab_rb_setting) do
-    {
-      consul: {
-        enable: true,
-        monitoring_service_discovery: true
-      }
-    }
-  end
-
-  describe 'consul service discovery' do
-    let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab-ee::default') }
-
-    context 'by default' do
-      it 'is not registered as consul service' do
-        expect(chef_run).not_to create_consul_service(service_name)
-      end
-    end
-
-    context 'when enabled' do
-      before do
-        stub_gitlab_rb(gitlab_rb_setting)
-      end
-
-      it 'is registered as a consul service with default name' do
-        expect(chef_run).to create_consul_service(service_name)
-      end
-
-      context 'with user specified service name' do
-        before do
-          gitlab_rb_setting[gitlab_rb_key] = {
-            enable: true,
-            consul_service_name: "#{service_name}-foobar"
-          }
-          stub_gitlab_rb(gitlab_rb_setting)
-        end
-
-        it 'is registered as a consul service with user specified name' do
-          expect(chef_run).to create_consul_service("#{service_name}-foobar")
-        end
-      end
-    end
-  end
-end
diff --git a/spec/support/shared_examples/geo_promotion_command_accepts_option_shared_examples.rb b/spec/support/shared_examples/geo_promotion_command_accepts_option_shared_examples.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L3NoYXJlZF9leGFtcGxlcy9nZW9fcHJvbW90aW9uX2NvbW1hbmRfYWNjZXB0c19vcHRpb25fc2hhcmVkX2V4YW1wbGVzLnJi..0000000000000000000000000000000000000000
--- a/spec/support/shared_examples/geo_promotion_command_accepts_option_shared_examples.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-RSpec.shared_examples 'geo promotion command accepts option' do |passed_option, expected_option|
-  it 'accepts given option' do
-    allow_any_instance_of(klass).to receive(:execute).and_return(true)
-
-    # ARGV contains the commands that were passed to rspec, which are
-    # invalid for the omnibus-ctl commands
-    oldargv = ARGV
-    ARGV = [passed_option] # rubocop:disable Style/MutableConstant
-
-    expect(klass).to receive(:new).with(
-      anything, expected_option).and_call_original
-
-    ctl.send(command_script, ARGV)
-
-    ARGV = oldargv
-  end
-end
diff --git a/spec/support/shared_examples/gitlab_geo_promotion_commands_shared_examples.rb b/spec/support/shared_examples/gitlab_geo_promotion_commands_shared_examples.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L3NoYXJlZF9leGFtcGxlcy9naXRsYWJfZ2VvX3Byb21vdGlvbl9jb21tYW5kc19zaGFyZWRfZXhhbXBsZXMucmI=..0000000000000000000000000000000000000000
--- a/spec/support/shared_examples/gitlab_geo_promotion_commands_shared_examples.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-RSpec.shared_examples 'gitlab geo promotion commands' do
-  it 'appends a geo replication command' do
-    expect(ctl.get_all_commands_hash).to include(command_name)
-  end
-
-  it 'executes the command when called' do
-    # ARGV contains the commands that were passed to rspec, which are
-    # invalid for the omnibus-ctl commands
-    oldargv = ARGV
-    ARGV = [] # rubocop:disable Style/MutableConstant
-
-    expect_any_instance_of(klass).to receive(:execute)
-
-    ctl.send(command_script)
-
-    ARGV = oldargv
-  end
-end
diff --git a/spec/support/shared_examples/gitlab_yml_object_storage_shared_examples.rb b/spec/support/shared_examples/gitlab_yml_object_storage_shared_examples.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L3NoYXJlZF9leGFtcGxlcy9naXRsYWJfeW1sX29iamVjdF9zdG9yYWdlX3NoYXJlZF9leGFtcGxlcy5yYg==..0000000000000000000000000000000000000000
--- a/spec/support/shared_examples/gitlab_yml_object_storage_shared_examples.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-shared_examples 'renders object storage settings in gitlab.yml' do |component, component_default = {}, workhorse_accelerated = true|
-  include_context 'gitlab-rails'
-  include_context 'object storage config'
-
-  describe "for #{component}" do
-    context 'with default values' do
-      it 'renders gitlab.yml with object storage disabled and other default values' do
-        default_values = {
-          enabled: false,
-          remote_directory: component,
-          connection: {}
-        }
-
-        if workhorse_accelerated
-          default_values.merge!(
-            direct_upload: false,
-            background_upload: true,
-            proxy_download: false
-          )
-        end
-
-        default_values.merge!(component_default)
-
-        config = gitlab_yml[:production][component.to_sym][:object_store]
-        expect(config).to eq(default_values)
-      end
-    end
-
-    context 'with user specified values' do
-      before do
-        gitlab_rails_config = {
-          "#{component}_object_store_enabled" => true,
-          "#{component}_object_store_remote_directory" => 'foobar',
-          "#{component}_object_store_connection" => aws_connection_hash
-        }
-
-        if workhorse_accelerated
-          gitlab_rails_config.merge!(
-            "#{component}_object_store_direct_upload" => true,
-            "#{component}_object_store_background_upload" => false,
-            "#{component}_object_store_proxy_download" => true
-          )
-        end
-
-        stub_gitlab_rb(
-          gitlab_rails: gitlab_rails_config.transform_keys(&:to_sym)
-        )
-      end
-
-      it 'renders gitlab.yml with user specified values' do
-        expected_output = {
-          enabled: true,
-          connection: aws_connection_data,
-          remote_directory: 'foobar'
-        }
-
-        if workhorse_accelerated
-          expected_output.merge!(
-            direct_upload: true,
-            background_upload: false,
-            proxy_download: true
-          )
-        end
-
-        expect(gitlab_yml[:production][component.to_sym][:object_store]).to eq(expected_output)
-      end
-    end
-  end
-end
diff --git a/spec/support/shared_examples/logrotate_config.rb b/spec/support/shared_examples/logrotate_config.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L3NoYXJlZF9leGFtcGxlcy9sb2dyb3RhdGVfY29uZmlnLnJi..0000000000000000000000000000000000000000
--- a/spec/support/shared_examples/logrotate_config.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-shared_examples 'configured logrotate service' do |svc, username = 'git', group = 'git'|
-  it 'creates logrotate config file' do
-    expect(chef_run).to render_file("/var/opt/gitlab/logrotate/logrotate.d/#{svc}")
-  end
-
-  it 'specifies su parameter in logrotate config' do
-    expect(chef_run).to render_file("/var/opt/gitlab/logrotate/logrotate.d/#{svc}").with_content(/su #{username} #{group}/)
-  end
-end
diff --git a/spec/support/shared_examples/registry_service.rb b/spec/support/shared_examples/registry_service.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L3NoYXJlZF9leGFtcGxlcy9yZWdpc3RyeV9zZXJ2aWNlLnJi..0000000000000000000000000000000000000000
--- a/spec/support/shared_examples/registry_service.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-shared_examples 'enabled registry service' do
-  it 'creates default set of directories' do
-    expect(chef_run.node['registry']['dir'])
-      .to eql('/var/opt/gitlab/registry')
-    expect(chef_run.node['registry']['log_directory'])
-      .to eql('/var/log/gitlab/registry')
-    expect(chef_run.node['gitlab']['gitlab-rails']['registry_path'])
-      .to eql('/var/opt/gitlab/gitlab-rails/shared/registry')
-
-    expect(chef_run).to create_directory('/var/opt/gitlab/registry')
-    expect(chef_run).to create_directory('/var/log/gitlab/registry').with(
-      owner: 'registry',
-      group: nil,
-      mode: '0700'
-    )
-    expect(chef_run).to create_directory('/var/opt/gitlab/gitlab-rails/shared/registry').with(
-      owner: 'registry',
-      group: 'git',
-      mode: '0770'
-    )
-  end
-
-  it 'creates default user and group' do
-    expect(chef_run.node['registry']['username'])
-      .to eql('registry')
-    expect(chef_run.node['registry']['group'])
-      .to eql('registry')
-
-    expect(chef_run).to create_account('Docker registry user and group').with(
-      username: 'registry',
-      groupname: 'registry',
-      uid: nil,
-      gid: nil,
-      system: true,
-      home: '/var/opt/gitlab/registry'
-    )
-  end
-
-  it 'creates default self signed key-certificate pair' do
-    expect(chef_run).to create_file('/var/opt/gitlab/registry/gitlab-registry.crt').with(
-      user: 'registry',
-      group: 'registry'
-    )
-    expect(chef_run).to create_file('/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key').with(
-      user: 'git',
-      group: 'git'
-    )
-
-    expect(chef_run).to render_file('/var/opt/gitlab/registry/gitlab-registry.crt')
-      .with_content(/-----BEGIN CERTIFICATE-----/)
-    expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key')
-      .with_content(/-----BEGIN RSA PRIVATE KEY-----/)
-  end
-
-  it 'creates registry config.yml template' do
-    expect(chef_run).to create_template('/var/opt/gitlab/registry/config.yml').with(
-      owner: 'registry',
-      group: nil,
-      mode: '0644'
-    )
-    expect(chef_run).to(
-      render_file('/var/opt/gitlab/registry/config.yml').with_content do |content|
-        expect(content).to match(/version: 0.1/)
-        expect(content).to match(/realm: .*\/jwt\/auth/)
-        expect(content).to match(/addr: localhost:5000/)
-        expect(content).to match(%r(storage: {"filesystem":{"rootdirectory":"/var/opt/gitlab/gitlab-rails/shared/registry"}))
-        expect(content).to match(/health:\s*storagedriver:\s*enabled:\s*true/)
-        expect(content).to match(/log:\s*level: info\s*formatter:\s*text/)
-        expect(content).to match(/validation:\s*disabled: true$/)
-        expect(content).not_to match(/^compatibility:/)
-        expect(content).not_to match(/^middleware:/)
-      end
-    )
-  end
-
-  it 'populates default settings for svlogd' do
-    expect(chef_run).to render_file('/opt/gitlab/sv/registry/log/run')
-      .with_content(/exec svlogd -tt \/var\/log\/gitlab\/registry/)
-  end
-
-  it 'creates a default VERSION file and restarts service' do
-    expect(chef_run).to create_version_file('Create version file for Registry').with(
-      version_file_path: '/var/opt/gitlab/registry/VERSION',
-      version_check_cmd: '/opt/gitlab/embedded/bin/registry --version'
-    )
-
-    expect(chef_run.version_file('Create version file for Registry')).to notify('runit_service[registry]').to(:restart)
-  end
-
-  it 'creates gitlab-rails config with default values' do
-    expect(chef_run).to create_templatesymlink('Create a gitlab.yml and create a symlink to Rails root').with_variables(hash_including('registry_api_url' => 'http://localhost:5000'))
-  end
-
-  it 'sets default storage options' do
-    expect(chef_run.node['registry']['storage']['filesystem'])
-      .to eql('rootdirectory' => '/var/opt/gitlab/gitlab-rails/shared/registry')
-    expect(chef_run.node['registry']['storage']['cache'])
-      .to eql('blobdescriptor' => 'inmemory')
-    expect(chef_run.node['registry']['storage']['delete'])
-      .to eql('enabled' => true)
-  end
-end
diff --git a/spec/support/shared_examples/runit_service.rb b/spec/support/shared_examples/runit_service.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L3NoYXJlZF9leGFtcGxlcy9ydW5pdF9zZXJ2aWNlLnJi..0000000000000000000000000000000000000000
--- a/spec/support/shared_examples/runit_service.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-def get_env_string(username, groupname)
-  env_string = ""
-  env_string << username if username
-  env_string << ":#{groupname}" if groupname
-
-  env_string
-end
-
-shared_examples 'enabled runit service' do |svc_name, owner, group, username = nil, groupname = nil|
-  it 'creates directories' do
-    expect(chef_run).to create_directory("/opt/gitlab/sv/#{svc_name}").with(
-      owner: owner,
-      group: group,
-      mode: '0755'
-    )
-    expect(chef_run).to create_directory("/opt/gitlab/sv/#{svc_name}/log").with(
-      owner: owner,
-      group: group,
-      mode: '0755'
-    )
-    expect(chef_run).to create_directory("/opt/gitlab/sv/#{svc_name}/log/main").with(
-      owner: owner,
-      group: group,
-      mode: '0755'
-    )
-  end
-
-  it 'creates files' do
-    expect(chef_run).to create_template("/opt/gitlab/sv/#{svc_name}/run").with(
-      owner: owner,
-      group: group,
-      mode: '0755'
-    )
-    expect(chef_run).to create_template("/opt/gitlab/sv/#{svc_name}/log/run").with(
-      owner: owner,
-      group: group,
-      mode: '0755'
-    )
-    expect(chef_run).to create_template("/opt/gitlab/sv/#{svc_name}/log/config").with(
-      owner: owner,
-      group: group,
-      mode: '0644'
-    )
-
-    env_string = get_env_string(username, groupname)
-
-    if env_string.empty?
-      expect(chef_run).to render_file("/opt/gitlab/sv/#{svc_name}/run").with_content(%r{.*})
-    else
-      expect(chef_run).to render_file("/opt/gitlab/sv/#{svc_name}/run").with_content(/-u #{env_string}/)
-      expect(chef_run).to render_file("/opt/gitlab/sv/#{svc_name}/run").with_content(/-U #{env_string}/)
-    end
-
-    expect(chef_run).to render_file("/opt/gitlab/sv/#{svc_name}/log/run").with_content(%r{.*})
-    expect(chef_run).to render_file("/opt/gitlab/sv/#{svc_name}/log/config")
-  end
-
-  it 'verifies that file ownership persists' do
-    expect(chef_run.template("/opt/gitlab/sv/#{svc_name}/log/config")).to notify("ruby_block[verify_chown_persisted_on_#{svc_name}]")
-  end
-
-  it 'raises an error when file ownership does not persist' do
-    log_config_re = /\/opt\/gitlab\/sv\/.*\/log\/config/
-    allow_any_instance_of(OmnibusHelper).to receive(:expected_user?).and_return(true)
-    allow_any_instance_of(OmnibusHelper).to receive(:expected_user?).with(log_config_re, anything).and_return(false)
-
-    allow_any_instance_of(OmnibusHelper).to receive(:expected_group?).and_return(true)
-    allow_any_instance_of(OmnibusHelper).to receive(:expected_group?).with(log_config_re, anything).and_return(false)
-
-    block = chef_run.find_resource('ruby_block', "verify_chown_persisted_on_#{svc_name}")
-    expect { block.block.call }.to raise_error("Unable to persist filesystem ownership changes of /opt/gitlab/sv/#{svc_name}/log/config. See https://docs.gitlab.com/ee/administration/high_availability/nfs.html#recommended-options for guidance.")
-  end
-
-  it 'creates the symlink to the service directory' do
-    expect(chef_run).to create_link("/opt/gitlab/init/#{svc_name}").with(to: '/opt/gitlab/embedded/bin/sv')
-  end
-
-  context 'gitlab customization' do
-    # These are specs related to changes we have made to the upstream runit cookbook
-    before do
-      %w(ok status control).each do |target|
-        file_name = ::File.join('/opt/gitlab/service', svc_name, 'log', 'supervise', target)
-        allow_any_instance_of(OmnibusHelper).to receive(:expected_owner?).with(file_name, username, groupname).and_return(false)
-      end
-    end
-
-    it 'sets the supervise log files with correct permissions' do
-      # For some services, we set the ownership on the sv log files to allow supervisor_owner to run commands
-      unless username.nil?
-        expect(chef_run).to create_directory("/opt/gitlab/service/#{svc_name}/log/supervise").with(mode: '0755')
-        %w(ok status control).each do |target|
-          file_name = ::File.join('/opt/gitlab/service', svc_name, 'log', 'supervise', target)
-          expect(chef_run).to touch_file(file_name).with(
-            owner: username,
-            group: groupname
-          )
-        end
-      end
-    end
-  end
-end
-
-shared_examples 'disabled runit service' do |svc_name|
-  it 'does not create directories' do
-    expect(chef_run).not_to create_directory("/opt/gitlab/sv/#{svc_name}")
-    expect(chef_run).not_to create_directory("/opt/gitlab/sv/#{svc_name}/log")
-    expect(chef_run).not_to create_directory("/opt/gitlab/sv/#{svc_name}/log/main")
-  end
-
-  it 'does not create files' do
-    expect(chef_run).not_to create_template("/opt/gitlab/sv/#{svc_name}/run")
-    expect(chef_run).not_to create_template("/opt/gitlab/sv/#{svc_name}/log/run")
-    expect(chef_run).not_to create_template("/var/log/gitlab/#{svc_name}/config")
-  end
-
-  it 'runs the disable service ruby block' do
-    allow_any_instance_of(Chef::Provider::RunitService).to receive(:enabled?).and_return(true)
-    expect(chef_run).to run_ruby_block("disable #{svc_name}")
-  end
-end
diff --git a/spec/support/shared_examples/yaml_check.rb b/spec/support/shared_examples/yaml_check.rb
deleted file mode 100644
index f8db0051e33f2ed7cec00f743c123af4a4185914_c3BlYy9zdXBwb3J0L3NoYXJlZF9leGFtcGxlcy95YW1sX2NoZWNrLnJi..0000000000000000000000000000000000000000
--- a/spec/support/shared_examples/yaml_check.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'yaml'
-
-shared_examples 'renders a valid YAML file' do |filename|
-  it 'parses YAML with no errors' do
-    expect(chef_run).to render_file(filename).with_content { |content|
-      expect { YAML.parse(content) }.not_to raise_error
-    }
-  end
-end