diff --git a/awx/provider.go b/awx/provider.go index ce348a8..656536f 100644 --- a/awx/provider.go +++ b/awx/provider.go @@ -55,6 +55,7 @@ func Provider() terraform.ResourceProvider { "awx_project": resourceProjectObject(), "awx_job_template": resourceJobTemplateObject(), "awx_user": resourceUserObject(), + "awx_user_role": resourceUserRoleObject(), }, ConfigureFunc: providerConfigure, diff --git a/awx/resource_role_user.go b/awx/resource_role_user.go new file mode 100644 index 0000000..cac8a56 --- /dev/null +++ b/awx/resource_role_user.go @@ -0,0 +1,222 @@ +package awx + +import ( + "fmt" + "strconv" + "time" + + "github.com/hashicorp/terraform/helper/schema" + awxgo "github.com/mauromedda/awx-go" +) + +func resourceUserRoleObject() *schema.Resource { + return &schema.Resource{ + Create: resourceUserRoleGrant, + Read: resourceUserRoleRead, + Delete: resourceUserRoleRevoke, + + Schema: map[string]*schema.Schema{ + "user_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "organization_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "role": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + validResourceTypes := map[string]bool{"admin": true, "read": true, "use": true, + "member": true, "execute": true, "adhoc": true, "update": true, "auditor": true} + value := v.(string) + if !validResourceTypes[value] { + errors = append(errors, fmt.Errorf("%q must match one of the valid vaules", k)) + } + return + }, + }, + "resource_type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + validResourceTypes := map[string]bool{"inventory": true, "team": true, "organization": true, + "job_template": true, "credential": true, "project": true} + value := v.(string) + if !validResourceTypes[value] { + errors = append(errors, fmt.Errorf("%q must match one of inventory, team, organization, job_template, credential or project", k)) + } + return + }, + }, + "resource_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(1 * time.Minute), + Delete: schema.DefaultTimeout(1 * time.Minute), + }, + } +} + +func getRoleID(d *schema.ResourceData, m interface{}) (int, error) { + awx := m.(*awxgo.AWX) + switch d.Get("resource_type").(string) { + case "inventory": + awxService := awx.InventoriesService + obj, _, err := awxService.ListInventories(map[string]string{ + "name": d.Get("resource_name").(string), + "organization": d.Get("organization_id").(string), + }) + if err != nil { + return 0, err + } + if d.Get("role").(string) == "admin" { + return obj[0].SummaryFields.ObjectRoles.AdminRole.ID, nil + } else if d.Get("role").(string) == "use" { + return obj[0].SummaryFields.ObjectRoles.UseRole.ID, nil + } else if d.Get("role").(string) == "read" { + return obj[0].SummaryFields.ObjectRoles.ReadRole.ID, nil + } else if d.Get("role").(string) == "update" { + return obj[0].SummaryFields.ObjectRoles.UpdateRole.ID, nil + } else { + return 0, fmt.Errorf("Role not valid for inventory") + } + + case "team": + return 0, fmt.Errorf("Team endpoint not implemeneted") + case "organization": + return 0, fmt.Errorf("Organization endpoint not implemeneted") + case "job_template": + awxService := awx.JobTemplateService + obj, _, err := awxService.ListJobTemplates(map[string]string{ + "name": d.Get("resource_name").(string), + }) + if err != nil { + return 0, err + } + if d.Get("role").(string) == "admin" { + return obj[0].SummaryFields.ObjectRoles.AdminRole.ID, nil + } else if d.Get("role").(string) == "execute" { + return obj[0].SummaryFields.ObjectRoles.ExecuteRole.ID, nil + } else if d.Get("role").(string) == "read" { + return obj[0].SummaryFields.ObjectRoles.ReadRole.ID, nil + } else { + return 0, fmt.Errorf("Role not valid for Job Template") + } + case "credential": + return 0, fmt.Errorf("Credential endpoint not implemeneted") + case "project": + awxService := awx.ProjectService + obj, _, err := awxService.ListProjects(map[string]string{ + "name": d.Get("resource_name").(string), + "organization": d.Get("organization_id").(string), + }) + if err != nil { + return 0, err + } + if d.Get("role").(string) == "admin" { + return obj[0].SummaryFields.ObjectRoles.AdminRole.ID, nil + } else if d.Get("role").(string) == "update" { + return obj[0].SummaryFields.ObjectRoles.UpdateRole.ID, nil + } else if d.Get("role").(string) == "read" { + return obj[0].SummaryFields.ObjectRoles.ReadRole.ID, nil + } else if d.Get("role").(string) == "use" { + return obj[0].SummaryFields.ObjectRoles.UseRole.ID, nil + } else { + return 0, fmt.Errorf("Role not valid for Project") + } + } + return 0, fmt.Errorf("Not implemented API endpoint") +} +func resourceUserRoleGrant(d *schema.ResourceData, m interface{}) error { + awx := m.(*awxgo.AWX) + awxService := awx.UserService + _, res, err := awxService.ListUsers(map[string]string{ + "id": d.Get("user_id").(string)}, + ) + if err != nil { + return err + } + if len(res.Results) == 0 { + return fmt.Errorf("User with Id %s doesn't exists", + d.Get("user_id").(string)) + } + id, _ := strconv.Atoi(d.Get("user_id").(string)) + roleID, err := getRoleID(d, m) + if err == nil { + err = awxService.GrantRole(id, roleID) + if err != nil { + return err + } + } else { + return err + } + d.SetId(d.Get("user_id").(string)) + return resourceUserRoleRead(d, m) + +} + +func resourceUserRoleRevoke(d *schema.ResourceData, m interface{}) error { + awx := m.(*awxgo.AWX) + awxService := awx.UserService + + _, res, err := awxService.ListUsers(map[string]string{ + "id": d.Get("user_id").(string)}, + ) + if err != nil { + return err + } + if len(res.Results) == 0 { + return fmt.Errorf("User with Id %s doesn't exists", + d.Get("user_id").(string)) + } + roleID, err := getRoleID(d, m) + if err == nil { + id, _ := strconv.Atoi(d.Get("user_id").(string)) + err = awxService.RevokeRole(id, roleID) + if err != nil { + return err + } + } else { + return err + } + d.SetId("") + return resourceUserRoleRead(d, m) +} + +func resourceUserRoleRead(d *schema.ResourceData, m interface{}) error { + awx := m.(*awxgo.AWX) + awxService := awx.UserService + _, res, err := awxService.ListUsers(map[string]string{ + "id": d.Get("user_id").(string)}) + if err != nil { + return err + } + if len(res.Results) == 0 { + return nil + } + d = setUserRoleResourceData(d, res.Results[0]) + return nil +} + +func setUserRoleResourceData(d *schema.ResourceData, r *awxgo.User) *schema.ResourceData { + d.Set("username", r.Username) + d.Set("user_id", r.ID) + d.Set("resource_name", d.Get("resource_name").(string)) + d.Set("resource_type", d.Get("resource_type").(string)) + d.Set("role", d.Get("role").(string)) + return d +} diff --git a/awx/resource_role_user_test.go b/awx/resource_role_user_test.go new file mode 100644 index 0000000..40e2055 --- /dev/null +++ b/awx/resource_role_user_test.go @@ -0,0 +1,57 @@ +package awx + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +// awx_user test case +func TestAccAWXUserRole(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccUserRoleConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckStateUserRole("role", "admin"), + testAccCheckStateUserRole("resource_type", "inventory"), + testAccCheckStateUserRole("resource_name", "Demo Inventory"), + ), + }, + }, + }) +} + +func testAccCheckStateUserRole(skey, svalue string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["awx_user_role.testacc-user_role_1"] + if !ok { + return fmt.Errorf("awx_user_role.testacc-user_role_1 not found") + } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + cr := rs.Primary + + if cr.Attributes[skey] != svalue { + return fmt.Errorf("%s != %s (actual: %s)", skey, svalue, cr.Attributes[skey]) + } + + return nil + } +} + +const testAccUserRoleConfig = ` +resource "awx_user_role" "testacc-user_role_1" { + user_id = 4 + organization_id = 1 + resource_type = "inventory" + resource_name = "Demo Inventory" + role = "admin" + } +` diff --git a/examples/main.tf b/examples/main.tf index fd99d85..d1babbd 100644 --- a/examples/main.tf +++ b/examples/main.tf @@ -8,7 +8,7 @@ variable "name" { provider "awx" { username = "admin" password = "password" - endpoint = "http://192.168.99.100:32309" + endpoint = "http://192.168.99.100:30967" } data "template_file" "default_yaml" { @@ -90,3 +90,27 @@ resource "awx_user" "test" { email = "medda.mauro@gmail.com" is_superuser = true } + +resource "awx_user_role" "inventory_test_admin" { + user_id = "${awx_user.test.id}" + resource_type = "inventory" + resource_name = "${awx_inventory.default.name}" + role = "admin" + organization_id = 1 +} + +resource "awx_user_role" "project_alpha_use" { + user_id = "${awx_user.test.id}" + resource_type = "project" + resource_name = "${awx_project.alpha.name}" + role = "use" + organization_id = 1 +} + +resource "awx_user_role" "jobtemplate_alpha_execute" { + user_id = "${awx_user.test.id}" + resource_type = "job_template" + resource_name = "${awx_job_template.alpha.name}" + role = "execute" + organization_id = 1 +} diff --git a/vendor/github.com/mauromedda/awx-go/ROADMAP.md b/vendor/github.com/mauromedda/awx-go/ROADMAP.md index 75811b2..19c5d8d 100644 --- a/vendor/github.com/mauromedda/awx-go/ROADMAP.md +++ b/vendor/github.com/mauromedda/awx-go/ROADMAP.md @@ -32,7 +32,8 @@ - [ ] Support SystemJobTemplates endpoints; - [ ] Support SystemJobs endpoints; - [ ] Support Schedules endpoints; -- [ ] Support Roles endpoints; +- [-] Support Roles endpoints: + - [x] Support for User Grant and revoke roles - [ ] Support NotificationTemplates endpoints; - [ ] Support Notifications endpoints; - [ ] Support Labels endpoints; diff --git a/vendor/github.com/mauromedda/awx-go/job_template.go b/vendor/github.com/mauromedda/awx-go/job_template.go index 0a576f2..4f62133 100644 --- a/vendor/github.com/mauromedda/awx-go/job_template.go +++ b/vendor/github.com/mauromedda/awx-go/job_template.go @@ -3,6 +3,7 @@ package awx import ( "bytes" "encoding/json" + "errors" "fmt" ) @@ -51,6 +52,11 @@ func (jt *JobTemplateService) Launch(id int, data map[string]interface{}, params return nil, err } + // in case invalid job id return + if result.Job == 0 { + return nil, errors.New("invalid job id 0") + } + return result, nil } diff --git a/vendor/github.com/mauromedda/awx-go/users.go b/vendor/github.com/mauromedda/awx-go/users.go index e06a94f..029e13c 100644 --- a/vendor/github.com/mauromedda/awx-go/users.go +++ b/vendor/github.com/mauromedda/awx-go/users.go @@ -101,3 +101,52 @@ func (u *UserService) DeleteUser(id int) (*User, error) { return result, nil } + +// GrantRole grant the provided role to the AWX User +func (u *UserService) GrantRole(id int, roleID int) error { + result := new(User) + endpoint := fmt.Sprintf("/api/v2/users/%d/roles/", id) + data := map[string]interface{}{ + "id": roleID, + } + payload, err := json.Marshal(data) + if err != nil { + return err + } + + resp, err := u.client.Requester.PostJSON(endpoint, bytes.NewReader(payload), result, nil) + if err != nil { + return err + } + + if err := CheckResponse(resp); err != nil { + return err + } + + return nil +} + +// RevokeRole revoke the provided role to the AWX User +func (u *UserService) RevokeRole(id int, roleID int) error { + result := new(User) + endpoint := fmt.Sprintf("/api/v2/users/%d/roles/", id) + data := map[string]interface{}{ + "id": roleID, + "disassociate": "true", + } + payload, err := json.Marshal(data) + if err != nil { + return err + } + + resp, err := u.client.Requester.PostJSON(endpoint, bytes.NewReader(payload), result, nil) + if err != nil { + return err + } + + if err := CheckResponse(resp); err != nil { + return err + } + + return nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 18acc44..fd93074 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -695,15 +695,15 @@ "checksumSHA1": "0xjvz/KGzXhAFsnDRvzofE0xNzI=", "origin": "github.com/mauromedda/awx-go/vendor/github.com/kylelemons/godebug/diff", "path": "github.com/kylelemons/godebug/diff", - "revision": "c563cde6cd77a5d9038294d433e2e06378b4805f", - "revisionTime": "2018-10-06T16:57:59Z" + "revision": "e8de834244d4bc8efedeb9271e7e8ce29d0e773e", + "revisionTime": "2018-12-02T10:27:39Z" }, { "checksumSHA1": "ED154c1vFO2UMbFwdOZZ9KdfAzw=", "origin": "github.com/mauromedda/awx-go/vendor/github.com/kylelemons/godebug/pretty", "path": "github.com/kylelemons/godebug/pretty", - "revision": "c563cde6cd77a5d9038294d433e2e06378b4805f", - "revisionTime": "2018-10-06T16:57:59Z" + "revision": "e8de834244d4bc8efedeb9271e7e8ce29d0e773e", + "revisionTime": "2018-12-02T10:27:39Z" }, { "checksumSHA1": "1otkAg+0Q9NksNSuQ3ijCW0xHxE=", @@ -713,10 +713,10 @@ "revisionTime": "2018-08-10T18:32:07Z" }, { - "checksumSHA1": "+ddqLCUM4/a1AkwCaav0F5zNn/E=", + "checksumSHA1": "qs1ysFpnIqdc+KOD+/yeKDv18jk=", "path": "github.com/mauromedda/awx-go", - "revision": "c563cde6cd77a5d9038294d433e2e06378b4805f", - "revisionTime": "2018-10-06T16:57:59Z" + "revision": "e8de834244d4bc8efedeb9271e7e8ce29d0e773e", + "revisionTime": "2018-12-02T10:27:39Z" }, { "checksumSHA1": "H3IQOMnazofqqNSyfY/GIx1T3mU=",