Skip to content

Cascade duo_features_enabled on save

Jessie Young requested to merge jy-cascade-duo-features-enabled into master

What does this MR do and why?

  • This MR updates the duo_features_enabled setting so that when a parent group changes the value of the attribute, all children also get a new vaue.
  • This is already a "cascading attribute" but the way cascading attributes currently work is that only if the child has a nil value for the setting they do a lookup and find the value of the attribute for the closest ancestor and inherit that value. Otherwise, the only way a setting cascades is if the parent locks the attribute.
  • This ancestor lookup method results in an N+1. Ideally we would move all cascading settings to actuall cascade like this one so that we avoid the N+1 but I am starting with just 1.
  • Follow-up to !144931 (comment 1777185055)
  • Feature flag issue: #460874 (closed)

Changelog: changed EE: true

MR acceptance checklist

Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Screenshots or screen recordings

This parameter is not sent anywhere, but will be once we implement [Ban AI] Add REST and GraphQL mutations/resourc... (#441482 - closed) (soon!)

Database

Database analysis of queries in the newly-added Worker:

Group#all_projects

For finding all projects within the root group and its descendants.

SQL:

puts Group.first.all_projects.to_sql

SELECT "projects"."id", "projects"."name", "projects"."path", "projects"."description", "projects"."created_at", "projects"."updated_at", "projects"."creator_id", "projects"."namespace_id", "projects"."last_activity_at", "projects"."import_url", "projects"."visibility_level", "projects"."archived", "projects"."avatar", "projects"."merge_requests_template", "projects"."star_count", "projects"."merge_requests_rebase_enabled", "projects"."import_type", "projects"."import_source", "projects"."approvals_before_merge", "projects"."reset_approvals_on_push", "projects"."merge_requests_ff_only_enabled", "projects"."issues_template", "projects"."mirror", "projects"."mirror_last_update_at", "projects"."mirror_last_successful_update_at", "projects"."mirror_user_id", "projects"."shared_runners_enabled", "projects"."runners_token", "projects"."build_allow_git_fetch", "projects"."build_timeout", "projects"."mirror_trigger_builds", "projects"."pending_delete", "projects"."public_builds", "projects"."last_repository_check_failed", "projects"."last_repository_check_at", "projects"."only_allow_merge_if_pipeline_succeeds", "projects"."has_external_issue_tracker", "projects"."repository_storage", "projects"."repository_read_only", "projects"."request_access_enabled", "projects"."has_external_wiki", "projects"."ci_config_path", "projects"."lfs_enabled", "projects"."description_html", "projects"."only_allow_merge_if_all_discussions_are_resolved", "projects"."repository_size_limit", "projects"."printing_merge_request_link_enabled", "projects"."auto_cancel_pending_pipelines", "projects"."service_desk_enabled", "projects"."cached_markdown_version", "projects"."delete_error", "projects"."last_repository_updated_at", "projects"."disable_overriding_approvers_per_merge_request", "projects"."storage_version", "projects"."resolve_outdated_diff_discussions", "projects"."remote_mirror_available_overridden", "projects"."only_mirror_protected_branches", "projects"."pull_mirror_available_overridden", "projects"."jobs_cache_index", "projects"."external_authorization_classification_label", "projects"."mirror_overwrites_diverged_branches", "projects"."pages_https_only", "projects"."external_webhook_token", "projects"."packages_enabled", "projects"."merge_requests_author_approval", "projects"."pool_repository_id", "projects"."runners_token_encrypted", "projects"."bfg_object_map", "projects"."detected_repository_languages", "projects"."merge_requests_disable_committers_approval", "projects"."require_password_to_approve", "projects"."max_pages_size", "projects"."max_artifacts_size", "projects"."pull_mirror_branch_prefix", "projects"."remove_source_branch_after_merge", "projects"."marked_for_deletion_at", "projects"."marked_for_deletion_by_user_id", "projects"."autoclose_referenced_issues", "projects"."suggestion_commit_message", "projects"."project_namespace_id", "projects"."hidden", "projects"."organization_id" FROM "projects" WHERE "projects"."namespace_id" IN (SELECT namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1)] AS id FROM "namespaces" WHERE "namespaces"."type" = 'Group' AND (traversal_ids @> ('{22}')))

Postgres.ai analysis for this sql using the gitlab-org root group:

https://postgres.ai/console/gitlab/gitlab-production-tunnel-pg12/sessions/26763/commands/83520.

Group#self_and_descendants

For finding all groups within the root group.

SQL:

puts Group.first.self_and_descendants.to_sql

SELECT "namespaces"."id", "namespaces"."name", "namespaces"."path", "namespaces"."owner_id", "namespaces"."created_at", "namespaces"."updated_at", "namespaces"."type", "namespaces"."description", "namespaces"."avatar", "namespaces"."membership_lock", "namespaces"."share_with_group_lock", "namespaces"."visibility_level", "namespaces"."request_access_enabled", "namespaces"."ldap_sync_status", "namespaces"."ldap_sync_error", "namespaces"."ldap_sync_last_update_at", "namespaces"."ldap_sync_last_successful_update_at", "namespaces"."ldap_sync_last_sync_at", "namespaces"."description_html", "namespaces"."lfs_enabled", "namespaces"."parent_id", "namespaces"."shared_runners_minutes_limit", "namespaces"."repository_size_limit", "namespaces"."require_two_factor_authentication", "namespaces"."two_factor_grace_period", "namespaces"."cached_markdown_version", "namespaces"."project_creation_level", "namespaces"."runners_token", "namespaces"."file_template_project_id", "namespaces"."saml_discovery_token", "namespaces"."runners_token_encrypted", "namespaces"."custom_project_templates_group_id", "namespaces"."auto_devops_enabled", "namespaces"."extra_shared_runners_minutes_limit", "namespaces"."last_ci_minutes_notification_at", "namespaces"."last_ci_minutes_usage_notification_level", "namespaces"."subgroup_creation_level", "namespaces"."emails_disabled", "namespaces"."max_pages_size", "namespaces"."max_artifacts_size", "namespaces"."mentions_disabled", "namespaces"."default_branch_protection", "namespaces"."max_personal_access_token_lifetime", "namespaces"."push_rule_id", "namespaces"."shared_runners_enabled", "namespaces"."allow_descendants_override_disabled_shared_runners", "namespaces"."traversal_ids", "namespaces"."organization_id" FROM "namespaces" WHERE "namespaces"."type" = 'Group' AND (traversal_ids @> ('{22}'))

Postgres.ai analysis for this sql using the gitlab-org root group: https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/26763/commands/83521

ProjectSetting.for_projects(project_ids_to_update).duo_features_set(!duo_features_enabled)

For finding all project settings with duo_features_enabled set to a certain value for a set of projects.

puts ProjectSetting.for_projects(project_ids_to_update).duo_features_set(!duo_features_enabled).to_sql

SELECT
    "project_settings"."project_id",
    "project_settings"."created_at",
    "project_settings"."updated_at",
    "project_settings"."push_rule_id",
    "project_settings"."show_default_award_emojis",
    "project_settings"."allow_merge_on_skipped_pipeline",
    "project_settings"."squash_option",
    "project_settings"."has_confluence",
    "project_settings"."has_vulnerabilities",
    "project_settings"."prevent_merge_without_jira_issue",
    "project_settings"."cve_id_request_enabled",
    "project_settings"."mr_default_target_self",
    "project_settings"."previous_default_branch",
    "project_settings"."warn_about_potentially_unwanted_characters",
    "project_settings"."merge_commit_template",
    "project_settings"."has_shimo",
    "project_settings"."squash_commit_template",
    "project_settings"."legacy_open_source_license_available",
    "project_settings"."target_platforms",
    "project_settings"."enforce_auth_checks_on_uploads",
    "project_settings"."selective_code_owner_removals",
    "project_settings"."issue_branch_template",
    "project_settings"."show_diff_preview_in_email",
    "project_settings"."suggested_reviewers_enabled",
    "project_settings"."only_allow_merge_if_all_status_checks_passed",
    "project_settings"."mirror_branch_regex",
    "project_settings"."allow_pipeline_trigger_approve_deployment",
    "project_settings"."emails_enabled",
    "project_settings"."pages_unique_domain_enabled",
    "project_settings"."pages_unique_domain",
    "project_settings"."runner_registration_enabled",
    "project_settings"."product_analytics_instrumentation_key",
    "project_settings"."product_analytics_data_collector_host",
    "project_settings"."cube_api_base_url",
    "project_settings"."encrypted_cube_api_key",
    "project_settings"."encrypted_cube_api_key_iv",
    "project_settings"."encrypted_product_analytics_configurator_connection_string",
    "project_settings"."encrypted_product_analytics_configurator_connection_string_iv",
    "project_settings"."pages_multiple_versions_enabled",
    "project_settings"."allow_merge_without_pipeline",
    "project_settings"."duo_features_enabled",
    "project_settings"."require_reauthentication_to_approve"
FROM
    "project_settings"
WHERE
    "project_settings"."project_id" = 41 AND
    "project_settings"."duo_features_enabled" = FALSE;

Postgres.ai analysis for this sql using the gitlab-org root group: https://postgres.ai/console/gitlab/gitlab-production-main/sessions/28277/commands/88274

**See also: **!145876 (comment 1911124186)

`NamespaceSetting.for_namespaces(namespace_ids)

                    .duo_features_set(!duo_features_enabled)
                    .update_all(duo_features_enabled: duo_features_enabled)`
puts NamespaceSetting.where(namespace_id: namespace_ids, duo_features_enabled: !duo_features_enabled).to_sql

SELECT "namespace_settings"."created_at", "namespace_settings"."updated_at", "namespace_settings"."namespace_id", "namespace_settings"."prevent_forking_outside_group", "namespace_settings"."allow_mfa_for_subgroups", "namespace_settings"."default_branch_name", "namespace_settings"."repository_read_only", "namespace_settings"."resource_access_token_creation_allowed", "namespace_settings"."prevent_sharing_groups_outside_hierarchy", "namespace_settings"."new_user_signups_cap", "namespace_settings"."setup_for_company", "namespace_settings"."jobs_to_be_done", "namespace_settings"."runner_token_expiration_interval", "namespace_settings"."subgroup_runner_token_expiration_interval", "namespace_settings"."project_runner_token_expiration_interval", "namespace_settings"."show_diff_preview_in_email", "namespace_settings"."enabled_git_access_protocol", "namespace_settings"."unique_project_download_limit", "namespace_settings"."unique_project_download_limit_interval_in_seconds", "namespace_settings"."unique_project_download_limit_allowlist", "namespace_settings"."auto_ban_user_on_excessive_projects_download", "namespace_settings"."only_allow_merge_if_pipeline_succeeds", "namespace_settings"."allow_merge_on_skipped_pipeline", "namespace_settings"."only_allow_merge_if_all_discussions_are_resolved", "namespace_settings"."default_compliance_framework_id", "namespace_settings"."runner_registration_enabled", "namespace_settings"."allow_runner_registration_token", "namespace_settings"."unique_project_download_limit_alertlist", "namespace_settings"."emails_enabled", "namespace_settings"."experiment_features_enabled", "namespace_settings"."default_branch_protection_defaults", "namespace_settings"."service_access_tokens_expiration_enforced", "namespace_settings"."product_analytics_enabled", "namespace_settings"."allow_merge_without_pipeline", "namespace_settings"."enforce_ssh_certificates", "namespace_settings"."toggle_security_policy_custom_ci", "namespace_settings"."lock_toggle_security_policy_custom_ci", "namespace_settings"."lock_toggle_security_policies_policy_scope", "namespace_settings"."math_rendering_limits_enabled", "namespace_settings"."lock_math_rendering_limits_enabled", "namespace_settings"."duo_features_enabled", "namespace_settings"."lock_duo_features_enabled", "namespace_settings"."disable_personal_access_tokens", "namespace_settings"."enable_auto_assign_gitlab_duo_pro_seats" FROM "namespace_settings" WHERE "namespace_settings"."namespace_id" IN (125, 126) AND "namespace_settings"."duo_features_enabled" = FALSE

Postgres.ai analysis for this sql using the gitlab-org root group: https://postgres.ai/console/gitlab/gitlab-production-main/sessions/28277/commands/88276

**See also: **!145876 (comment 1911124202)

How to set up and validate locally

  1. Create a group with subgroups and projects (and even projects within the subgroups) and add a user as an owner of the group.
  2. Update the duo_features_enabled attribute on that group using EE::Groups::UpdateService.new(group, user, { duo_features_enabled: false }).execute
  3. All subgroups and projects should also now have duo_features_enable set to false
Edited by Jeff Park

Merge request reports