Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions gitlab/data_source_gitlab_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ func dataSourceGitlabProject() *schema.Resource {
Optional: true,
Computed: true,
},
"archived": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
},
}
}
Expand Down Expand Up @@ -118,5 +123,6 @@ func dataSourceGitlabProjectRead(d *schema.ResourceData, meta interface{}) error
d.Set("http_url_to_repo", found.HTTPURLToRepo)
d.Set("web_url", found.WebURL)
d.Set("runners_token", found.RunnersToken)
d.Set("archived", found.Archived)
return nil
}
144 changes: 142 additions & 2 deletions gitlab/resource_gitlab_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ var resourceGitLabProjectSchema = map[string]*schema.Schema{
},
},
},
"archived": {
Type: schema.TypeBool,
Description: "Whether the project is archived.",
Optional: true,
Default: false,
},
}

func resourceGitlabProject() *schema.Resource {
Expand Down Expand Up @@ -181,10 +187,12 @@ func resourceGitlabProjectSetToState(d *schema.ResourceData, project *gitlab.Pro
d.Set("shared_runners_enabled", project.SharedRunnersEnabled)
d.Set("shared_with_groups", flattenSharedWithGroupsOptions(project))
d.Set("tags", project.TagList)
d.Set("archived", project.Archived)
}

func resourceGitlabProjectCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*gitlab.Client)

options := &gitlab.CreateProjectOptions{
Name: gitlab.String(d.Get("name").(string)),
IssuesEnabled: gitlab.Bool(d.Get("issues_enabled").(bool)),
Expand All @@ -200,20 +208,43 @@ func resourceGitlabProjectCreate(d *schema.ResourceData, meta interface{}) error
SharedRunnersEnabled: gitlab.Bool(d.Get("shared_runners_enabled").(bool)),
}

// need to manage partial state since project creation may require
// more than a single API call, and they may all fail independently;
// the default set of attributes is prepopulated with those used above
d.Partial(true)
setProperties := []string{
"name",
"issues_enabled",
"merge_requests_enabled",
"approvals_before_merge",
"wiki_enabled",
"snippets_enabled",
"container_registry_enabled",
"visibility_level",
"merge_method",
"only_allow_merge_if_pipeline_succeeds",
"only_allow_merge_if_all_discussions_are_resolved",
"shared_runners_enabled",
}

if v, ok := d.GetOk("path"); ok {
options.Path = gitlab.String(v.(string))
setProperties = append(setProperties, "path")
}

if v, ok := d.GetOk("namespace_id"); ok {
options.NamespaceID = gitlab.Int(v.(int))
setProperties = append(setProperties, "namespace_id")
}

if v, ok := d.GetOk("description"); ok {
options.Description = gitlab.String(v.(string))
setProperties = append(setProperties, "description")
}

if v, ok := d.GetOk("tags"); ok {
options.TagList = stringSetToStringSlice(v.(*schema.Set))
setProperties = append(setProperties, "tags")
}

log.Printf("[DEBUG] create gitlab project %q", *options.Name)
Expand All @@ -223,16 +254,38 @@ func resourceGitlabProjectCreate(d *schema.ResourceData, meta interface{}) error
return err
}

for _, setProperty := range setProperties {
log.Printf("[DEBUG] partial gitlab project %s creation of property %q", d.Id(), setProperty)
d.SetPartial(setProperty)
}

// 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))

if v, ok := d.GetOk("shared_with_groups"); ok {
for _, option := range expandSharedWithGroupsOptions(v) {
if _, err := client.Projects.ShareProjectWithGroup(project.ID, option); err != nil {
return err
}
}
d.SetPartial("shared_with_groups")
}

d.SetId(fmt.Sprintf("%d", project.ID))
v := d.Get("archived")
if v.(bool) {
// strange as it may seem, this project is created in archived state...
err := archiveProject(d, meta)
if err != nil {
log.Printf("[WARN] New project (%s) could not be created in archived state: error %#v", d.Id(), err)
return err
}
d.SetPartial(("archived"))
}

// everything went OK, we can revert to ordinary state management
// and let the Gitlab server fill in the resource state via a read
d.Partial(false)
return resourceGitlabProjectRead(d, meta)
}

Expand All @@ -254,68 +307,89 @@ func resourceGitlabProjectUpdate(d *schema.ResourceData, meta interface{}) error

options := &gitlab.EditProjectOptions{}

// need to manage partial state since project archiving requires
// a separate API call which could fail
d.Partial(true)
updatedProperties := []string{}

if d.HasChange("name") {
options.Name = gitlab.String(d.Get("name").(string))
updatedProperties = append(updatedProperties, "name")
}

if d.HasChange("path") && (d.Get("path").(string) != "") {
options.Path = gitlab.String(d.Get("path").(string))
updatedProperties = append(updatedProperties, "path")
}

if d.HasChange("description") {
options.Description = gitlab.String(d.Get("description").(string))
updatedProperties = append(updatedProperties, "description")
}

if d.HasChange("default_branch") {
options.DefaultBranch = gitlab.String(d.Get("default_branch").(string))
updatedProperties = append(updatedProperties, "default_branch")
}

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

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

if d.HasChange("only_allow_merge_if_pipeline_succeeds") {
options.OnlyAllowMergeIfPipelineSucceeds = gitlab.Bool(d.Get("only_allow_merge_if_pipeline_succeeds").(bool))
updatedProperties = append(updatedProperties, "only_allow_merge_if_pipeline_succeeds")
}

if d.HasChange("only_allow_merge_if_all_discussions_are_resolved") {
options.OnlyAllowMergeIfAllDiscussionsAreResolved = gitlab.Bool(d.Get("only_allow_merge_if_all_discussions_are_resolved").(bool))
updatedProperties = append(updatedProperties, "only_allow_merge_if_all_discussions_are_resolved")
}

if d.HasChange("issues_enabled") {
options.IssuesEnabled = gitlab.Bool(d.Get("issues_enabled").(bool))
updatedProperties = append(updatedProperties, "issues_enabled")
}

if d.HasChange("merge_requests_enabled") {
options.MergeRequestsEnabled = gitlab.Bool(d.Get("merge_requests_enabled").(bool))
updatedProperties = append(updatedProperties, "merge_requests_enabled")
}

if d.HasChange("approvals_before_merge") {
options.ApprovalsBeforeMerge = gitlab.Int(d.Get("approvals_before_merge").(int))
updatedProperties = append(updatedProperties, "approvals_before_merge")
}

if d.HasChange("wiki_enabled") {
options.WikiEnabled = gitlab.Bool(d.Get("wiki_enabled").(bool))
updatedProperties = append(updatedProperties, "wiki_enabled")
}

if d.HasChange("snippets_enabled") {
options.SnippetsEnabled = gitlab.Bool(d.Get("snippets_enabled").(bool))
updatedProperties = append(updatedProperties, "snippets_enabled")
}

if d.HasChange("shared_runners_enabled") {
options.SharedRunnersEnabled = gitlab.Bool(d.Get("shared_runners_enabled").(bool))
updatedProperties = append(updatedProperties, "shared_runners_enabled")
}

if d.HasChange("tags") {
options.TagList = stringSetToStringSlice(d.Get("tags").(*schema.Set))
updatedProperties = append(updatedProperties, "tags")
}

if d.HasChange("container_registry_enabled") {
options.ContainerRegistryEnabled = gitlab.Bool(d.Get("container_registry_enabled").(bool))
updatedProperties = append(updatedProperties, "container_registry_enabled")
}

if *options != (gitlab.EditProjectOptions{}) {
Expand All @@ -324,12 +398,40 @@ func resourceGitlabProjectUpdate(d *schema.ResourceData, meta interface{}) error
if err != nil {
return err
}
for _, updatedProperty := range updatedProperties {
log.Printf("[DEBUG] partial gitlab project %s update of property %q", d.Id(), updatedProperty)
d.SetPartial(updatedProperty)
}
}

if d.HasChange("shared_with_groups") {
updateSharedWithGroups(d, meta)
err := updateSharedWithGroups(d, meta)
// TODO: check if handling partial state update in this simplistic
// way is ok when an error in the "shared groups" API calls occurs
if err != nil {
d.SetPartial("shared_with_groups")
}
}

if d.HasChange("archived") {
v := d.Get("archived")
if v.(bool) {
err := archiveProject(d, meta)
if err != nil {
log.Printf("[WARN] Project (%s) could not be archived: error %#v", d.Id(), err)
return err
}
} else {
err := unarchiveProject(d, meta)
if err != nil {
log.Printf("[WARN] Project (%s) could not be unarchived: error %#v", d.Id(), err)
return err
}
}
d.SetPartial("archived")
}

d.Partial(false)
return resourceGitlabProjectRead(d, meta)
}

Expand Down Expand Up @@ -488,3 +590,41 @@ func updateSharedWithGroups(d *schema.ResourceData, meta interface{}) error {

return nil
}

// archiveProject calls the Gitlab server to archive a project; if the
// project is already archived, the call will do nothing (the API is
// idempotent).
func archiveProject(d *schema.ResourceData, meta interface{}) error {
log.Printf("[TRACE] Project (%s) will be archived", d.Id())
client := meta.(*gitlab.Client)
out, _, err := client.Projects.ArchiveProject(d.Id())
if err != nil {
log.Printf("[ERROR] Error archiving project (%s), received %#v", d.Id(), err)
return err
}
if !out.Archived {
log.Printf("[ERROR] Project (%s) is still not archived", d.Id())
return fmt.Errorf("error archiving project (%s): its status on the server is still unarchived", d.Id())
}
log.Printf("[TRACE] Project (%s) archived", d.Id())
return nil
}

// unarchiveProject calls the Gitlab server to unarchive a project; if the
// project is already not archived, the call will do nothing (the API is
// idempotent).
func unarchiveProject(d *schema.ResourceData, meta interface{}) error {
log.Printf("[INFO] Project (%s) will be unarchived", d.Id())
client := meta.(*gitlab.Client)
out, _, err := client.Projects.UnarchiveProject(d.Id())
if err != nil {
log.Printf("[ERROR] Error unarchiving project (%s), received %#v", d.Id(), err)
return err
}
if out.Archived {
log.Printf("[ERROR] Project (%s) is still archived", d.Id())
return fmt.Errorf("error unarchiving project (%s): its status on the server is still archived", d.Id())
}
log.Printf("[TRACE] Project (%s) unarchived", d.Id())
return nil
}
11 changes: 7 additions & 4 deletions gitlab/resource_gitlab_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,23 @@ func TestAccGitlabProject_basic(t *testing.T) {
MergeMethod: gitlab.FastForwardMerge,
OnlyAllowMergeIfPipelineSucceeds: true,
OnlyAllowMergeIfAllDiscussionsAreResolved: true,
Archived: false, // needless, but let's make this explicit
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckGitlabProjectDestroy,
Steps: []resource.TestStep{
// Step0 Create a project with all the features on
// Step0 Create a project with all the features on (note: "archived" is "false")
{
Config: testAccGitlabProjectConfig(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckGitlabProjectExists("gitlab_project.foo", &received),
testAccCheckAggregateGitlabProject(&defaults, &received),
),
},
// Step1 Update the project to turn the features off
// Step1 Update the project to turn the features off (note: "archived" is "true")
{
Config: testAccGitlabProjectUpdateConfig(rInt),
Check: resource.ComposeTestCheckFunc(
Expand All @@ -65,10 +66,11 @@ func TestAccGitlabProject_basic(t *testing.T) {
MergeMethod: gitlab.FastForwardMerge,
OnlyAllowMergeIfPipelineSucceeds: true,
OnlyAllowMergeIfAllDiscussionsAreResolved: true,
Archived: true,
}, &received),
),
},
// Step2 Update the project to turn the features on again
// Step2 Update the project to turn the features on again (note: "archived" is "false")
{
Config: testAccGitlabProjectConfig(rInt),
Check: resource.ComposeTestCheckFunc(
Expand Down Expand Up @@ -411,7 +413,8 @@ resource "gitlab_project" "foo" {
wiki_enabled = false
snippets_enabled = false
container_registry_enabled = false
shared_runners_enabled = false
shared_runners_enabled = false
archived = true
}
`, rInt, rInt)
}
Expand Down
2 changes: 2 additions & 0 deletions website/docs/d/project.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@ The following attributes are exported:
* `web_url` - URL that can be used to find the project in a browser.

* `runners_token` - Registration token to use during runner setup.

* `archived` - Whether the project is in read-only mode (archived).
4 changes: 3 additions & 1 deletion website/docs/r/project.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ The following arguments are supported:

* `shared_with_groups` - (Optional) Enable sharing the project with a list of groups (maps).
* `group_id` - (Required) Group id of the group you want to share the project with.
* `group_access_level` - (Optional) Group's sharing permissions. See [group members permission][group_members_permissions] for more info.
* `group_access_level` - (Required) Group's sharing permissions. See [group members permission][group_members_permissions] for more info.
Valid values are `guest`, `reporter`, `developer`, `master`.

* `archived` - (Optional) Whether the project is in read-only mode (archived). Repositories can be archived/unarchived by toggling this parameter.

## Attributes Reference

The following additional attributes are exported:
Expand Down