func resourceGitlabProjectDelete()

in internal/provider/sdk/resource_gitlab_project.go [1728:1850]


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

	if !d.Get("archive_on_destroy").(bool) {
		tflog.Debug(ctx, fmt.Sprintf("[DEBUG] Delete gitlab project %s", d.Id()))

		// The behavior for permanently delete is different in pre and post 18.0, so
		// a version check is required. See comments in each `if` block for more details.
		isVersionAtLeast18, err := api.IsGitLabVersionAtLeast(ctx, client, "18.0")()
		if err != nil {
			return diag.FromErr(errors.Join(errors.New("unable to fetch version of gitlab to determine proper deletion priority"), err))
		}
		if isVersionAtLeast18 {

			tflog.Debug(ctx, "[DELETE] Working with gitlab 18.0+ - flagging project for soft delete.", map[string]any{
				"project_id": d.Id(),
			})

			// in 18.0+, an initial delete call needs to be made before permanent delete can
			// be made.
			_, err := client.Projects.DeleteProject(d.Id(), nil, gitlab.WithContext(ctx))
			if err != nil {
				return diag.FromErr(errors.Join(errors.New("error encountered while calling the GitLab API to delete project"), err))
			}

			// Once the project is marked for deletion, check if permanent delete is required, then
			// re-retrieve the path (marking it for deletion changes the path), then permanently delete.
			if d.Get("permanently_delete_on_destroy").(bool) {

				tflog.Debug(ctx, "[DELETE] Working with gitlab 18.0+ - project needs to be hard deleted because `permanently_delete_on_destroy` is set", map[string]any{
					"project_id": d.Id(),
				})

				softDeletedProject, _, err := client.Projects.GetProject(d.Id(), nil, gitlab.WithContext(ctx))
				if err != nil {
					return diag.FromErr(errors.Join(errors.New("error encountered when reading the soft deleted project to get the new namespace; this is needed to permanently delete a project"), err))
				}
				options := gitlab.DeleteProjectOptions{
					PermanentlyRemove: gitlab.Ptr(true),
					FullPath:          &softDeletedProject.PathWithNamespace,
				}

				tflog.Debug(ctx, "[DELETE] permanently_delete_on_destroy is set, calling a second time to permanently delete", map[string]any{})
				_, err = client.Projects.DeleteProject(d.Id(), &options, gitlab.WithContext(ctx))
				if err != nil {
					// Ensure the error clearly states the project is still flagged in case the user wants to stop the deletion.
					return diag.FromErr(errors.Join(errors.New("error encountered when permanently deleting the project; Project is still flagged for deletion"), err))
				}
				tflog.Debug(ctx, "[DELETE] project permanently destroyed successfully", map[string]any{})
			}

		} else {

			permanentlyDelete := d.Get("permanently_delete_on_destroy").(bool)

			var options gitlab.DeleteProjectOptions
			tflog.Debug(ctx, "[DELETE] Working with gitlab 17.11 or before; deleting project.", map[string]any{
				"project_id":         d.Id(),
				"permanently_delete": permanentlyDelete,
			})

			// If the permanent delete option is set pre-18.0, the options need to be passed with
			// the initial delete call, so set them here.
			if permanentlyDelete {
				options = gitlab.DeleteProjectOptions{
					PermanentlyRemove: gitlab.Ptr(true),
				}
				if v, ok := d.GetOk("path_with_namespace"); ok {
					options.FullPath = gitlab.Ptr(v.(string))
				}
			}

			_, err := client.Projects.DeleteProject(d.Id(), &options, gitlab.WithContext(ctx))
			if err != nil {
				return diag.FromErr(errors.Join(errors.New("error encountered while calling the GitLab API to delete project"), err))
			}
			tflog.Debug(ctx, "[DELETE] project deleted successfully. If `permanently_delete` is true, project is permanently destroyed.", map[string]any{
				"project_id":         d.Id(),
				"permanently_delete": permanentlyDelete,
			})
		}

		// Wait for the project to be deleted.
		// Deleting a project in gitlab is async.
		stateConf := &retry.StateChangeConf{
			Pending: []string{"Deleting"},
			Target:  []string{"Deleted"},
			Refresh: func() (any, string, error) {
				out, _, err := client.Projects.GetProject(d.Id(), nil, gitlab.WithContext(ctx))
				if err != nil {
					if api.Is404(err) {
						return out, "Deleted", nil
					}
					tflog.Debug(ctx, fmt.Sprintf("[ERROR] Received error: %#v", err))
					return out, "Error", err
				}
				if out.MarkedForDeletionOn != nil {
					// Represents a Gitlab EE soft-delete
					return out, "Deleted", nil
				}
				return out, "Deleting", nil
			},

			Timeout:    d.Timeout(schema.TimeoutDelete),
			MinTimeout: 3 * time.Second,
			Delay:      5 * time.Second,
		}

		_, err = stateConf.WaitForStateContext(ctx)
		if err != nil {
			return diag.Errorf("error waiting for project (%s) to become deleted: %s", d.Id(), err)
		}

	} else {
		tflog.Debug(ctx, fmt.Sprintf("[DEBUG] Archive gitlab project %s", d.Id()))
		_, _, err := client.Projects.ArchiveProject(d.Id(), gitlab.WithContext(ctx))
		if err != nil {
			return diag.FromErr(err)
		}
	}

	return nil
}