1# frozen_string_literal: true
2
3module Gitlab
4  module Ci
5    class Config
6      module Entry
7        ##
8        # This class represents a global entry - root Entry for entire
9        # GitLab CI Configuration file.
10        #
11        class Root < ::Gitlab::Config::Entry::Node
12          include ::Gitlab::Config::Entry::Configurable
13
14          ALLOWED_KEYS = %i[default include before_script image services
15                            after_script variables stages types cache workflow].freeze
16
17          validations do
18            validates :config, allowed_keys: ALLOWED_KEYS
19          end
20
21          # reserved:
22          #   defines whether the node name is reserved
23          #   the reserved name cannot be used a job name
24          #   reserved should not be used as it will make
25          #   breaking change to `.gitlab-ci.yml`
26
27          entry :default, Entry::Default,
28            description: 'Default configuration for all jobs.',
29            default: {}
30
31          entry :include, Entry::Includes,
32            description: 'List of external YAML files to include.',
33            reserved: true
34
35          entry :before_script, Entry::Script,
36            description: 'Script that will be executed before each job.',
37            reserved: true
38
39          entry :image, Entry::Image,
40            description: 'Docker image that will be used to execute jobs.',
41            reserved: true
42
43          entry :services, Entry::Services,
44            description: 'Docker images that will be linked to the container.',
45            reserved: true
46
47          entry :after_script, Entry::Script,
48            description: 'Script that will be executed after each job.',
49            reserved: true
50
51          entry :variables, Entry::Variables,
52            description: 'Environment variables that will be used.',
53            metadata: { use_value_data: true },
54            reserved: true
55
56          entry :stages, Entry::Stages,
57            description: 'Configuration of stages for this pipeline.',
58            reserved: true
59
60          entry :types, Entry::Stages,
61            description: 'Deprecated: stages for this pipeline.',
62            reserved: true
63
64          entry :cache, Entry::Caches,
65            description: 'Configure caching between build jobs.',
66            reserved: true
67
68          entry :workflow, Entry::Workflow,
69            description: 'List of evaluable rules to determine Pipeline status',
70            default: {}
71
72          dynamic_helpers :jobs
73
74          delegate :before_script_value,
75                   :image_value,
76                   :services_value,
77                   :after_script_value,
78                   :cache_value, to: :default_entry
79
80          attr_reader :jobs_config
81
82          class << self
83            include ::Gitlab::Utils::StrongMemoize
84
85            def reserved_nodes_names
86              strong_memoize(:reserved_nodes_names) do
87                self.nodes.select do |_, node|
88                  node.reserved?
89                end.keys
90              end
91            end
92          end
93
94          def initialize(config, **metadata)
95            super do
96              filter_jobs!
97            end
98          end
99
100          def compose!(_deps = nil)
101            super(self) do
102              compose_deprecated_entries!
103              compose_jobs!
104            end
105          end
106
107          private
108
109          # rubocop: disable CodeReuse/ActiveRecord
110          def compose_jobs!
111            factory = ::Gitlab::Config::Entry::Factory.new(Entry::Jobs)
112              .value(jobs_config)
113              .with(key: :jobs, parent: self,
114                    description: 'Jobs definition for this pipeline')
115
116            @entries[:jobs] = factory.create!
117          end
118          # rubocop: enable CodeReuse/ActiveRecord
119
120          def compose_deprecated_entries!
121            ##
122            # Deprecated `:types` key workaround - if types are defined and
123            # stages are not defined we use types definition as stages.
124            #
125            if types_defined? && !stages_defined?
126              @entries[:stages] = @entries[:types]
127            end
128
129            @entries.delete(:types)
130          end
131
132          def filter_jobs!
133            return unless @config.is_a?(Hash)
134
135            @jobs_config = @config
136              .except(*self.class.reserved_nodes_names)
137              .select do |name, config|
138              Entry::Jobs.find_type(name, config).present? || ALLOWED_KEYS.exclude?(name)
139            end
140
141            @config = @config.except(*@jobs_config.keys)
142          end
143        end
144      end
145    end
146  end
147end
148