summaryrefslogtreecommitdiff
path: root/lib/gitlab/config/entry/configurable.rb
blob: aa6c724c2a35d06e9ea9bef00da7839aabb7eea8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# frozen_string_literal: true

module Gitlab
  module Config
    module Entry
      ##
      # This mixin is responsible for adding DSL, which purpose is to
      # simplify the process of adding child nodes.
      #
      # This can be used only if parent node is a configuration entry that
      # holds a hash as a configuration value, for example:
      #
      # job:
      #   script: ...
      #   artifacts: ...
      #
      module Configurable
        extend ActiveSupport::Concern

        included do
          include Validatable

          validations do
            validates :config, type: Hash, unless: :skip_config_hash_validation?
          end
        end

        def compose!(deps = nil)
          return unless valid?

          super do
            self.class.nodes.each do |key, factory|
              # If we override the config type validation
              # we can end with different config types like String
              next unless config.is_a?(Hash)

              entry_create!(key, config[key])
            end

            yield if block_given?

            entries.each_value do |entry|
              entry.compose!(deps)
            end
          end
        end

        # rubocop: disable CodeReuse/ActiveRecord
        def entry_create!(key, value)
          factory = self.class
            .nodes[key]
            .value(value)
            .with(key: key, parent: self)

          entries[key] = factory.create!
        end
        # rubocop: enable CodeReuse/ActiveRecord

        def skip_config_hash_validation?
          false
        end

        class_methods do
          def nodes
            return {} unless @nodes

            @nodes.transform_values(&:dup)
          end

          def reserved_node_names
            self.nodes.select do |_, node|
              node.reserved?
            end.keys
          end

          private

          # rubocop: disable CodeReuse/ActiveRecord
          def entry(key, entry, description: nil, default: nil, inherit: nil, reserved: nil, deprecation: nil, metadata: {})
            entry_name = key.to_sym
            raise ArgumentError, "Entry '#{key}' already defined in '#{name}'" if @nodes.to_h[entry_name]

            factory = ::Gitlab::Config::Entry::Factory.new(entry)
              .with(description: description)
              .with(default: default)
              .with(inherit: inherit)
              .with(reserved: reserved)
              .with(deprecation: deprecation)
              .metadata(metadata)

            @nodes ||= {}
            @nodes[entry_name] = factory

            helpers(entry_name)
          end
          # rubocop: enable CodeReuse/ActiveRecord

          def dynamic_helpers(*nodes)
            helpers(*nodes, dynamic: true)
          end

          def helpers(*nodes, dynamic: false)
            nodes.each do |symbol|
              if method_defined?("#{symbol}_defined?") || method_defined?("#{symbol}_entry") || method_defined?("#{symbol}_value")
                raise ArgumentError, "Method '#{symbol}_defined?', '#{symbol}_entry' or '#{symbol}_value' already defined in '#{name}'"
              end

              unless @nodes.to_h[symbol]
                raise ArgumentError, "Entry for #{symbol} is undefined" unless dynamic
              end

              define_method("#{symbol}_defined?") do
                entries[symbol]&.specified?
              end

              define_method("#{symbol}_entry") do
                entries[symbol]
              end

              define_method("#{symbol}_value") do
                entry = entries[symbol]
                entry.value if entry&.valid?
              end
            end
          end
        end
      end
    end
  end
end