diff --git a/docs/data-sources/teams.md b/docs/data-sources/teams.md new file mode 100644 index 000000000..8afbaadfb --- /dev/null +++ b/docs/data-sources/teams.md @@ -0,0 +1,78 @@ +--- +page_title: "octopusdeploy_teams Data Source - terraform-provider-octopusdeploy" +subcategory: "" +description: |- + Provides information about existing users. +--- + +# Data Source `octopusdeploy_teams` + +Provides information about existing users. + + + +## Schema + +### Optional + +- **ids** (List of String, Optional) A filter to search by a list of IDs. +- **include_system** (Boolean, Optional) A filter to include system teams. +- **partial_name** (String, Optional) A filter to search by the partial match of a name. +- **skip** (Number, Optional) A filter to specify the number of items to skip in the response. +- **take** (Number, Optional) A filter to specify the number of items to take (or return) in the response. + +### Read-only + +- **id** (String, Read-only) A auto-generated identifier that includes the timestamp when this data source was last modified. +- **spaces** (Block List) A list of spaces that match the filter(s). (see [below for nested schema](#nestedblock--spaces)) +- **teams** (Block List) A list of teams that match the filter(s). (see [below for nested schema](#nestedblock--teams)) + + +### Nested Schema for `spaces` + +Read-only: + +- **can_be_deleted** (Boolean, Read-only) +- **can_be_renamed** (Boolean, Read-only) +- **can_change_members** (Boolean, Read-only) +- **can_change_roles** (Boolean, Read-only) +- **description** (String, Read-only) +- **external_security_groups** (List of Object, Read-only) (see [below for nested schema](#nestedatt--spaces--external_security_groups)) +- **id** (String, Read-only) The unique ID for this resource. +- **name** (String, Read-only) +- **space_id** (String, Read-only) +- **users** (List of String, Read-only) A list of user IDs designated to be members of this team. + + +### Nested Schema for `spaces.external_security_groups` + +- **display_id_and_name** (Boolean) +- **display_name** (String) +- **id** (String) + + + + +### Nested Schema for `teams` + +Read-only: + +- **can_be_deleted** (Boolean, Read-only) +- **can_be_renamed** (Boolean, Read-only) +- **can_change_members** (Boolean, Read-only) +- **can_change_roles** (Boolean, Read-only) +- **description** (String, Read-only) +- **external_security_groups** (List of Object, Read-only) (see [below for nested schema](#nestedatt--teams--external_security_groups)) +- **id** (String, Read-only) The unique ID for this resource. +- **name** (String, Read-only) +- **space_id** (String, Read-only) +- **users** (List of String, Read-only) A list of user IDs designated to be members of this team. + + +### Nested Schema for `teams.external_security_groups` + +- **display_id_and_name** (Boolean) +- **display_name** (String) +- **id** (String) + + diff --git a/docs/resources/team.md b/docs/resources/team.md new file mode 100644 index 000000000..03b3f961e --- /dev/null +++ b/docs/resources/team.md @@ -0,0 +1,44 @@ +--- +page_title: "octopusdeploy_team Resource - terraform-provider-octopusdeploy" +subcategory: "" +description: |- + This resource manages teams in Octopus Deploy. +--- + +# Resource `octopusdeploy_team` + +This resource manages teams in Octopus Deploy. + + + +## Schema + +### Required + +- **name** (String, Required) + +### Optional + +- **can_be_deleted** (Boolean, Optional) +- **can_be_renamed** (Boolean, Optional) +- **can_change_members** (Boolean, Optional) +- **can_change_roles** (Boolean, Optional) +- **description** (String, Optional) +- **external_security_groups** (Block List) (see [below for nested schema](#nestedblock--external_security_groups)) +- **id** (String, Optional) The unique ID for this resource. +- **users** (List of String, Optional) A list of user IDs designated to be members of this team. + +### Read-only + +- **space_id** (String, Read-only) + + +### Nested Schema for `external_security_groups` + +Optional: + +- **display_id_and_name** (Boolean, Optional) +- **display_name** (String, Optional) +- **id** (String, Optional) The unique ID for this resource. + + diff --git a/octopusdeploy/data_source_teams.go b/octopusdeploy/data_source_teams.go new file mode 100644 index 000000000..27e808041 --- /dev/null +++ b/octopusdeploy/data_source_teams.go @@ -0,0 +1,44 @@ +package octopusdeploy + +import ( + "context" + "time" + + "github.com/OctopusDeploy/go-octopusdeploy/octopusdeploy" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceTeams() *schema.Resource { + return &schema.Resource{ + Description: "Provides information about existing users.", + ReadContext: dataSourceTeamsRead, + Schema: getTeamDataSchema(), + } +} + +func dataSourceTeamsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + query := octopusdeploy.TeamsQuery{ + IDs: expandArray(d.Get("ids").([]interface{})), + IncludeSystem: d.Get("include_system").(bool), + PartialName: d.Get("partial_name").(string), + Skip: d.Get("skip").(int), + Take: d.Get("take").(int), + } + + client := meta.(*octopusdeploy.Client) + users, err := client.Teams.Get(query) + if err != nil { + return diag.FromErr(err) + } + + flattenedTeams := []interface{}{} + for _, user := range users.Items { + flattenedTeams = append(flattenedTeams, flattenTeam(user)) + } + + d.Set("teams", flattenedTeams) + d.SetId("Teams " + time.Now().UTC().String()) + + return nil +} diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go index 031d6ce55..a1c5eb98e 100644 --- a/octopusdeploy/provider.go +++ b/octopusdeploy/provider.go @@ -35,6 +35,7 @@ func Provider() *schema.Provider { "octopusdeploy_spaces": dataSourceSpaces(), "octopusdeploy_ssh_connection_deployment_targets": dataSourceSSHConnectionDeploymentTargets(), "octopusdeploy_tag_sets": dataSourceTagSets(), + "octopusdeploy_teams": dataSourceTeams(), "octopusdeploy_tenants": dataSourceTenants(), "octopusdeploy_users": dataSourceUsers(), "octopusdeploy_user_roles": dataSourceUserRoles(), @@ -70,6 +71,7 @@ func Provider() *schema.Provider { "octopusdeploy_ssh_connection_deployment_target": resourceSSHConnectionDeploymentTarget(), "octopusdeploy_ssh_key_account": resourceSSHKeyAccount(), "octopusdeploy_tag_set": resourceTagSet(), + "octopusdeploy_team": resourceTeam(), "octopusdeploy_tenant": resourceTenant(), "octopusdeploy_token_account": resourceTokenAccount(), "octopusdeploy_user": resourceUser(), diff --git a/octopusdeploy/resource_team.go b/octopusdeploy/resource_team.go new file mode 100644 index 000000000..669b092e7 --- /dev/null +++ b/octopusdeploy/resource_team.go @@ -0,0 +1,98 @@ +package octopusdeploy + +import ( + "context" + "log" + + "github.com/OctopusDeploy/go-octopusdeploy/octopusdeploy" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceTeam() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceTeamCreate, + DeleteContext: resourceTeamDelete, + Description: "This resource manages teams in Octopus Deploy.", + Importer: getImporter(), + ReadContext: resourceTeamRead, + Schema: getTeamSchema(), + UpdateContext: resourceTeamUpdate, + } +} + +func resourceTeamCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + team := expandTeam(d) + + log.Printf("[INFO] creating team: %#v", team) + + client := m.(*octopusdeploy.Client) + createdTeam, err := client.Teams.Add(team) + if err != nil { + return diag.FromErr(err) + } + + if err := setTeam(ctx, d, createdTeam); err != nil { + return diag.FromErr(err) + } + + d.SetId(createdTeam.GetID()) + + log.Printf("[INFO] team created (%s)", d.Id()) + return nil +} + +func resourceTeamDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Printf("[INFO] deleting team (%s)", d.Id()) + + client := m.(*octopusdeploy.Client) + if err := client.Teams.DeleteByID(d.Id()); err != nil { + return diag.FromErr(err) + } + + d.SetId("") + + log.Printf("[INFO] team deleted") + return nil +} + +func resourceTeamRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Printf("[INFO] reading team (%s)", d.Id()) + + client := m.(*octopusdeploy.Client) + team, err := client.Teams.GetByID(d.Id()) + if err != nil { + apiError := err.(*octopusdeploy.APIError) + if apiError.StatusCode == 404 { + log.Printf("[INFO] team (%s) not found; deleting from state", d.Id()) + d.SetId("") + return nil + } + return diag.FromErr(err) + } + + if err := setTeam(ctx, d, team); err != nil { + return diag.FromErr(err) + } + + log.Printf("[INFO] team read (%s)", d.Id()) + return nil +} + +func resourceTeamUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Printf("[INFO] updating team (%s)", d.Id()) + + team := expandTeam(d) + client := m.(*octopusdeploy.Client) + updatedTeam, err := client.Teams.Update(team) + if err != nil { + return diag.FromErr(err) + } + + if err := setTeam(ctx, d, updatedTeam); err != nil { + return diag.FromErr(err) + } + + log.Printf("[INFO] team updated (%s)", d.Id()) + return nil +} diff --git a/octopusdeploy/schema_external_security_groups.go b/octopusdeploy/schema_external_security_groups.go new file mode 100644 index 000000000..acf7b5086 --- /dev/null +++ b/octopusdeploy/schema_external_security_groups.go @@ -0,0 +1,73 @@ +package octopusdeploy + +import ( + "github.com/OctopusDeploy/go-octopusdeploy/octopusdeploy" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func expandExternalSecurityGroups(externalSecurityGroups []interface{}) []octopusdeploy.NamedReferenceItem { + expandedExternalSecurityGroups := make([]octopusdeploy.NamedReferenceItem, 0, len(externalSecurityGroups)) + for _, externalSecurityGroup := range externalSecurityGroups { + if externalSecurityGroup != nil { + rawExternalSecurityGroup := externalSecurityGroup.(map[string]interface{}) + + displayIDAndName := false + if rawExternalSecurityGroup["display_id_and_name"] != nil { + displayIDAndName = rawExternalSecurityGroup["display_id_and_name"].(bool) + } + + displayName := "" + if rawExternalSecurityGroup["display_name"] != nil { + displayName = rawExternalSecurityGroup["display_name"].(string) + } + + id := "" + if rawExternalSecurityGroup["id"] != nil { + id = rawExternalSecurityGroup["id"].(string) + } + + item := octopusdeploy.NamedReferenceItem{ + DisplayIDAndName: displayIDAndName, + DisplayName: displayName, + ID: id, + } + expandedExternalSecurityGroups = append(expandedExternalSecurityGroups, item) + } + } + return expandedExternalSecurityGroups +} + +func flattenExternalSecurityGroups(externalSecurityGroups []octopusdeploy.NamedReferenceItem) []interface{} { + if externalSecurityGroups == nil { + return nil + } + + flattenedExternalSecurityGroups := make([]interface{}, len(externalSecurityGroups)) + for i, externalSecurityGroup := range externalSecurityGroups { + rawExternalSecurityGroup := map[string]interface{}{ + "display_id_and_name": externalSecurityGroup.DisplayIDAndName, + "display_name": externalSecurityGroup.DisplayName, + "id": externalSecurityGroup.ID, + } + + flattenedExternalSecurityGroups[i] = rawExternalSecurityGroup + } + + return flattenedExternalSecurityGroups +} + +func getExternalSecurityGroupsSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "display_id_and_name": { + Computed: true, + Optional: true, + Type: schema.TypeBool, + }, + "display_name": { + Computed: true, + Optional: true, + Type: schema.TypeString, + }, + "id": getIDSchema(), + } +} diff --git a/octopusdeploy/schema_queries.go b/octopusdeploy/schema_queries.go index 95d9f166e..f2be184d3 100644 --- a/octopusdeploy/schema_queries.go +++ b/octopusdeploy/schema_queries.go @@ -165,6 +165,14 @@ func getQueryIDs() *schema.Schema { } } +func getQueryIncludeSystem() *schema.Schema { + return &schema.Schema{ + Description: "A filter to include system teams.", + Optional: true, + Type: schema.TypeBool, + } +} + func getQueryIsClone() *schema.Schema { return &schema.Schema{ Description: "A filter to search for cloned resources.", diff --git a/octopusdeploy/schema_team.go b/octopusdeploy/schema_team.go new file mode 100644 index 000000000..873fa6123 --- /dev/null +++ b/octopusdeploy/schema_team.go @@ -0,0 +1,170 @@ +package octopusdeploy + +import ( + "context" + "fmt" + + "github.com/OctopusDeploy/go-octopusdeploy/octopusdeploy" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func expandTeam(d *schema.ResourceData) *octopusdeploy.Team { + name := d.Get("name").(string) + + team := octopusdeploy.NewTeam(name) + team.ID = d.Id() + + if v, ok := d.GetOk("can_be_deleted"); ok { + team.CanBeDeleted = v.(bool) + } + + if v, ok := d.GetOk("can_be_renamed"); ok { + team.CanBeRenamed = v.(bool) + } + + if v, ok := d.GetOk("can_change_members"); ok { + team.CanChangeMembers = v.(bool) + } + + if v, ok := d.GetOk("can_change_roles"); ok { + team.CanChangeRoles = v.(bool) + } + + if v, ok := d.GetOk("description"); ok { + team.Description = v.(string) + } + + if v, ok := d.GetOk("external_security_groups"); ok { + team.ExternalSecurityGroups = expandExternalSecurityGroups(v.(*schema.Set).List()) + } + + if v, ok := d.GetOk("space_id"); ok { + team.SpaceID = v.(string) + } + + if v, ok := d.GetOk("users"); ok { + team.MemberUserIDs = getSliceFromTerraformTypeList(v) + } + + return team +} + +func flattenTeam(team *octopusdeploy.Team) map[string]interface{} { + if team == nil { + return nil + } + + return map[string]interface{}{ + "can_be_deleted": team.CanBeDeleted, + "can_be_renamed": team.CanBeRenamed, + "can_change_members": team.CanChangeMembers, + "can_change_roles": team.CanChangeRoles, + "description": team.Description, + "external_security_groups": flattenExternalSecurityGroups(team.ExternalSecurityGroups), + "id": team.GetID(), + "name": team.Name, + "space_id": team.SpaceID, + "users": team.MemberUserIDs, + } +} + +func getTeamDataSchema() map[string]*schema.Schema { + dataSchema := getTeamSchema() + setDataSchema(&dataSchema) + + return map[string]*schema.Schema{ + "id": getDataSchemaID(), + "ids": getQueryIDs(), + "include_system": getQueryIncludeSystem(), + "partial_name": getQueryPartialName(), + "skip": getQuerySkip(), + "spaces": { + Description: "A list of spaces that match the filter(s).", + Elem: &schema.Resource{Schema: dataSchema}, + Optional: true, + Type: schema.TypeList, + }, + "take": getQueryTake(), + "teams": { + Computed: true, + Description: "A list of teams that match the filter(s).", + Elem: &schema.Resource{Schema: dataSchema}, + Optional: true, + Type: schema.TypeList, + }, + } +} + +func getTeamSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "can_be_deleted": { + Computed: true, + Optional: true, + Type: schema.TypeBool, + }, + "can_be_renamed": { + Computed: true, + Optional: true, + Type: schema.TypeBool, + }, + "can_change_members": { + Computed: true, + Optional: true, + Type: schema.TypeBool, + }, + "can_change_roles": { + Computed: true, + Optional: true, + Type: schema.TypeBool, + }, + "description": { + Optional: true, + Type: schema.TypeString, + }, + "external_security_groups": { + Optional: true, + Elem: &schema.Resource{Schema: getExternalSecurityGroupsSchema()}, + Type: schema.TypeList, + }, + "id": getIDSchema(), + "name": { + Required: true, + Type: schema.TypeString, + }, + "space_id": { + Computed: true, + Type: schema.TypeString, + }, + "users": { + Computed: true, + Description: "A list of user IDs designated to be members of this team.", + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Type: schema.TypeList, + }, + } +} + +func setTeam(ctx context.Context, d *schema.ResourceData, team *octopusdeploy.Team) error { + d.Set("can_be_deleted", team.CanBeDeleted) + d.Set("can_be_renamed", team.CanBeRenamed) + d.Set("can_change_members", team.CanChangeMembers) + d.Set("can_change_roles", team.CanChangeRoles) + d.Set("description", team.Description) + + if err := d.Set("external_security_groups", flattenExternalSecurityGroups(team.ExternalSecurityGroups)); err != nil { + return fmt.Errorf("error setting external_security_groups: %s", err) + } + + d.Set("id", team.GetID()) + d.Set("name", team.Name) + d.Set("space_id", team.SpaceID) + + if err := d.Set("users", team.MemberUserIDs); err != nil { + return fmt.Errorf("error setting users: %s", err) + } + + d.SetId(team.GetID()) + + return nil +}