files/gitlab-cookbooks/package/libraries/settings_dsl.rb (170 lines of code) (raw):
#
# Copyright:: Copyright (c) 2017 GitLab Inc.
# License:: Apache License, Version 2.0
#
# 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 'mixlib/config'
require 'chef/json_compat'
require 'chef/mixin/deep_merge'
require 'securerandom'
require 'uri'
require_relative 'config_mash.rb'
require_relative 'gitlab_cluster'
module SettingsDSL
def self.extended(base)
# Setup getter/setters for roles and settings
class << base
attr_accessor :available_roles, :settings
end
base.available_roles = {}
base.settings = {}
end
# Change the default root location for node attributes
# Pass in the root (ie 'gitlab') and a block containing the attributes that should
# use that root.
# ex:
# attribute_block('example') do
# attribute('some_attribute')
# end
# This will convert Gitlab['some_attribute'] to node['example']['some-attribute']
def attribute_block(root = nil)
return unless block_given?
begin
@_default_parent = root
yield
ensure
@_default_parent = nil
end
end
# Create a new role with the given 'name' config
# config options are:
# manage_services - Boolean to indicate whether the role enables/disables services. Defaults to enabled.
# If enabled, the default service role is disabled when using a different role that manages services
# Roles are configured as Gitlab['<name>_role'] and are added to the node as node['roles']['<name>']
# ex: some_specific_role['enable'] = true
# will result in Gitlab['some_specific_role']['enable'] = true
# and node['roles']['some-specific']['enable'] = true
def role(name, **config)
@available_roles[name] = HandledHash.new.merge!(
{ manage_services: true }
).merge(config)
send("#{name}_role", Gitlab::ConfigMash.new)
@available_roles[name]
end
# Create a new attribute with the given 'name' and config
#
# config options are:
# parent - String name for the root node attribute, default can be specified using the attribute_block method
# priority - Integer used to sort the settings when applying them, defaults to 20, similar to sysvinit startups. Lower numbers are loaded first.
# ee - Boolean to indicate that the variable should only be used in GitLab EE
# default - Default value to set for the Gitlab Config. Defaults to Gitlab::ConfigMash.new, should be set to nil config expecting non hash values
#
# ex: attribute('some_attribute', parent: 'gitlab', sequence: 10, default: nil)
# will right away set Gitlab['some_attribute'] = nil
# and when the config is generated it will set node['gitlab']['some-attribute'] = nil
def attribute(name, **config)
@settings[name] = HandledHash.new.merge!(
{ parent: @_default_parent, priority: 20, ee: false, default: Gitlab::ConfigMash.new }
).merge(config)
send(name.to_sym, @settings[name][:default])
@settings[name]
end
# Same as 'attribute' but defaults 'enable' to false if the GitlabEE module is unavailable
def ee_attribute(name, **config)
config = { ee: true }.merge(config)
attribute(name, **config)
end
def from_file(_file_path)
# Throw errors for unrecognized top level calls (usually spelling mistakes)
config_strict_mode true
# Turn on node deprecation messages
Gitlab::Deprecations::NodeAttribute.log_deprecations = true
# Allow auto mash creation during from_file call
Gitlab::ConfigMash.auto_vivify { super }
ensure
config_strict_mode false
Gitlab::Deprecations::NodeAttribute.log_deprecations = false
end
# Enhance set so strict mode errors aren't thrown as long as the setting is witin our defined config
def internal_set(symbol, value)
if configuration.key?(symbol)
configuration[symbol] = value
else
super
end
end
# Enhance get so strict mode errors aren't thrown as long as the setting is witin our defined config
def internal_get(symbol)
if configuration.key?(symbol)
configuration[symbol]
else
super
end
end
def sanitized_config
results = { "gitlab" => {}, "roles" => {}, "monitoring" => {} }
# Add the settings to the results
sorted_settings.each do |key, value|
raise "Attribute parent value invalid for key: #{key} (#{value})" if value[:parent] && !results.key?(value[:parent])
target = value[:parent] ? results[value[:parent]] : results
target[Utils.node_attribute_key(key)] = Gitlab[key]
end
# Add the roles the the results
@available_roles.each do |key, value|
results['roles'][Utils.node_attribute_key(key)] = Gitlab["#{key}_role"]
end
results
end
def load_roles
# System services are enabled by default
Services.enable_group(Services::SYSTEM_GROUP)
RolesHelper.parse_enabled
# Roles defined in the cluster configuration file overrides roles from /etc/gitlab/gitlab.rb
GitlabCluster.config.load_roles!
# Load our roles
DefaultRole.load_role
@available_roles.each do |key, value|
handler = value.handler
handler.load_role if handler.respond_to?(:load_role)
end
end
def generate_secrets(node_name, path = SecretsHelper::SECRETS_FILE)
# Gitlab['node'][SecretsHelper::SKIP_GENERATE_SECRETS_CHEF_ATTR] is set to
# true if we are calling from the 'gitlab-ctrl generate-secrets' command
# and we are running the 'config(-ee)' recipe in which case we will generate
# secrets in the 'generate_secrets' recipe where it will then be set to
# false.
return if Gitlab['node'][SecretsHelper::SKIP_GENERATE_SECRETS_CHEF_ATTR] == true
force_write_secrets = !Gitlab['node'][SecretsHelper::SECRETS_FILE_CHEF_ATTR].nil?
# guards against creating secrets on non-bootstrap node
SecretsHelper.read_gitlab_secrets(path)
generate_default_secrets = Gitlab['package']['generate_default_secrets'] != false
Chef::Log.info("Generating default secrets") if generate_default_secrets
# Parse secrets using the handlers
sorted_settings.each do |_key, value|
handler = value.handler
handler.parse_secrets if handler.respond_to?(:parse_secrets) && generate_default_secrets
handler.validate_secrets if handler.respond_to?(:validate_secrets)
end
if Gitlab['package']['generate_secrets_json_file'] == false && !force_write_secrets
return unless generate_default_secrets
warning_message = <<~EOS
You've enabled generating default secrets but have disabled writing them to #{path} file.
This results in secrets not persisting across `gitlab-ctl reconfigure` runs and can cause issues with functionality.
EOS
LoggingHelper.warning(warning_message)
else
Chef::Log.info("Generating #{path} file")
SecretsHelper.write_to_gitlab_secrets(path)
end
end
def generate_config(node_name)
generate_secrets(node_name)
load_roles
# Parse all our variables using the handlers
sorted_settings.each do |_key, value|
handler = value.handler
handler.parse_variables if handler.respond_to?(:parse_variables)
end
strip_nils(sanitized_config)
end
def strip_nils(attributes)
results = {}
attributes.each_pair do |key, value|
next if value.nil?
recursive_classes = [Hash, Gitlab::ConfigMash, ChefUtils::Mash]
results[key] = if recursive_classes.include?(value.class)
strip_nils(value)
else
value
end
end
results
end
# Merge provided role and value set if value is defined
#
# @param [String] role
# @param [String] value
def override_role!(role, value)
return if value.nil?
GitlabCluster.log_overriding_message(role, value) unless dig(role, 'enable').nil?
Gitlab::ConfigMash.auto_vivify do
self[role]['enable'] = value
end
end
private
# Sort settings by their sequence value
def sorted_settings
@settings.select { |_k, value| !value[:ee] || Gitlab['edition'] == :ee }.sort_by { |_k, value| value[:priority] }
end
# Custom Hash object used to add a handler as a block to the attribute
class HandledHash < Hash
attr_writer :handler
def use(&block)
@handler = block
self
end
def handler
@handler = @handler.call if @handler.respond_to?(:call)
@handler
end
end
class Utils
class << self
# In service names, words are seperated with a hyphen
def service_name(service)
service.tr('_', '-')
end
# Node attributes corresponding to a service are formatted by replacing
# hyphens in the service names with underscores
def node_attribute_key(service)
service.tr('-', '_')
end
end
end
end