func resourceGitlabProjectUpdate()

in internal/provider/sdk/resource_gitlab_project.go [1232:1726]


func resourceGitlabProjectUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
	client := meta.(*gitlab.Client)

	// Always send the name field, to satisfy the requirement of having one
	// of the project attributes listed below in the update call
	// https://gitlab.com/gitlab-org/gitlab-foss/-/blob/master/lib/api/helpers/projects_helpers.rb#L120-188
	options := &gitlab.EditProjectOptions{
		Name: gitlab.Ptr(d.Get("name").(string)),
	}

	transferOptions := &gitlab.TransferProjectOptions{}

	if d.HasChange("name") {
		options.Name = gitlab.Ptr(d.Get("name").(string))
	}

	if d.HasChange("path") && (d.Get("path").(string) != "") {
		options.Path = gitlab.Ptr(d.Get("path").(string))
	}

	if d.HasChange("namespace_id") {
		transferOptions.Namespace = gitlab.Ptr(d.Get("namespace_id").(int))
	}

	if d.HasChange("description") {
		options.Description = gitlab.Ptr(d.Get("description").(string))
	}

	if d.HasChange("default_branch") {
		options.DefaultBranch = gitlab.Ptr(d.Get("default_branch").(string))
	}

	if d.HasChange("visibility_level") {
		options.Visibility = stringToVisibilityLevel(d.Get("visibility_level").(string))
	}

	if d.HasChange("merge_method") {
		options.MergeMethod = stringToMergeMethod(d.Get("merge_method").(string))
	}

	if d.HasChange("only_allow_merge_if_pipeline_succeeds") {
		options.OnlyAllowMergeIfPipelineSucceeds = gitlab.Ptr(d.Get("only_allow_merge_if_pipeline_succeeds").(bool))
	}

	if d.HasChange("only_allow_merge_if_all_discussions_are_resolved") {
		options.OnlyAllowMergeIfAllDiscussionsAreResolved = gitlab.Ptr(d.Get("only_allow_merge_if_all_discussions_are_resolved").(bool))
	}

	if d.HasChange("allow_merge_on_skipped_pipeline") {
		options.AllowMergeOnSkippedPipeline = gitlab.Ptr(d.Get("allow_merge_on_skipped_pipeline").(bool))
	}

	if d.HasChange("allow_pipeline_trigger_approve_deployment") {
		options.AllowPipelineTriggerApproveDeployment = gitlab.Ptr(d.Get("allow_pipeline_trigger_approve_deployment").(bool))
	}

	if d.HasChange("restrict_user_defined_variables") {
		options.RestrictUserDefinedVariables = gitlab.Ptr(d.Get("restrict_user_defined_variables").(bool)) //nolint:staticcheck
	}

	if d.HasChange("request_access_enabled") {
		options.RequestAccessEnabled = gitlab.Ptr(d.Get("request_access_enabled").(bool))
	}

	if d.HasChange("issues_enabled") {
		// TODO: Remove issuesEnabled on the next breaking update, since it will need to be replaced with a
		// issue access level integer.
		// nolint:staticcheck // SA1019
		options.IssuesEnabled = gitlab.Ptr(d.Get("issues_enabled").(bool))
	}

	if d.HasChange("merge_requests_enabled") {
		// TODO: Remove mergeRequestsEnabled on the next breaking update, since it will need to be replaced with a
		// merge request access level integer.
		// nolint:staticcheck // SA1019
		options.MergeRequestsEnabled = gitlab.Ptr(d.Get("merge_requests_enabled").(bool))
	}

	if d.HasChange("pipelines_enabled") {
		// nolint:staticcheck // SA1019
		options.JobsEnabled = gitlab.Ptr(d.Get("pipelines_enabled").(bool))
	}

	if d.HasChange("approvals_before_merge") {
		options.ApprovalsBeforeMerge = gitlab.Ptr(d.Get("approvals_before_merge").(int)) //nolint:staticcheck
	}

	if d.HasChange("wiki_enabled") {
		// nolint:staticcheck // SA1019
		options.WikiEnabled = gitlab.Ptr(d.Get("wiki_enabled").(bool)) //nolint:staticcheck
	}

	if d.HasChange("snippets_enabled") {
		// nolint:staticcheck // SA1019
		options.SnippetsEnabled = gitlab.Ptr(d.Get("snippets_enabled").(bool))
	}

	if d.HasChange("shared_runners_enabled") {
		options.SharedRunnersEnabled = gitlab.Ptr(d.Get("shared_runners_enabled").(bool))
	}

	if d.HasChange("group_runners_enabled") {
		options.GroupRunnersEnabled = gitlab.Ptr(d.Get("group_runners_enabled").(bool))
	}

	if d.HasChange("tags") {
		// nolint:staticcheck // SA1019
		options.TagList = stringSetToStringSlice(d.Get("tags").(*schema.Set))
	}

	if d.HasChange("container_registry_enabled") {
		// nolint:staticcheck // SA1019
		options.ContainerRegistryEnabled = gitlab.Ptr(d.Get("container_registry_enabled").(bool))
	}

	if d.HasChange("lfs_enabled") {
		options.LFSEnabled = gitlab.Ptr(d.Get("lfs_enabled").(bool))
	}

	if d.HasChange("squash_option") {
		options.SquashOption = stringToSquashOptionValue(d.Get("squash_option").(string))
	}

	if d.HasChange("remove_source_branch_after_merge") {
		options.RemoveSourceBranchAfterMerge = gitlab.Ptr(d.Get("remove_source_branch_after_merge").(bool))
	}

	if d.HasChange("printing_merge_request_link_enabled") {
		options.PrintingMergeRequestLinkEnabled = gitlab.Ptr(d.Get("printing_merge_request_link_enabled").(bool))
	}

	if d.HasChange("packages_enabled") {
		options.PackagesEnabled = gitlab.Ptr(d.Get("packages_enabled").(bool))
	}

	if d.HasChange("pages_access_level") {
		options.PagesAccessLevel = stringToAccessControlValue(d.Get("pages_access_level").(string))
	}

	if d.HasChanges("mirror", "import_url", "import_url_username", "import_url_password") {
		options.Mirror = gitlab.Ptr(d.Get("mirror").(bool))
		importURL, err := constructImportUrl(d.Get("import_url").(string), d.Get("import_url_username").(string), d.Get("import_url_password").(string))
		if err != nil {
			return diag.Errorf("Unable to construct import URL for API: %s", err)
		}
		options.ImportURL = gitlab.Ptr(importURL)
	}

	if d.HasChange("mirror_trigger_builds") {
		options.MirrorTriggerBuilds = gitlab.Ptr(d.Get("mirror_trigger_builds").(bool))
		if options.ImportURL == nil {
			importURL, err := constructImportUrl(d.Get("import_url").(string), d.Get("import_url_username").(string), d.Get("import_url_password").(string))
			if err != nil {
				return diag.Errorf("Unable to construct import URL for API: %s", err)
			}
			options.ImportURL = gitlab.Ptr(importURL)
		}
	}

	if d.HasChange("only_mirror_protected_branches") {
		options.OnlyMirrorProtectedBranches = gitlab.Ptr(d.Get("only_mirror_protected_branches").(bool))
		if options.ImportURL == nil {
			importURL, err := constructImportUrl(d.Get("import_url").(string), d.Get("import_url_username").(string), d.Get("import_url_password").(string))
			if err != nil {
				return diag.Errorf("Unable to construct import URL for API: %s", err)
			}
			options.ImportURL = gitlab.Ptr(importURL)
		}
	}

	if d.HasChange("mirror_overwrites_diverged_branches") {
		options.MirrorOverwritesDivergedBranches = gitlab.Ptr(d.Get("mirror_overwrites_diverged_branches").(bool))
		if options.ImportURL == nil {
			importURL, err := constructImportUrl(d.Get("import_url").(string), d.Get("import_url_username").(string), d.Get("import_url_password").(string))
			if err != nil {
				return diag.Errorf("Unable to construct import URL for API: %s", err)
			}
			options.ImportURL = gitlab.Ptr(importURL)
		}
	}

	if d.HasChange("issues_template") {
		options.IssuesTemplate = gitlab.Ptr(d.Get("issues_template").(string))
	}

	if d.HasChange("merge_requests_template") {
		options.MergeRequestsTemplate = gitlab.Ptr(d.Get("merge_requests_template").(string))
	}

	if d.HasChange("ci_config_path") {
		options.CIConfigPath = gitlab.Ptr(d.Get("ci_config_path").(string))
	}

	if d.HasChange("ci_id_token_sub_claim_components") {
		options.CIIdTokenSubClaimComponents = stringListToStringSlice(d.Get("ci_id_token_sub_claim_components").([]any))
	}

	if d.HasChange("ci_forward_deployment_enabled") {
		options.CIForwardDeploymentEnabled = gitlab.Ptr(d.Get("ci_forward_deployment_enabled").(bool))
	}

	if d.HasChange("ci_restrict_pipeline_cancellation_role") {
		stringVal := d.Get("ci_restrict_pipeline_cancellation_role").(string)
		options.CIRestrictPipelineCancellationRole = gitlab.Ptr(api.AccessControlLevelValueToName(stringVal))
	}

	if d.HasChange("ci_pipeline_variables_minimum_override_role") {
		stringVal := d.Get("ci_pipeline_variables_minimum_override_role").(string)
		options.CIPipelineVariablesMinimumOverrideRole = gitlab.Ptr(stringVal)
	}

	if d.HasChange("merge_pipelines_enabled") {
		options.MergePipelinesEnabled = gitlab.Ptr(d.Get("merge_pipelines_enabled").(bool))
	}

	if d.HasChange("merge_trains_enabled") {
		options.MergeTrainsEnabled = gitlab.Ptr(d.Get("merge_trains_enabled").(bool))
	}

	if d.HasChange("resolve_outdated_diff_discussions") {
		options.ResolveOutdatedDiffDiscussions = gitlab.Ptr(d.Get("resolve_outdated_diff_discussions").(bool))
	}

	if d.HasChange("analytics_access_level") {
		options.AnalyticsAccessLevel = stringToAccessControlValue(d.Get("analytics_access_level").(string))
	}

	if d.HasChange("auto_cancel_pending_pipelines") {
		options.AutoCancelPendingPipelines = gitlab.Ptr(d.Get("auto_cancel_pending_pipelines").(string))
	}

	if d.HasChange("auto_devops_deploy_strategy") {
		options.AutoDevopsDeployStrategy = gitlab.Ptr(d.Get("auto_devops_deploy_strategy").(string))
	}

	if d.HasChange("auto_devops_enabled") {
		options.AutoDevopsEnabled = gitlab.Ptr(d.Get("auto_devops_enabled").(bool))
	}

	if d.HasChange("autoclose_referenced_issues") {
		options.AutocloseReferencedIssues = gitlab.Ptr(d.Get("autoclose_referenced_issues").(bool))
	}

	if d.HasChange("build_git_strategy") {
		options.BuildGitStrategy = gitlab.Ptr(d.Get("build_git_strategy").(string))
	}

	if d.HasChange("build_timeout") {
		options.BuildTimeout = gitlab.Ptr(d.Get("build_timeout").(int))
	}

	if d.HasChange("builds_access_level") {
		options.BuildsAccessLevel = stringToAccessControlValue(d.Get("builds_access_level").(string))
	}

	if d.HasChange("container_expiration_policy") {
		options.ContainerExpirationPolicyAttributes = expandContainerExpirationPolicyAttributes(d)
	}

	if d.HasChange("container_registry_access_level") {
		options.ContainerRegistryAccessLevel = stringToAccessControlValue(d.Get("container_registry_access_level").(string))
	}

	if d.HasChange("emails_enabled") {
		options.EmailsEnabled = gitlab.Ptr(d.Get("emails_enabled").(bool))
	}

	if d.HasChange("external_authorization_classification_label") {
		options.ExternalAuthorizationClassificationLabel = gitlab.Ptr(d.Get("external_authorization_classification_label").(string))
	}

	if d.HasChange("forking_access_level") {
		options.ForkingAccessLevel = stringToAccessControlValue(d.Get("forking_access_level").(string))
	}

	if d.HasChange("issues_access_level") {
		options.IssuesAccessLevel = stringToAccessControlValue(d.Get("issues_access_level").(string))
	}

	if d.HasChange("merge_requests_access_level") {
		options.MergeRequestsAccessLevel = stringToAccessControlValue(d.Get("merge_requests_access_level").(string))
	}

	// Ignore deprecated public_builds in favor of public_jobs.
	if d.HasChange("public_jobs") {
		options.PublicBuilds = gitlab.Ptr(d.Get("public_jobs").(bool)) //nolint:staticcheck
	} else if d.HasChange("public_builds") {
		options.PublicBuilds = gitlab.Ptr(d.Get("public_builds").(bool)) //nolint:staticcheck
	}

	if d.HasChange("repository_access_level") {
		options.RepositoryAccessLevel = stringToAccessControlValue(d.Get("repository_access_level").(string))
	}

	if d.HasChange("repository_storage") {
		options.RepositoryStorage = gitlab.Ptr(d.Get("repository_storage").(string))
	}

	if d.HasChange("requirements_access_level") {
		options.RequirementsAccessLevel = stringToAccessControlValue(d.Get("requirements_access_level").(string))
	}

	if d.HasChange("security_and_compliance_access_level") {
		options.SecurityAndComplianceAccessLevel = stringToAccessControlValue(d.Get("security_and_compliance_access_level").(string))
	}

	if d.HasChange("snippets_access_level") {
		options.SnippetsAccessLevel = stringToAccessControlValue(d.Get("snippets_access_level").(string))
	}

	if d.HasChange("suggestion_commit_message") {
		options.SuggestionCommitMessage = gitlab.Ptr(d.Get("suggestion_commit_message").(string))
	}

	if d.HasChange("topics") {
		options.Topics = stringSetToStringSlice(d.Get("topics").(*schema.Set))
	}

	if d.HasChange("wiki_access_level") {
		options.WikiAccessLevel = stringToAccessControlValue(d.Get("wiki_access_level").(string))
	}

	if d.HasChange("squash_commit_template") {
		options.SquashCommitTemplate = gitlab.Ptr(d.Get("squash_commit_template").(string))
	}

	if d.HasChange("merge_commit_template") {
		options.MergeCommitTemplate = gitlab.Ptr(d.Get("merge_commit_template").(string))
	}

	if d.HasChange("ci_default_git_depth") {
		options.CIDefaultGitDepth = gitlab.Ptr(d.Get("ci_default_git_depth").(int))
	}

	if d.HasChange("ci_delete_pipelines_in_seconds") {
		if v, ok := d.GetOk("ci_delete_pipelines_in_seconds"); !ok || v == nil {
			err := updateNilCIDeletePipelinesInSecondsSetting(client, d.Id())
			if err != nil {
				return diag.FromErr(err)
			}
			options.CIDeletePipelinesInSeconds = nil
		} else {
			options.CIDeletePipelinesInSeconds = gitlab.Ptr(d.Get("ci_delete_pipelines_in_seconds").(int))
		}
	}

	if d.HasChange("ci_separated_caches") {
		options.CISeperateCache = gitlab.Ptr(d.Get("ci_separated_caches").(bool))
	}

	if d.HasChange("keep_latest_artifact") {
		options.KeepLatestArtifact = gitlab.Ptr(d.Get("keep_latest_artifact").(bool))
	}

	if d.HasChange("mr_default_target_self") {
		options.MergeRequestDefaultTargetSelf = gitlab.Ptr(d.Get("mr_default_target_self").(bool))
	}

	if d.HasChange("releases_access_level") {
		options.ReleasesAccessLevel = stringToAccessControlValue(d.Get("releases_access_level").(string))
	}

	if d.HasChange("environments_access_level") {
		options.EnvironmentsAccessLevel = stringToAccessControlValue(d.Get("environments_access_level").(string))
	}

	if d.HasChange("feature_flags_access_level") {
		options.FeatureFlagsAccessLevel = stringToAccessControlValue(d.Get("feature_flags_access_level").(string))
	}

	if d.HasChange("infrastructure_access_level") {
		options.InfrastructureAccessLevel = stringToAccessControlValue(d.Get("infrastructure_access_level").(string))
	}

	if d.HasChange("monitor_access_level") {
		options.MonitorAccessLevel = stringToAccessControlValue(d.Get("monitor_access_level").(string))
	}

	if d.HasChange("model_experiments_access_level") {
		options.ModelExperimentsAccessLevel = stringToAccessControlValue(d.Get("model_experiments_access_level").(string))
	}

	if d.HasChange("model_registry_access_level") {
		options.ModelRegistryAccessLevel = stringToAccessControlValue(d.Get("model_registry_access_level").(string))
	}

	if d.HasChange("prevent_merge_without_jira_issue") {
		options.PreventMergeWithoutJiraIssue = gitlab.Ptr(d.Get("prevent_merge_without_jira_issue").(bool))
	}

	avatar, err := handleAvatarOnUpdate(d)
	if err != nil {
		return diag.FromErr(err)
	}
	if avatar != nil {
		options.Avatar = &gitlab.ProjectAvatar{
			Filename: avatar.Filename,
			Image:    avatar.Image,
		}
	}

	var project *gitlab.Project
	if *options != (gitlab.EditProjectOptions{}) {
		tflog.Debug(ctx, fmt.Sprintf("[DEBUG] update gitlab project %s", d.Id()))
		project, _, err = client.Projects.EditProject(d.Id(), options, gitlab.WithContext(ctx))
		if err != nil {
			return diag.FromErr(err)
		}
	}

	// If we don't have the project from the EditProject call, retrieve the project here so we have the full
	// path for further calls.
	if project == nil {
		project, _, err = client.Projects.GetProject(d.Id(), nil)
		if err != nil {
			return diag.FromErr(err)
		}
	}

	// If enabling Secret Push Detection, then update that value on the project via
	// GraphQL
	// lintignore: XR001 // TODO: replace with alternative for GetOkExists
	if changed := d.HasChange("pre_receive_secret_detection_enabled"); changed {
		val := d.Get("pre_receive_secret_detection_enabled").(bool)
		err := updateProjectSecretDetectionValue(ctx, client, project.PathWithNamespace, val)
		if err != nil {
			return diag.Errorf("Error updating Secret Push Detection on Project %s: %v", d.Id(), err)
		}
	}

	if d.HasChange("forked_from_project_id") {
		oldRaw, newRaw := d.GetChange("forked_from_project_id")
		oldValue, newValue := oldRaw.(int), newRaw.(int)
		var createRelation, removeRelation bool

		if newValue != 0 && oldValue != 0 {
			// change fork relation
			removeRelation = true
			createRelation = true
		} else if newValue != 0 {
			// create fork relation
			createRelation = true
		} else if newValue == 0 {
			removeRelation = true
		}

		if removeRelation {
			// Remove fork relation
			if _, err := client.Projects.DeleteProjectForkRelation(d.Id(), gitlab.WithContext(ctx)); err != nil {
				return diag.Errorf("unable to remove fork relation from project %q: %v", d.Id(), err)
			}
		}

		// Add fork relationship
		if createRelation {
			// Add fork relation
			if _, _, err := client.Projects.CreateProjectForkRelation(d.Id(), newValue, gitlab.WithContext(ctx)); err != nil {
				return diag.Errorf("unable to add fork relation to project %q (to project %d): %v", d.Id(), newValue, err)
			}
		}
	}

	if *transferOptions != (gitlab.TransferProjectOptions{}) {
		tflog.Debug(ctx, fmt.Sprintf("[DEBUG] transferring project %s to namespace %d", d.Id(), transferOptions.Namespace))
		_, _, err := client.Projects.TransferProject(d.Id(), transferOptions, gitlab.WithContext(ctx))
		if err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("archived") {
		if d.Get("archived").(bool) {
			if _, _, err := client.Projects.ArchiveProject(d.Id(), gitlab.WithContext(ctx)); err != nil {
				return diag.Errorf("project %q could not be archived: %s", d.Id(), err)
			}
		} else {
			if _, _, err := client.Projects.UnarchiveProject(d.Id(), gitlab.WithContext(ctx)); err != nil {
				return diag.Errorf("project %q could not be unarchived: %s", d.Id(), err)
			}
		}
	}

	if d.HasChange("push_rules") {
		err := editOrAddPushRules(ctx, client, d.Id(), d)
		if err != nil {
			if api.Is404(err) {
				tflog.Debug(ctx, fmt.Sprintf("[DEBUG] Failed to get push rules for project %q: %v", d.Id(), err))
				return diag.Errorf("Project push rules are not supported in your version of GitLab")
			}
			return diag.Errorf("Failed to edit push rules for project %q: %s", d.Id(), err)
		}
	}

	return resourceGitlabProjectRead(ctx, d, meta)
}