Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finish up project_user and project_group resources #101

Merged
merged 3 commits into from
Mar 4, 2024
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
11 changes: 8 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
## 1.4.0 (not yet released)
## 1.4.0 (March 4, 2024)

FEATURES:

* **New Resource:** `project_user` - Separate resource to manage project memberships for users.
* **New Resource:** `project_group` - Separate resource to project project memberships for groups.
* resource/project: Add `cd ..` attribute to toggle if `project` resource should use its `member` or not to manage project users. Should be set to `true` when using in conjunction with `project_user` resource.
* resource/project: Add `use_project_group_resource` attribute to toggle if `project` resource should use its `group` or not to manage project users. Should be set to `true` when using in conjunction with `project_group` resource.
* resource/project: Add `use_project_user_resource` attribute to toggle if `project` resource should use its `member` or not to manage project users. Should be set to `false` to continue using existing `member` attribute.
* resource/project: Add `use_project_group_resource` attribute to toggle if `project` resource should use its `group` or not to manage project users. Should be set to `false` to continue using existing `group` attribute.
* resource/project: Switch default value for `use_project_role_resource` attribute to `true` so new provider practioners won't need to explicity set this attribute to use `project_role` resource.

Issue: [#83](https://github.com/jfrog/terraform-provider-project/issues/83), [#98](https://github.com/jfrog/terraform-provider-project/issues/98)

PR: [#101](https://github.com/jfrog/terraform-provider-project/pull/101)

## 1.3.5 (Feburary 9, 2024)

Expand Down
8 changes: 5 additions & 3 deletions docs/resources/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ resource "project" "myproject" {
~>This setting only applies to self-hosted environment. See [Manage Storage Quotas](https://jfrog.com/help/r/jfrog-platform-administration-documentation/manage-storage-quotas).
- `description` (String)
- `email_notification` (Boolean) Alerts will be sent when reaching 75% and 95% of the storage quota. This serves as a notification only and is not a blocker
- `group` (Block Set) Project group. Element has one to one mapping with the [JFrog Project Groups API](https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-UpdateGroupinProject) (see [below for nested schema](#nestedblock--group))
- `group` (Block Set, Deprecated) Project group. Element has one to one mapping with the [JFrog Project Groups API](https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-UpdateGroupinProject) (see [below for nested schema](#nestedblock--group))
- `max_storage_in_gibibytes` (Number) Storage quota in GiB. Must be 1 or larger. Set to -1 for unlimited storage. This is translated to binary bytes for Artifactory API. So for a 1TB quota, this should be set to 1024 (vs 1000) which will translate to 1099511627776 bytes for the API.
- `member` (Block Set) Member of the project. Element has one to one mapping with the [JFrog Project Users API](https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-UpdateUserinProject). (see [below for nested schema](#nestedblock--member))
- `member` (Block Set, Deprecated) Member of the project. Element has one to one mapping with the [JFrog Project Users API](https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-UpdateUserinProject). (see [below for nested schema](#nestedblock--member))
- `repos` (Set of String) (Optional) List of existing repo keys to be assigned to the project. **Note** We *strongly* recommend using this attribute to manage the list of repositories. If you wish to use the alternate method of setting `project_key` attribute in each `artifactory_*_repository` resource in the `artifactory` provider, you will need to use `lifecycle.ignore_changes` in the `project` resource to avoid state drift.

```hcl
Expand All @@ -114,7 +114,9 @@ lifecycle {
}
```
- `role` (Block Set, Deprecated) Project role. Element has one to one mapping with the [JFrog Project Roles API](https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-AddaNewRole) (see [below for nested schema](#nestedblock--role))
- `use_project_role_resource` (Boolean) When set to true, this resource will ignore the `roles` attributes and allow roles to be managed by `project_role` resource instead. Default to false.
- `use_project_group_resource` (Boolean) When set to true, this resource will ignore the `group` attributes and allow users to be managed by `project_group` resource instead. Default to `true`.
- `use_project_role_resource` (Boolean) When set to true, this resource will ignore the `roles` attributes and allow roles to be managed by `project_role` resource instead. Default to `true`.
- `use_project_user_resource` (Boolean) When set to true, this resource will ignore the `member` attributes and allow users to be managed by `project_user` resource instead. Default to `true`.

### Read-Only

Expand Down
2 changes: 1 addition & 1 deletion docs/resources/user.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ resource "project_user" "myuser" {

### Optional

- `ignore_missing_user` (Boolean) When set to true, the resource will not fail if the user does not exist. Default to false. This is useful when the user is externally managed and the local account wasn't created yet.
- `ignore_missing_user` (Boolean) When set to `true`, the resource will not fail if the user does not exist. Default to `false`. This is useful when the user is externally managed and the local account wasn't created yet.

### Read-Only

Expand Down
12 changes: 12 additions & 0 deletions pkg/project/membership_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func TestAccProject_membership(t *testing.T) {
index_resources = true
}

use_project_user_resource = false

member {
name = "{{ .username1 }}"
roles = ["{{ .developeRole }}"]
Expand All @@ -60,6 +62,8 @@ func TestAccProject_membership(t *testing.T) {
index_resources = true
}

use_project_user_resource = false

member {
name = "{{ .username1 }}"
roles = ["{{ .developeRole }}", "{{ .contributorRole }}"]
Expand All @@ -82,6 +86,8 @@ func TestAccProject_membership(t *testing.T) {
manage_resources = true
index_resources = true
}

use_project_user_resource = false
}
`, params)

Expand Down Expand Up @@ -177,6 +183,8 @@ func TestAccProject_group(t *testing.T) {
index_resources = true
}

use_project_group_resource = false

group {
name = "{{ .group1 }}"
roles = ["{{ .developeRole }}"]
Expand All @@ -195,6 +203,8 @@ func TestAccProject_group(t *testing.T) {
index_resources = true
}

use_project_group_resource = false

group {
name = "{{ .group1 }}"
roles = ["{{ .developeRole }}", "{{ .contributorRole }}"]
Expand All @@ -217,6 +227,8 @@ func TestAccProject_group(t *testing.T) {
manage_resources = true
index_resources = true
}

use_project_group_resource = false
}
`, params)

Expand Down
42 changes: 0 additions & 42 deletions pkg/project/project_group.go

This file was deleted.

45 changes: 0 additions & 45 deletions pkg/project/project_user.go

This file was deleted.

8 changes: 4 additions & 4 deletions pkg/project/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ var updateRepos = func(ctx context.Context, projectKey string, terraformRepoKeys
return nil, fmt.Errorf("failed to add repos for project: %s", addErr)
}

deleteErr := deleteRepos(ctx, projectKey, repoKeysToBeDeleted, m)
deleteErr := deleteRepos(ctx, repoKeysToBeDeleted, m)
if deleteErr != nil {
return nil, fmt.Errorf("failed to delete repos for project: %s", deleteErr)
}
Expand Down Expand Up @@ -139,7 +139,7 @@ var addRepo = func(ctx context.Context, projectKey string, repoKey RepoKey, req
return err
}

var deleteRepos = func(ctx context.Context, projectKey string, repoKeys []RepoKey, m interface{}) error {
var deleteRepos = func(ctx context.Context, repoKeys []RepoKey, m interface{}) error {
tflog.Debug(ctx, fmt.Sprintf("deleteRepos: %s", repoKeys))

req := m.(util.ProvderMetadata).Client.R().
Expand All @@ -148,7 +148,7 @@ var deleteRepos = func(ctx context.Context, projectKey string, repoKeys []RepoKe
AddRetryCondition(retryOnSpecificMsgBody("Web server is returning an unknown error"))

for _, repoKey := range repoKeys {
err := deleteRepo(ctx, projectKey, repoKey, req)
err := deleteRepo(ctx, repoKey, req)
if err != nil {
return fmt.Errorf("failed to delete repo %s: %s", repoKey, err)
}
Expand All @@ -157,7 +157,7 @@ var deleteRepos = func(ctx context.Context, projectKey string, repoKeys []RepoKe
return nil
}

var deleteRepo = func(ctx context.Context, projectKey string, repoKey RepoKey, req *resty.Request) error {
var deleteRepo = func(ctx context.Context, repoKey RepoKey, req *resty.Request) error {
tflog.Debug(ctx, fmt.Sprintf("deleteRepo: %s", repoKey))

type Error struct {
Expand Down
38 changes: 20 additions & 18 deletions pkg/project/resource_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,8 @@ func projectResource() *schema.Resource {
"use_project_role_resource": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "When set to true, this resource will ignore the `roles` attributes and allow roles to be managed by `project_role` resource instead. Default to false.",
Default: true,
Description: "When set to true, this resource will ignore the `roles` attributes and allow roles to be managed by `project_role` resource instead. Default to `true`.",
},
},
)
Expand Down Expand Up @@ -297,6 +297,12 @@ func projectResource() *schema.Resource {
Description: "Member of the project. Element has one to one mapping with the [JFrog Project Users API](https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-UpdateUserinProject).",
Deprecated: "Replaced by `project_user` resource. This should not be used in combination with `project_user` resource. Use `use_project_user_resource` attribute to control which resource manages project roles.",
},
"use_project_user_resource": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "When set to true, this resource will ignore the `member` attributes and allow users to be managed by `project_user` resource instead. Default to `true`.",
},
"group": {
Type: schema.TypeSet,
Optional: true,
Expand All @@ -319,17 +325,11 @@ func projectResource() *schema.Resource {
Description: "Project group. Element has one to one mapping with the [JFrog Project Groups API](https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-UpdateGroupinProject)",
Deprecated: "Replaced by `project_group` resource. This should not be used in combination with `project_group` resource. Use `use_project_group_resource` attribute to control which resource manages project roles.",
},
"use_project_user_resource": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "When set to true, this resource will ignore the `member` attributes and allow users to be managed by `project_user` resource instead. Default to false.",
},
"use_project_group_resource": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "When set to true, this resource will ignore the `group` attributes and allow users to be managed by `project_group` resource instead. Default to false.",
Default: true,
Description: "When set to true, this resource will ignore the `group` attributes and allow users to be managed by `project_group` resource instead. Default to `true`.",
},
},
)
Expand Down Expand Up @@ -466,7 +466,9 @@ func projectResource() *schema.Resource {
return diag.FromErr(err)
}

_, err = m.(util.ProvderMetadata).Client.R().SetBody(project).Post(projectsUrl)
_, err = m.(util.ProvderMetadata).Client.R().
SetBody(project).
Post(projectsUrl)
if err != nil {
return diag.FromErr(err)
}
Expand Down Expand Up @@ -534,7 +536,7 @@ func projectResource() *schema.Resource {
}
}

useProjectUserResource := data.Get("use_project_role_resource").(bool)
useProjectUserResource := data.Get("use_project_user_resource").(bool)
if !useProjectUserResource {
_, err = updateMembers(ctx, data.Id(), usersMembershipType, users, m)
if err != nil {
Expand Down Expand Up @@ -567,7 +569,7 @@ func projectResource() *schema.Resource {
return diag.FromErr(err)
}

deleteErr := deleteRepos(ctx, data.Id(), repos, m)
deleteErr := deleteRepos(ctx, repos, m)
if deleteErr != nil {
return diag.FromErr(fmt.Errorf("failed to delete repos for project: %s", deleteErr))
}
Expand Down Expand Up @@ -607,16 +609,16 @@ func projectResource() *schema.Resource {
}

var resourceStateUpgradeV1 = func(ctx context.Context, rawState map[string]any, meta any) (map[string]any, error) {
// set use_project_role_resource to true for existing state so the resource will continue
// using `roles` attribute until explicitly set to false
rawState["use_project_role_resource"] = true
// set use_project_role_resource to false for existing state so the resource will continue
// using `roles` attribute until explicitly set to true
rawState["use_project_role_resource"] = false
return rawState, nil
}

var resourceStateUpgradeV2 = func(ctx context.Context, rawState map[string]any, meta any) (map[string]any, error) {
// like in v1 where the project_role was introduced, just for project_user and project_group
rawState["use_project_user_resource"] = true
rawState["use_project_group_resource"] = true
rawState["use_project_user_resource"] = false
rawState["use_project_group_resource"] = false
return rawState, nil
}

Expand Down
33 changes: 33 additions & 0 deletions pkg/project/resource_project_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ import (

const projectGroupsUrl = "access/api/v1/projects/{projectKey}/groups/{name}"

type ProjectGroup struct {
ProjectKey string `json:"-"`
Name string `json:"name"`
Roles []string `json:"roles"`
}

func (m ProjectGroup) Id() string {
return fmt.Sprintf(`%s:%s`, m.ProjectKey, m.Name)
}

func projectGroupResource() *schema.Resource {
var projectGroupSchema = map[string]*schema.Schema{
"project_key": {
Expand All @@ -26,6 +36,7 @@ func projectGroupResource() *schema.Resource {
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty),
Description: "The name of an artifactory group.",
},
Expand All @@ -37,6 +48,28 @@ func projectGroupResource() *schema.Resource {
},
}

var unpackProjectGroup = func(d *schema.ResourceData) ProjectGroup {
return ProjectGroup{
ProjectKey: d.Get("project_key").(string),
Name: d.Get("name").(string),
Roles: util.CastToStringArr(d.Get("roles").(*schema.Set).List()),
}
}

var packProjectGroup = func(_ context.Context, data *schema.ResourceData, m ProjectGroup) diag.Diagnostics {
setValue := util.MkLens(data)

setValue("name", m.Name)
setValue("project_key", m.ProjectKey)
errors := setValue("roles", m.Roles)

if len(errors) > 0 {
return diag.Errorf("failed to pack project member %q", errors)
}

return nil
}

var readProjectGroup = func(ctx context.Context, data *schema.ResourceData, m interface{}) diag.Diagnostics {
projectGroup := unpackProjectGroup(data)
var loadedProjectGroup ProjectGroup
Expand Down
Loading
Loading