GET /projects returns public_jobs, but POST and PUT still only take public_builds

Summary

The build terminology has been deprecated since 9.0 but the project create and update API endpoints still require public_builds as a param, rather than public_jobs, which is the new, remapped param for the GET endpoint, and the only one returned in the GET response.

The build naming has been deprecated for so long you can't even find it in current docs anymore, see 12.10 for example: https://docs.gitlab.com/12.10/ee/ci/variables/deprecated_variables.html

Steps to reproduce

List projects/Get a single project and see that public_jobs is returned:

curl -H 'Private-Token: <token>' "https://gitlab.example.com/api/v4/projects`

Create or update a project and see that only public_builds is accepted:

curl -X POST -H 'Private-Token: <token>' --data '{"name":"<example-name>", "public_jobs":"false"}'  https://gitlab.example.com/api/v4/projects
# 400: allow_merge_on_skipped_pipeline, autoclose_referenced_issues, auto_devops_enabled, auto_devops_deploy_strategy, auto_cancel_pending_pipelines, build_coverage_regex, build_git_strategy, build_timeout, builds_access_level, ci_config_path, ci_default_git_depth, ci_forward_deployment_enabled, container_registry_enabled, container_expiration_policy_attributes, default_branch, description, emails_disabled, forking_access_level, issues_access_level, lfs_enabled, merge_requests_access_level, merge_method, name, only_allow_merge_if_all_discussions_are_resolved, only_allow_merge_if_pipeline_succeeds, pages_access_level, path, printing_merge_request_link_enabled, public_builds, remove_source_branch_after_merge, repository_access_level, request_access_enabled, resolve_outdated_diff_discussions, restrict_user_defined_variables, shared_runners_enabled, snippets_access_level, tag_list, visibility, wiki_access_level, avatar, suggestion_commit_message, repository_storage, compliance_framework_setting, packages_enabled, service_desk_enabled, issues_enabled, jobs_enabled, merge_requests_enabled, wiki_enabled, snippets_enabled, approvals_before_merge, external_authorization_classification_label, fallback_approvals_required, import_url, issues_template, merge_requests_template are missing, at least one parameter must be provided

curl -X POST -H 'Private-Token: <token>' --data '{"name":"<example-name>", "public_builds":"false"}'  https://gitlab.example.com/api/v4/projects
# 201 Created

Example Project

Try the above GET/PUT on any project on any instance.

What is the current bug behavior?

Get returns public_jobs but this cannot be used to create/update projects. This is painful for people working with the API programmatically who expect to work with consistent attributes in their objects. See for example https://github.com/python-gitlab/python-gitlab/issues/1428.

What is the expected correct behavior?

A consistent public_jobs param should be used for all endpoints.

Possible fixes

Although public_builds is exposed as public_jobs for the GET endpoint, public_builds should probably be renamed instead. Maybe an opportunity to make a breaking change in 14.0? See it scattered around the codebase:

./app/views/projects/settings/ci_cd/_form.html.haml:            = f.check_box :public_builds, { class: 'form-check-input' }
./app/views/projects/settings/ci_cd/_form.html.haml:            = f.label :public_builds, class: 'form-check-label' do
./app/graphql/types/project_type.rb:    field :public_jobs, GraphQL::BOOLEAN_TYPE, method: :public_builds, null: true,
./app/controllers/projects_controller.rb:      :public_builds,
./app/controllers/projects/settings/ci_cd_controller.rb:          :build_timeout_human_readable, :build_coverage_regex, :public_builds,
./app/policies/project_policy.rb:  condition(:public_builds, scope: :subject, score: 0) { project.public_builds? }
./app/policies/project_policy.rb:  rule { public_builds }.policy do
./app/policies/project_policy.rb:  rule { public_builds & can?(:guest_access) }.policy do
./spec/finders/ci/jobs_finder_spec.rb:  let_it_be(:project) { create(:project, :private, public_builds: false) }
./spec/lib/gitlab/cycle_analytics/permissions_spec.rb:  let(:project) { create(:project, public_builds: false) }
./spec/lib/gitlab/ci/status/build/common_spec.rb:        project.update!(public_builds: false)
./spec/lib/gitlab/import_export/safe_model_attributes.yml:- public_builds
./spec/presenters/ci/build_presenter_spec.rb:      let(:project) { create(:project, public_builds: false) }
./spec/requests/api/merge_requests_spec.rb:        project = create(:project, public_builds: false)
./spec/requests/api/jobs_spec.rb:    create(:project, :repository, public_builds: false)
./spec/requests/api/jobs_spec.rb:            project.update_column(:public_builds, true)
./spec/requests/api/jobs_spec.rb:            project.update_column(:public_builds, true)
./spec/requests/api/jobs_spec.rb:              project.update_column(:public_builds, true)
./spec/requests/api/jobs_spec.rb:            project.update_column(:public_builds, false)
./spec/requests/api/jobs_spec.rb:            project.update_column(:public_builds, true)
./spec/requests/api/jobs_spec.rb:            project.update_column(:public_builds, true)
./spec/requests/api/jobs_spec.rb:      let(:public_builds) { true }
./spec/requests/api/jobs_spec.rb:                       public_builds: public_builds)
./spec/requests/api/jobs_spec.rb:          let(:public_builds) { true }
./spec/requests/api/jobs_spec.rb:          let(:public_builds) { false }
./spec/requests/api/jobs_spec.rb:          let(:public_builds) { true }
./spec/requests/api/jobs_spec.rb:          let(:public_builds) { true }
./spec/requests/api/jobs_spec.rb:        let(:public_builds) { true }
./spec/requests/api/jobs_spec.rb:      where(:public_builds, :user_project_role, :expected_status) do
./spec/requests/api/jobs_spec.rb:          project.update!(public_builds: public_builds)
./spec/requests/api/ci/pipelines_spec.rb:        project.update!(public_builds: false)
./spec/requests/api/ci/pipelines_spec.rb:        project.update!(public_builds: false)
./spec/requests/api/ci/pipeline_schedules_spec.rb:  let_it_be(:project) { create(:project, :repository, public_builds: false) }
./spec/requests/api/ci/pipeline_schedules_spec.rb:      let_it_be(:project) { create(:project, :repository, :public, public_builds: false) }
./spec/requests/api/project_attributes.yml:    public_builds: public_jobs
./spec/requests/projects/cycle_analytics_events_spec.rb:  let(:project) { create(:project, :repository, public_builds: false) }
./spec/features/security/project/public_access_spec.rb:        project.update!(public_builds: true)
./spec/features/security/project/public_access_spec.rb:        project.update!(public_builds: false)
./spec/features/security/project/public_access_spec.rb:        project.update!(public_builds: true)
./spec/features/security/project/public_access_spec.rb:        project.update!(public_builds: false)
./spec/features/security/project/public_access_spec.rb:        project.update!(public_builds: true)
./spec/features/security/project/public_access_spec.rb:        project.update!(public_builds: false)
./spec/features/security/project/internal_access_spec.rb:        project.update!(public_builds: true)
./spec/features/security/project/internal_access_spec.rb:        project.update!(public_builds: false)
./spec/features/security/project/internal_access_spec.rb:        project.update!(public_builds: true)
./spec/features/security/project/internal_access_spec.rb:        project.update!(public_builds: false)
./spec/features/security/project/internal_access_spec.rb:        project.update!(public_builds: true)
./spec/features/security/project/internal_access_spec.rb:        project.update!(public_builds: false)
./spec/features/security/project/private_access_spec.rb:  let_it_be(:project, reload: true) { create(:project, :private, :repository, public_builds: false) }
./spec/features/security/project/private_access_spec.rb:        project.update!(public_builds: true)
./spec/features/security/project/private_access_spec.rb:        project.update!(public_builds: true)
./spec/features/security/project/private_access_spec.rb:        project.update!(public_builds: true)
./spec/features/security/project/private_access_spec.rb:        project.update!(public_builds: true)
./spec/features/security/project/private_access_spec.rb:        project.public_builds = false
./spec/features/security/project/private_access_spec.rb:        project.update!(public_builds: true)
./spec/features/security/project/private_access_spec.rb:        project.update!(public_builds: false)
./spec/features/merge_request/user_sees_merge_widget_spec.rb:        public_builds: false
./spec/features/dashboard/projects_spec.rb:        project.update!(public_builds: false)
./spec/features/projects/jobs/permissions_spec.rb:        project.update!(public_builds: false)
./spec/features/projects/jobs/permissions_spec.rb:        project.update!(public_builds: true)
./spec/features/projects/jobs/permissions_spec.rb:          project.update!(public_builds: false)
./spec/features/projects/jobs/permissions_spec.rb:      where(:public_builds, :user_project_role, :ci_debug_trace, :expected_status_code) do
./spec/features/projects/jobs/permissions_spec.rb:          project.update!(public_builds: public_builds)
./spec/features/projects/jobs/permissions_spec.rb:      where(:public_builds, :user_project_role, :ci_debug_trace, :expected_status_code, :expected_msg) do
./spec/features/projects/jobs/permissions_spec.rb:          project.update!(public_builds: public_builds)
./spec/features/projects/user_sees_sidebar_spec.rb:  let(:project) { create(:project, :private, public_builds: false, namespace: user.namespace) }
./spec/features/projects/user_sees_sidebar_spec.rb:      project.public_builds = true
./spec/features/projects/pipelines/pipeline_spec.rb:        let(:project) { create(:project, :public, :repository, public_builds: false) }
./spec/features/projects/pipelines/pipeline_spec.rb:      project.update!(public_builds: false)
./spec/features/projects/pipelines/pipeline_spec.rb:        project.update!(public_builds: false)
./spec/features/commits_spec.rb:            public_builds: false)
./spec/controllers/projects/graphs_controller_spec.rb:          project.update_column(:public_builds, false)
./spec/controllers/projects/badges_controller_spec.rb:        project.update!(public_builds: true)
./spec/controllers/projects/badges_controller_spec.rb:        project.update!(public_builds: false)
./spec/support/shared_examples/requests/api/pipelines/visibility_table_shared_examples.rb:      public_builds: public_builds
./spec/support/shared_examples/requests/api/pipelines/visibility_table_shared_examples.rb:  where(:visibility_level, :builds_access_level, :public_builds, :is_admin, :user_role, :response_status) do
./spec/support/shared_examples/policies/project_policy_shared_examples.rb:    project.update!(public_builds: false)
./spec/support/shared_examples/policies/project_policy_shared_examples.rb:        project.update!(public_builds: false)
./spec/support/shared_examples/policies/project_policy_shared_examples.rb:        project.update!(public_builds: false)
./spec/support/shared_examples/policies/project_policy_shared_examples.rb:        project.update!(public_builds: false)
./spec/fixtures/lib/gitlab/import_export/designs/project.json:   "public_builds":true,
./spec/fixtures/lib/gitlab/import_export/multi_pipeline_ref_one_external_pr/project.json:    "public_builds": true,
./spec/fixtures/lib/gitlab/import_export/multi_pipeline_ref_one_external_pr/tree/project.json:{"id":5,"approvals_before_merge":0,"archived":false,"auto_cancel_pending_pipelines":"enabled","autoclose_referenced_issues":true,"build_allow_git_fetch":true,"build_coverage_regex":null,"build_timeout":3600,"ci_config_path":null,"delete_error":null,"description":"Vim, Tmux and others","disable_overriding_approvers_per_merge_request":null,"external_authorization_classification_label":"","external_webhook_token":"D3mVYFzZkgZ5kMfcW_wx","public_builds":true,"shared_runners_enabled":true,"visibility_level":20}
./spec/services/notification_service_spec.rb:            project.update!(public_builds: false)
./spec/services/projects/update_service_spec.rb:        result = update_project(project, admin, public_builds: true)
./spec/services/projects/update_service_spec.rb:        expect(project.reload.public_builds).to be true
./spec/policies/ci/build_policy_spec.rb:      project.update_attribute(:public_builds, false)
./lib/api/entities/project.rb:      expose :public_builds, as: :public_jobs
./lib/api/helpers/projects_helpers.rb:        optional :public_builds, type: Boolean, desc: 'Perform public builds'
./lib/api/helpers/projects_helpers.rb:          :public_builds,
./db/structure.sql:    public_builds boolean DEFAULT true NOT NULL,
./db/migrate/20181228175414_init_schema.rb:      t.boolean "public_builds", default: true, null: false
./config/pseudonymizer.yml:    - public_builds
./doc/api/projects.md:| `public_builds`                                             | boolean | **{dotted-circle}** No | If `true`, jobs can be viewed by non-project members. |
./doc/api/projects.md:| `public_builds`                                             | boolean | **{dotted-circle}** No | If `true`, jobs can be viewed by non-project-members. |
./doc/api/projects.md:| `public_builds`                                             | boolean        | **{dotted-circle}** No | If `true`, jobs can be viewed by non-project members. |
./ee/spec/requests/api/jobs_spec.rb:    create(:project, :repository, public_builds: false)
./ee/spec/features/security/project/public_access_spec.rb:        project.update!(public_builds: true)
./ee/spec/features/security/project/public_access_spec.rb:        project.update!(public_builds: false)
./ee/spec/features/security/project/public_access_spec.rb:        project.update!(public_builds: true)
./ee/spec/features/security/project/public_access_spec.rb:        project.update!(public_builds: false)
./ee/spec/features/security/project/internal_access_spec.rb:        project.update!(public_builds: true)
./ee/spec/features/security/project/internal_access_spec.rb:        project.update!(public_builds: false)
./ee/spec/features/security/project/internal_access_spec.rb:        project.update!(public_builds: true)
./ee/spec/features/security/project/internal_access_spec.rb:        project.update!(public_builds: false)