Kludge used to override default job:only:[branches, tags] in rules-based Job config

Problem

We currently use a little bit of a hack to override default Entry::Policy values for job:only and job:except in the CI configuration.

Gitlab::Ci::Config::Entry::Policy has a DEFAULT_ONLY = { refs: %w[branches tags] }.freeze constant that is set as the default value for only: at the class level in Entry::Job.

          entry :only, Entry::Policy,
            description: 'Refs policy this job will be executed for.',
            default: Entry::Policy::DEFAULT_ONLY

When using Job rules, we need to optionally override this default at the instance level, because we don't know when this entry is defined whether or not we will have job rules. And to make the default optionally not be set at the instance level, we would need to add logic about neighboring Entry::Rules sections into Entry::Policy, which is a violation of good encapsulation.

To truly fix this would require a nontrivial refactor of how default values are set for Entry objects.

In the mean time, this is our implementation of Entry::Job#compose!.

def compose!(deps = nil)
  super do
    if type_defined? && !stage_defined?
      @entries[:stage] = @entries[:type]
    end

    @entries.delete(:type)

    # This is something of a hack, see issue for details:
    # https://gitlab.com/gitlab-org/gitlab/-/issues/31685
    if !only_defined? && has_rules?
      @entries.delete(:only)
      @entries.delete(:except)
    end
  end
end

Update:

This also affects workflow:rules. The following configuration should create a single merge request pipeline, but instead will create nothing:

workflow:
  rules:
    - if: $CI_MERGE_REQUEST_ID

rspec:
  script: echo Hello World

The workflow:rules specifies that this should only run as a merge request pipeline, but the default only:[branches,tags] excludes the single job, so no pipeline will be created.

Proposals

Short-term (read: "bad")

We can add another logical possibility to the kludge: check to see if workflow is using rules and delete the default entry of job:only.

- if !only_defined? && has_rules?
+ if !only_defined? && (has_rules? || workflow_has_rules?)

Better? Less Bad? But also relies on hypothetical pseudocode.

Override Entry::Node#default= on Entry::Policy. Since the defaults are passed into individual instances of Entry::Node in Entry::Factory:

        def fabricate(entry_class, value = nil)
          entry_class.new(value, @metadata) do |node|
            ...
            node.default = @attributes[:default]
            ...

We can define a setter on Entry::Policy to prevent setting the default if there are workflow rules that apply to this pipeline:

def default=(value)
  return if workflow_rules? # workflow_used? is completely made up. I don't know how to check for workflow config from an Entry::Node.
  @default = value
end
Edited by drew stachon