Gitlabcom enable DuoCore for new subscription
What does this MR do and why?
For issue #530705+
This MR enables DuoCore features automatically for new subscriptions. When a customer purchases a new subscription with DuoCore, the system will now automatically enable the DuoCore features in their namespace settings by setting duo_nano_features_enabled to true.
The changes include:
- Extending the API to support a
new_subscriptionflag parameter for add-on purchases - Adding logic to enable the namespace settings
duo_nano_features_enabledwhennew_subscription: truefor DuoCore add-on-purchase.- if customer has not already set DuoCore setting(means
duo_nano_features_enabled: nil), automatically setduo_nano_features_enabled: true - if customer has a previous DuoCore setting, respect customer's decision.
- if customer has not already set DuoCore setting(means
References
Screenshots or screen recordings
| Before | After |
|---|---|
How to set up and validate locally
- Start Gitlab (In Gitlab.com mode: GDK env
GITLAB_SIMULATE_SAAS=1) - In Gitlab rails console, find a root namespace
My local test result
[109] pry(main)> g = Group.last
Group Load (0.5ms) 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"."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' ORDER BY "namespaces"."id" DESC LIMIT 1 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:(pry):90:in `__pry__'*/
Route Load (0.2ms) SELECT "routes".* FROM "routes" WHERE "routes"."source_id" = 112 AND "routes"."source_type" = 'Namespace' LIMIT 1 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:/app/models/concerns/routable.rb:155:in `block in full_attribute'*/
=> #<Group id:112 @g_016>
[110] pry(main)>
[111] pry(main)> gs = g.gitlab_subscription
GitlabSubscription Load (0.8ms) SELECT "gitlab_subscriptions".* FROM "gitlab_subscriptions" WHERE "gitlab_subscriptions"."namespace_id" = 112 LIMIT 1 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:(pry):91:in `__pry__'*/
=> #<GitlabSubscription:0x00000001618cb6a0
id: 20,
created_at: Sun, 27 Apr 2025 12:52:38.905955000 UTC +00:00,
updated_at: Mon, 28 Apr 2025 14:59:42.680078000 UTC +00:00,
start_date: Sun, 27 Apr 2025,
end_date: Mon, 27 Apr 2026,
trial_ends_on: nil,
namespace_id: 112,
hosted_plan_id: 4,
max_seats_used: 1,
seats: 15,
trial: false,
trial_starts_on: nil,
auto_renew: true,
seats_in_use: 1,
seats_owed: 0,
trial_extension_type: nil,
max_seats_used_changed_at: Mon, 28 Apr 2025 06:00:05.255085000 UTC +00:00,
last_seat_refresh_at: Mon, 28 Apr 2025 06:00:04.994856000 UTC +00:00>
[112] pry(main)>
[113] pry(main)> Plan.find(4)
Plan Load (0.4ms) SELECT "plans".* FROM "plans" WHERE "plans"."id" = 4 LIMIT 1 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:(pry):92:in `__pry__'*/
=> #<Plan:0x000000031137d700
id: 4,
created_at: Mon, 17 Mar 2025 13:28:39.894998000 UTC +00:00,
updated_at: Mon, 17 Mar 2025 13:28:39.894998000 UTC +00:00,
name: "premium",
title: "[FILTERED]">
[114] pry(main)>
[115] pry(main)> GitlabSubscriptions::AddOnPurchase.for_duo_core.where(namespace_id: g.id)
GitlabSubscriptions::AddOnPurchase Load (0.7ms) SELECT "subscription_add_on_purchases".* FROM "subscription_add_on_purchases" WHERE "subscription_add_on_purchases"."subscription_add_on_id" IN (SELECT "subscription_add_ons"."id" FROM "subscription_add_ons" WHERE "subscription_add_ons"."name" = 5) AND "subscription_add_on_purchases"."namespace_id" = 112 /* loading for pp */ LIMIT 11 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:<internal:kernel>:187:in `loop'*/
=> []
[116] pry(main)>
[117] pry(main)> g.namespace_settings.reload.duo_nano_features_enabled
NamespaceSetting Load (0.8ms) 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"."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", "namespace_settings"."early_access_program_participant", "namespace_settings"."remove_dormant_members", "namespace_settings"."remove_dormant_members_period", "namespace_settings"."early_access_program_joined_by_id", "namespace_settings"."seat_control", "namespace_settings"."last_dormant_member_review_at", "namespace_settings"."enterprise_users_extensions_marketplace_opt_in_status", "namespace_settings"."spp_repository_pipeline_access", "namespace_settings"."lock_spp_repository_pipeline_access", "namespace_settings"."archived", "namespace_settings"."resource_access_token_notify_inherited", "namespace_settings"."lock_resource_access_token_notify_inherited", "namespace_settings"."pipeline_variables_default_role", "namespace_settings"."force_pages_access_control", "namespace_settings"."extended_grat_expiry_webhooks_execute", "namespace_settings"."jwt_ci_cd_job_token_enabled", "namespace_settings"."jwt_ci_cd_job_token_opted_out", "namespace_settings"."require_dpop_for_manage_api_endpoints", "namespace_settings"."job_token_policies_enabled", "namespace_settings"."security_policies", "namespace_settings"."duo_nano_features_enabled" FROM "namespace_settings" WHERE "namespace_settings"."namespace_id" = 112 LIMIT 1 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:(pry):94:in `__pry__'*/
NamespaceSetting Load (0.4ms) 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"."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", "namespace_settings"."early_access_program_participant", "namespace_settings"."remove_dormant_members", "namespace_settings"."remove_dormant_members_period", "namespace_settings"."early_access_program_joined_by_id", "namespace_settings"."seat_control", "namespace_settings"."last_dormant_member_review_at", "namespace_settings"."enterprise_users_extensions_marketplace_opt_in_status", "namespace_settings"."spp_repository_pipeline_access", "namespace_settings"."lock_spp_repository_pipeline_access", "namespace_settings"."archived", "namespace_settings"."resource_access_token_notify_inherited", "namespace_settings"."lock_resource_access_token_notify_inherited", "namespace_settings"."pipeline_variables_default_role", "namespace_settings"."force_pages_access_control", "namespace_settings"."extended_grat_expiry_webhooks_execute", "namespace_settings"."jwt_ci_cd_job_token_enabled", "namespace_settings"."jwt_ci_cd_job_token_opted_out", "namespace_settings"."require_dpop_for_manage_api_endpoints", "namespace_settings"."job_token_policies_enabled", "namespace_settings"."security_policies", "namespace_settings"."duo_nano_features_enabled" FROM "namespace_settings" WHERE "namespace_settings"."namespace_id" = 112 LIMIT 1 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:(pry):94:in `__pry__'*/
=> false
[118] pry(main)>
- Use a curl script to send provision request to the namespace, with
new_subscription: truein the add-on params
My local test result
NOTE: if you want to repeat this script in your local, you need to get the X-CUSTOMERS-DOT-INTERNAL-TOKEN by running Gitlab::Api::Internal::GenerateJwt.instance.execute under CustomersDot rails console.
NOTE: the duo_core add-on has \"new_subscription\":true in the below request data.
qingyuzhao@qzhao--20220912-2L0FG ~ % curl -X POST \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-AUTHENTICATION-TYPE: oidc" \
-H "X-CUSTOMERS-DOT-INTERNAL-TOKEN: eyJ0eXAiOiJKV1QiLCJraWQiOiI5YzNjYmM4MzBkNmQwN2JjYWUzNGRlYjc4M2QxNTUzYmMwODk4YTcyOTQzN2EzMmRmMjJiZWY1OTg0MjkwN2Y4IiwiYWxnIjoiUlMyNTYifQ.eyJqdGkiOiIyNDRkYTc5MC01MGFjLTQ1ZDctYjM0YS0xMmJhYThmYzk5YjMiLCJhdWQiOiJnaXRsYWItc3Vic2NyaXB0aW9ucyIsInN1YiI6ImN1c3RvbWVycy1kb3QtaW50ZXJuYWwtYXBpIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwLyIsImlhdCI6MTc0NTg5OTIwOSwibmJmIjoxNzQ1ODk5MjA0LCJleHAiOjE3NDU4OTk1MDl9.ccJEXe-wL8aYpji1BfXz-g9UkoJv_1yZbdQzb2FO39Y7_bxZd1mRrR1pA6ciMdQmmkYkKL-ZmuVJFLDlRFB2iefkTmaAJYCRIgTLClFXOaIBUNfuyJ1jVfNygwwGdeMdp2xD8-wwi8C7AoFI1QGDjPLzEBb70T01Vfopzb42APm8j5YCY5pYE1BXWbpe4u837JWlYegIouM1umvA6Ir9FypV4iX1KmGUDlDoT-XkbeJwoupU60Zgd9OA3X3ZU8bAH082PALjcSXzV_qG_pxRQfvteD_BCFgoN_YqgsnZBk9v8jlgO1JdwVPfju2aVXDi7lv3X5cpZbqHAFUZOfYd1Q" \
-d "{\"provision\":{\"base_product\":{\"auto_renew\":true,\"start_date\":\"2025-04-27\",\"end_date\":\"2026-04-27\",\"plan_code\":\"premium\",\"trial\":false,\"seats\":15},\"compute_minutes\":{\"shared_runners_minutes_limit\":10000},\"storage\":{\"additional_purchased_storage_size\":0,\"additional_purchased_storage_ends_on\":null},\"add_on_purchases\":{\"duo_core\":[{\"quantity\":15,\"started_on\":\"2025-04-27\",\"expires_on\":\"2026-05-11\",\"purchase_xid\":\"A-S00180871\",\"trial\":false,\"new_subscription\":true}],\"duo_pro\":[{\"expires_on\":\"2025-04-28\",\"started_on\":\"2025-04-28\",\"deprovision\":true}],\"duo_enterprise\":[{\"expires_on\":\"2025-04-28\",\"started_on\":\"2025-04-28\",\"deprovision\":true}],\"product_analytics\":[{\"expires_on\":\"2025-04-28\",\"started_on\":\"2025-04-28\",\"deprovision\":true}]}},\"compute_minutes_info\":{\"minutes\":0,\"purchased_at\":null}}" \
http://localhost:3000/api/v4/internal/gitlab_subscriptions/namespaces/112/provision
200% qingyuzhao@qzhao--20220912-2L0FG ~ %
- In Gitlab rails console, check the group setting
duo_nano_features_enabledis set totrue.
My local test result
[126] pry(main)> GitlabSubscriptions::AddOnPurchase.for_duo_core.where(namespace_id: g.id)
GitlabSubscriptions::AddOnPurchase Load (1.0ms) SELECT "subscription_add_on_purchases".* FROM "subscription_add_on_purchases" WHERE "subscription_add_on_purchases"."subscription_add_on_id" IN (SELECT "subscription_add_ons"."id" FROM "subscription_add_ons" WHERE "subscription_add_ons"."name" = 5) AND "subscription_add_on_purchases"."namespace_id" = 112 /* loading for pp */ LIMIT 11 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:<internal:kernel>:187:in `loop'*/
=> [#<GitlabSubscriptions::AddOnPurchase:0x00000003114d3848
id: 88,
created_at: Tue, 29 Apr 2025 04:01:01.152060000 UTC +00:00,
updated_at: Tue, 29 Apr 2025 04:01:01.152060000 UTC +00:00,
subscription_add_on_id: 17,
namespace_id: 112,
quantity: 15,
expires_on: Mon, 11 May 2026,
purchase_xid: "A-S00180871",
last_assigned_users_refreshed_at: nil,
trial: false,
started_at: Sun, 27 Apr 2025,
organization_id: 1>]
[127] pry(main)>
[128] pry(main)> g.namespace_settings.reload.duo_nano_features_enabled
NamespaceSetting Load (0.4ms) 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"."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", "namespace_settings"."early_access_program_participant", "namespace_settings"."remove_dormant_members", "namespace_settings"."remove_dormant_members_period", "namespace_settings"."early_access_program_joined_by_id", "namespace_settings"."seat_control", "namespace_settings"."last_dormant_member_review_at", "namespace_settings"."enterprise_users_extensions_marketplace_opt_in_status", "namespace_settings"."spp_repository_pipeline_access", "namespace_settings"."lock_spp_repository_pipeline_access", "namespace_settings"."archived", "namespace_settings"."resource_access_token_notify_inherited", "namespace_settings"."lock_resource_access_token_notify_inherited", "namespace_settings"."pipeline_variables_default_role", "namespace_settings"."force_pages_access_control", "namespace_settings"."extended_grat_expiry_webhooks_execute", "namespace_settings"."jwt_ci_cd_job_token_enabled", "namespace_settings"."jwt_ci_cd_job_token_opted_out", "namespace_settings"."require_dpop_for_manage_api_endpoints", "namespace_settings"."job_token_policies_enabled", "namespace_settings"."security_policies", "namespace_settings"."duo_nano_features_enabled" FROM "namespace_settings" WHERE "namespace_settings"."namespace_id" = 112 LIMIT 1 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:(pry):98:in `__pry__'*/
=> true
[129] pry(main)>
- Set group setting
duo_nano_features_enabledtofalsefor the next test
My local test
[129] pry(main)> g.namespace_settings.update!(duo_nano_features_enabled: false)
TRANSACTION (0.3ms) BEGIN
Namespace Load (1.5ms) 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"."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 find_namespaces_by_id(112) AS namespaces WHERE ("namespaces"."id" IS NOT NULL) LIMIT 1 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:/ee/app/models/ee/namespace_setting.rb:205:in `valid_namespace_for_duo_core_features'*/
NamespaceSetting Update (0.4ms) UPDATE "namespace_settings" SET "updated_at" = '2025-04-29 04:06:16.176254', "duo_nano_features_enabled" = FALSE WHERE "namespace_settings"."namespace_id" = 112 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:(pry):99:in `__pry__'*/
TRANSACTION (0.1ms) COMMIT
=> true
[130] pry(main)> g.namespace_settings.reload.duo_nano_features_enabled
NamespaceSetting Load (0.7ms) 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"."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", "namespace_settings"."early_access_program_participant", "namespace_settings"."remove_dormant_members", "namespace_settings"."remove_dormant_members_period", "namespace_settings"."early_access_program_joined_by_id", "namespace_settings"."seat_control", "namespace_settings"."last_dormant_member_review_at", "namespace_settings"."enterprise_users_extensions_marketplace_opt_in_status", "namespace_settings"."spp_repository_pipeline_access", "namespace_settings"."lock_spp_repository_pipeline_access", "namespace_settings"."archived", "namespace_settings"."resource_access_token_notify_inherited", "namespace_settings"."lock_resource_access_token_notify_inherited", "namespace_settings"."pipeline_variables_default_role", "namespace_settings"."force_pages_access_control", "namespace_settings"."extended_grat_expiry_webhooks_execute", "namespace_settings"."jwt_ci_cd_job_token_enabled", "namespace_settings"."jwt_ci_cd_job_token_opted_out", "namespace_settings"."require_dpop_for_manage_api_endpoints", "namespace_settings"."job_token_policies_enabled", "namespace_settings"."security_policies", "namespace_settings"."duo_nano_features_enabled" FROM "namespace_settings" WHERE "namespace_settings"."namespace_id" = 112 LIMIT 1 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:(pry):100:in `__pry__'*/
=> false
[131] pry(main)>
- Another API endpoint to sync add_on_purchase is bulk_sync endpoint. Use a curl script to send bulk_sync request to the namespace, with
new_subscription: truein the add-on params
My local test result
qingyuzhao@qzhao--20220912-2L0FG ~ % curl -X POST \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-AUTHENTICATION-TYPE: oidc" \
-H "X-CUSTOMERS-DOT-INTERNAL-TOKEN: eyJ0eXAiOiJKV1QiLCJraWQiOiI5YzNjYmM4MzBkNmQwN2JjYWUzNGRlYjc4M2QxNTUzYmMwODk4YTcyOTQzN2EzMmRmMjJiZWY1OTg0MjkwN2Y4IiwiYWxnIjoiUlMyNTYifQ.eyJqdGkiOiIwYjlmZmI0Yi03OGFjLTQwNmItYTI5Zi02ZTU0ODM3MWFjMmIiLCJhdWQiOiJnaXRsYWItc3Vic2NyaXB0aW9ucyIsInN1YiI6ImN1c3RvbWVycy1kb3QtaW50ZXJuYWwtYXBpIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwLyIsImlhdCI6MTc0NTg5OTY2NiwibmJmIjoxNzQ1ODk5NjYxLCJleHAiOjE3NDU4OTk5NjZ9.REDX6bTP-oUU_EtLj9nAlLd0NH_OzUNUYQNifH_BedIlb6MEc44-v1cAXovw2b64R140H_E9iUaHDi48JcpME9pDsywrfQydXgkOvPvcEenMuJE-GHKLCRarCl-uV-yiyFBigQ3VWKgcYveOR4Dh6iYFININvUvNBDejKEe8JqakzBgupgEa2IDHIhSM-9_AuAUI3--_zsNC1yIRmYRmx7hX_nNGsSTuH_FIcLxRRGMCfW5nR1jUcjGliMXOjVKZZeLuWzm7eouBahFM_twQbmm4ItYi_fKwdFGTvKyZJxaPW9283C8qjePd8nwCIBrh8nTx6Qm9CEyRNGdbkJRdGg" \
-d "{\"add_on_purchases\":{\"duo_core\":[{\"quantity\":15,\"started_on\":\"2025-04-27\",\"expires_on\":\"2026-05-11\",\"purchase_xid\":\"A-S00180871\",\"trial\":false,\"new_subscription\":true}],\"duo_pro\":[{\"expires_on\":\"2025-04-28\",\"started_on\":\"2025-04-28\",\"deprovision\":true}],\"duo_enterprise\":[{\"expires_on\":\"2025-04-28\",\"started_on\":\"2025-04-28\",\"deprovision\":true}],\"product_analytics\":[{\"expires_on\":\"2025-04-28\",\"started_on\":\"2025-04-28\",\"deprovision\":true}]}}" \
http://localhost:3000/api/v4/internal/gitlab_subscriptions/namespaces/112/subscription_add_on_purchases
[{"namespace_id":112,"namespace_name":"g_016","add_on":"Duo Core","quantity":15,"started_on":"2025-04-27","expires_on":"2026-05-11","purchase_xid":"A-S00180871","trial":false}]% qingyuzhao@qzhao--20220912-2L0FG ~ %
- In Gitlab rails console, check the group setting
duo_nano_features_enabledis set totrue
My local test result
[134] pry(main)> g.namespace_settings.reload.duo_nano_features_enabled
NamespaceSetting Load (0.9ms) 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"."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", "namespace_settings"."early_access_program_participant", "namespace_settings"."remove_dormant_members", "namespace_settings"."remove_dormant_members_period", "namespace_settings"."early_access_program_joined_by_id", "namespace_settings"."seat_control", "namespace_settings"."last_dormant_member_review_at", "namespace_settings"."enterprise_users_extensions_marketplace_opt_in_status", "namespace_settings"."spp_repository_pipeline_access", "namespace_settings"."lock_spp_repository_pipeline_access", "namespace_settings"."archived", "namespace_settings"."resource_access_token_notify_inherited", "namespace_settings"."lock_resource_access_token_notify_inherited", "namespace_settings"."pipeline_variables_default_role", "namespace_settings"."force_pages_access_control", "namespace_settings"."extended_grat_expiry_webhooks_execute", "namespace_settings"."jwt_ci_cd_job_token_enabled", "namespace_settings"."jwt_ci_cd_job_token_opted_out", "namespace_settings"."require_dpop_for_manage_api_endpoints", "namespace_settings"."job_token_policies_enabled", "namespace_settings"."security_policies", "namespace_settings"."duo_nano_features_enabled" FROM "namespace_settings" WHERE "namespace_settings"."namespace_id" = 112 LIMIT 1 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:qzhao--20220912-2L0FG,console_username:qingyuzhao,line:(pry):101:in `__pry__'*/
=> true
[135] pry(main)>
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Related to #530705