summaryrefslogtreecommitdiff
path: root/lib/gitlab/ci/variables/builder.rb
blob: bfcf67693e73539b4bbb3798155084d6cc7165d5 (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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# frozen_string_literal: true

module Gitlab
  module Ci
    module Variables
      class Builder
        include ::Gitlab::Utils::StrongMemoize

        def initialize(pipeline)
          @pipeline = pipeline
          @instance_variables_builder = Builder::Instance.new
          @project_variables_builder = Builder::Project.new(project)
          @group_variables_builder = Builder::Group.new(project.group)
        end

        def scoped_variables(job, environment:, dependencies:)
          Gitlab::Ci::Variables::Collection.new.tap do |variables|
            variables.concat(predefined_variables(job))
            variables.concat(project.predefined_variables)
            variables.concat(pipeline.predefined_variables)
            variables.concat(job.runner.predefined_variables) if job.runnable? && job.runner
            variables.concat(kubernetes_variables(environment: environment, job: job))
            variables.concat(job.yaml_variables)
            variables.concat(user_variables(job.user))
            variables.concat(job.dependency_variables) if dependencies
            variables.concat(secret_instance_variables)
            variables.concat(secret_group_variables(environment: environment, ref: job.git_ref))
            variables.concat(secret_project_variables(environment: environment, ref: job.git_ref))
            variables.concat(job.trigger_request.user_variables) if job.trigger_request
            variables.concat(pipeline.variables)
            variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
          end
        end

        def kubernetes_variables(environment:, job:)
          ::Gitlab::Ci::Variables::Collection.new.tap do |collection|
            # NOTE: deployment_variables will be removed as part of cleanup for
            # https://gitlab.com/groups/gitlab-org/configure/-/epics/8
            # Until then, we need to make both the old and the new KUBECONFIG contexts available
            collection.concat(deployment_variables(environment: environment, job: job))
            template = ::Ci::GenerateKubeconfigService.new(job).execute
            kubeconfig_yaml = collection['KUBECONFIG']&.value
            template.merge_yaml(kubeconfig_yaml) if kubeconfig_yaml.present?

            if template.valid?
              collection.append(key: 'KUBECONFIG', value: template.to_yaml, public: false, file: true)
            end
          end
        end

        def deployment_variables(environment:, job:)
          return [] unless environment

          project.deployment_variables(
            environment: environment,
            kubernetes_namespace: job.expanded_kubernetes_namespace
          )
        end

        def user_variables(user)
          Gitlab::Ci::Variables::Collection.new.tap do |variables|
            break variables if user.blank?

            variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
            variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
            variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
            variables.append(key: 'GITLAB_USER_NAME', value: user.name)
          end
        end

        def secret_instance_variables
          strong_memoize(:secret_instance_variables) do
            instance_variables_builder
              .secret_variables(protected_ref: protected_ref?)
          end
        end

        def secret_group_variables(environment:, ref:)
          if memoize_secret_variables?
            memoized_secret_group_variables(environment: environment)
          else
            return [] unless project.group

            project.group.ci_variables_for(ref, project, environment: environment)
          end
        end

        def secret_project_variables(environment:, ref:)
          if memoize_secret_variables?
            memoized_secret_project_variables(environment: environment)
          else
            project.ci_variables_for(ref: ref, environment: environment)
          end
        end

        private

        attr_reader :pipeline
        attr_reader :instance_variables_builder
        attr_reader :project_variables_builder
        attr_reader :group_variables_builder

        delegate :project, to: :pipeline

        def predefined_variables(job)
          Gitlab::Ci::Variables::Collection.new.tap do |variables|
            variables.append(key: 'CI_JOB_NAME', value: job.name)
            variables.append(key: 'CI_JOB_STAGE', value: job.stage)
            variables.append(key: 'CI_JOB_MANUAL', value: 'true') if job.action?
            variables.append(key: 'CI_PIPELINE_TRIGGERED', value: 'true') if job.trigger_request

            variables.append(key: 'CI_NODE_INDEX', value: job.options[:instance].to_s) if job.options&.include?(:instance)
            variables.append(key: 'CI_NODE_TOTAL', value: ci_node_total_value(job).to_s)

            # legacy variables
            variables.append(key: 'CI_BUILD_NAME', value: job.name)
            variables.append(key: 'CI_BUILD_STAGE', value: job.stage)
            variables.append(key: 'CI_BUILD_TRIGGERED', value: 'true') if job.trigger_request
            variables.append(key: 'CI_BUILD_MANUAL', value: 'true') if job.action?
          end
        end

        def memoized_secret_project_variables(environment:)
          strong_memoize_with(:secret_project_variables, environment) do
            project_variables_builder
              .secret_variables(
                environment: environment,
                protected_ref: protected_ref?)
          end
        end

        def memoized_secret_group_variables(environment:)
          strong_memoize_with(:secret_group_variables, environment) do
            group_variables_builder
              .secret_variables(
                environment: environment,
                protected_ref: protected_ref?)
          end
        end

        def ci_node_total_value(job)
          parallel = job.options&.dig(:parallel)
          parallel = parallel.dig(:total) if parallel.is_a?(Hash)
          parallel || 1
        end

        def protected_ref?
          strong_memoize(:protected_ref) do
            project.protected_for?(pipeline.jobs_git_ref)
          end
        end

        def memoize_secret_variables?
          strong_memoize(:memoize_secret_variables) do
            ::Feature.enabled?(:ci_variables_builder_memoize_secret_variables,
              project,
              default_enabled: :yaml)
          end
        end

        def strong_memoize_with(name, *args)
          container = strong_memoize(name) { {} }

          if container.key?(args)
            container[args]
          else
            container[args] = yield
          end
        end
      end
    end
  end
end