in internal/provider/sdk/resource_gitlab_project.go [1020:1192]
func resourceGitlabProjectCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
client := meta.(*gitlab.Client)
// Project that has either been created or forked
var project *gitlab.Project
// The base project is created in one of two ways: We either create it from scratch,
// or it's forked from an existing project and edited. This block checks the forked
// status and handles the base create or forked logic
if forkedFromProjectID, ok := d.GetOk("forked_from_project_id"); ok {
tflog.Debug(ctx, "Creating forked project", map[string]any{
"forked_from_project_id": forkedFromProjectID,
"data": d,
})
createdProject, diag := createForkedProject(ctx, forkedFromProjectID.(int), d, client)
if diag != nil {
return diag
}
project = createdProject
} else {
tflog.Debug(ctx, "Creating project", map[string]any{
"data": d,
})
createdProject, diag := createProject(ctx, d, client)
if diag != nil {
return diag
}
project = createdProject
}
// from this point onwards no matter how we return, resource creation
// is committed to state since we set its ID
d.SetId(fmt.Sprintf("%d", project.ID))
// An import can be triggered by import_url or by creating the project from a template or from a fork.
if project.ImportStatus != "none" {
tflog.Debug(ctx, fmt.Sprintf("waiting for project %q import to finish", project.Name))
stateConf := &retry.StateChangeConf{
Pending: []string{"scheduled", "started"},
Target: []string{"finished"},
Timeout: d.Timeout(schema.TimeoutCreate),
Refresh: func() (any, string, error) {
status, _, err := client.ProjectImportExport.ImportStatus(d.Id(), gitlab.WithContext(ctx))
if err != nil {
return nil, "", err
}
return status, status.ImportStatus, nil
},
}
if _, err := stateConf.WaitForStateContext(ctx); err != nil {
return diag.Errorf("error while waiting for project %q import to finish: %s", project.Name, err)
}
// Read the project again, so that we can detect the default branch.
var err error
project, _, err = client.Projects.GetProject(project.ID, nil, gitlab.WithContext(ctx))
if err != nil {
return diag.Errorf("Failed to get project %q after completing import: %s", d.Id(), err)
}
}
if d.Get("archived").(bool) {
// strange as it may seem, this project is created in archived state...
if _, _, err := client.Projects.ArchiveProject(d.Id(), gitlab.WithContext(ctx)); err != nil {
return diag.Errorf("new project %q could not be archived: %s", d.Id(), err)
}
}
if _, ok := d.GetOk("push_rules"); ok {
err := editOrAddPushRules(ctx, client, d.Id(), d)
if err != nil {
if api.Is404(err) {
tflog.Debug(ctx, fmt.Sprintf("[DEBUG] Failed to edit 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)
}
}
// If enabling Secret Push Detection, then update that value on the project via
// GraphQL
// nolint:staticcheck // SA1019 ignore deprecated GetOkExists
// lintignore: XR001 // TODO: replace with alternative for GetOkExists
if _, ok := d.GetOkExists("pre_receive_secret_detection_enabled"); ok {
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 %d: %v", project.ID, err)
}
}
// nolint:staticcheck // SA1019 ignore deprecated GetOkExists
// lintignore: XR001 // TODO: replace with alternative for GetOkExists
if v, ok := d.GetOkExists("skip_wait_for_default_branch_protection"); ok {
d.Set("skip_wait_for_default_branch_protection", v.(bool))
}
if !d.Get("skip_wait_for_default_branch_protection").(bool) {
// If the project is assigned to a group namespace and the group has *default branch protection*
// disabled (`default_branch_protection = 0`) then we don't have to wait for one.
waitForDefaultBranchProtection, err := expectDefaultBranchProtection(ctx, client, project)
if err != nil {
return diag.Errorf("Failed to discover if branch protection is enabled by default or not for project %d: %+v", project.ID, err)
}
if waitForDefaultBranchProtection {
var branch *gitlab.Branch
// Branch protection for a newly created branch is an async action, so use WaitForState to ensure it's protected
// before we continue. Note this check should only be required when there is a custom default branch set
// See issue 800: https://gitlab.com/gitlab-org/terraform-provider-gitlab/-/issues/800
stateConf := &retry.StateChangeConf{
Pending: []string{"false"},
Target: []string{"true"},
// The async action usually completes very quickly, within seconds. However in
// lower compute or disk constrained environment, it can take a while.
// When importing a project and changing the branch protection, the "TimeoutCreate" may
// happen twice, and that's OK.
Timeout: d.Timeout(schema.TimeoutCreate),
Refresh: func() (any, string, error) {
branch, _, err = client.Branches.GetBranch(project.ID, project.DefaultBranch, gitlab.WithContext(ctx))
if err != nil {
if api.Is404(err) {
// When we hit a 404 here, it means the default branch wasn't created at all as part of the project
// this will happen when "default_branch" isn't set, or "initialize_with_readme" is set to false.
// We don't need to wait anymore, so return "true" to exist the wait loop.
return branch, "true", nil
}
// This is legit error, return the error.
tflog.Debug(ctx, "Error received when attempting to read branch protection of the default branch", map[string]any{
"error": err,
"project": project,
"branch": project.DefaultBranch,
})
return nil, "", err
}
tflog.Debug(ctx, "Project polling for default branch status", map[string]any{
"project": project,
"branch": project.DefaultBranch,
"protectionStatus": branch.Protected,
})
return branch, strconv.FormatBool(branch.Protected), nil
},
}
if _, err := stateConf.WaitForStateContext(ctx); err != nil {
return diag.Errorf("error while waiting for branch %s to reach 'protected' status; current status is protected %t, %s", branch.Name, branch.Protected, err)
}
}
}
// Create our "EditProjectOptions" call using state and the existing project
var editProjectOptions gitlab.EditProjectOptions
updatePostCreateEditOptions(ctx, &editProjectOptions, d, client, project)
if (editProjectOptions != gitlab.EditProjectOptions{}) {
if _, _, err := client.Projects.EditProject(d.Id(), &editProjectOptions, gitlab.WithContext(ctx)); err != nil {
return diag.Errorf("Could not update project %q: %s", d.Id(), err)
}
}
return resourceGitlabProjectRead(ctx, d, meta)
}