Skip to content

Commit

Permalink
feat (team service account) Manage Team Based Service Account as Terr…
Browse files Browse the repository at this point in the history
…aform resource (#386)

* add support for team service account

* add documentation

* fix typo in test

* add test for secure

* add sensitive flag to api_key

* fix error description

* replace schema strings with constants

* rename from GetTeamServiceAccountById -> GetTeamServiceAccountByID

* replace strings with costants also in utility methods
  • Loading branch information
Shadow649 authored Jul 24, 2023
1 parent ce2d591 commit 41345d3
Show file tree
Hide file tree
Showing 8 changed files with 504 additions and 0 deletions.
6 changes: 6 additions & 0 deletions sysdig/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sysdig

const (
SchemaIDKey = "id"
SchemaTeamIDKey = "team_id"
SchemaPoliciesKey = "policies"
SchemaPolicyIDsKey = "policy_ids"
SchemaNameKey = "name"
Expand All @@ -14,7 +15,9 @@ const (
SchemaAuthorKey = "author"
SchemaLastModifiedBy = "last_modified_by"
SchemaLastUpdated = "last_updated"
SchemaExpirationDateKey = "expiration_date"
SchemaPublishedDateKey = "published_date"
SchemaCreatedDateKey = "date_created"
SchemaMinKubeVersionKey = "min_kube_version"
SchemaMaxKubeVersionKey = "max_kube_version"
SchemaIsCustomKey = "is_custom"
Expand All @@ -26,5 +29,8 @@ const (
SchemaScopeKey = "scope"
SchemaScopesKey = "scopes"
SchemaTargetTypeKey = "target_type"
SchemaRoleKey = "role"
SchemaSystemRoleKey = "system_role"
SchemaRulesKey = "rules"
SchemaApiKeyKey = "api_key"
)
11 changes: 11 additions & 0 deletions sysdig/internal/client/v2/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ type UserRoles struct {
Admin bool `json:"admin,omitempty"`
}

type TeamServiceAccount struct {
ID int `json:"id,omitempty"`
Name string `json:"name"`
SystemRole string `json:"systemRole"`
TeamId int `json:"teamId"`
TeamRole string `json:"teamRole"`
DateCreated int64 `json:"dateCreated,omitempty"`
ExpirationDate int64 `json:"expirationDate"`
ApiKey string `json:"apiKey,omitempty"`
}

type EntryPoint struct {
Module string `json:"module"`
Selection string `json:"selection,omitempty"`
Expand Down
1 change: 1 addition & 0 deletions sysdig/internal/client/v2/sysdig.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type SysdigCommon interface {
Common
GroupMappingInterface
GroupMappingConfigInterface
TeamServiceAccountInterface
}

type SysdigMonitor interface {
Expand Down
123 changes: 123 additions & 0 deletions sysdig/internal/client/v2/team_service_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package v2

import (
"context"
"errors"
"fmt"
"net/http"
)

var TeamServiceAccountNotFound = errors.New("team service account not found")

const (
ServiceAccountsPath = "%s/api/serviceaccounts/team"
ServiceAccountPath = "%s/api/serviceaccounts/team/%d"
ServiceAccountDeletePath = "%s/api/serviceaccounts/team/%d/delete"
)

type TeamServiceAccountInterface interface {
Base
GetTeamServiceAccountByID(ctx context.Context, id int) (*TeamServiceAccount, error)
CreateTeamServiceAccount(ctx context.Context, account *TeamServiceAccount) (*TeamServiceAccount, error)
UpdateTeamServiceAccount(ctx context.Context, account *TeamServiceAccount, id int) (*TeamServiceAccount, error)
DeleteTeamServiceAccount(ctx context.Context, id int) error
}

func (client *Client) GetTeamServiceAccountByID(ctx context.Context, id int) (*TeamServiceAccount, error) {
response, err := client.requester.Request(ctx, http.MethodGet, client.GetTeamServiceAccountURL(id), nil)
if err != nil {
return nil, err
}
defer response.Body.Close()

if response.StatusCode != http.StatusOK {
if response.StatusCode == http.StatusNotFound {
return nil, TeamServiceAccountNotFound
}
return nil, client.ErrorFromResponse(response)
}

teamServiceAccount, err := Unmarshal[TeamServiceAccount](response.Body)
if err != nil {
return nil, err
}
return &teamServiceAccount, nil
}

func (client *Client) CreateTeamServiceAccount(ctx context.Context, account *TeamServiceAccount) (*TeamServiceAccount, error) {
payload, err := Marshal(account)
if err != nil {
return nil, err
}

response, err := client.requester.Request(ctx, http.MethodPost, client.CreateTeamServiceAccountURL(), payload)
if err != nil {
return nil, err
}
defer response.Body.Close()

if response.StatusCode != http.StatusOK {
return nil, client.ErrorFromResponse(response)
}

created, err := Unmarshal[TeamServiceAccount](response.Body)
if err != nil {
return nil, err
}

return &created, nil
}

func (client *Client) UpdateTeamServiceAccount(ctx context.Context, account *TeamServiceAccount, id int) (*TeamServiceAccount, error) {
payload, err := Marshal(account)
if err != nil {
return nil, err
}

response, err := client.requester.Request(ctx, http.MethodPut, client.UpdateTeamServiceAccountURL(id), payload)
if err != nil {
return nil, err
}
defer response.Body.Close()

if response.StatusCode != http.StatusOK {
return nil, client.ErrorFromResponse(response)
}

updated, err := Unmarshal[TeamServiceAccount](response.Body)
if err != nil {
return nil, err
}

return &updated, nil
}

func (client *Client) DeleteTeamServiceAccount(ctx context.Context, id int) error {
response, err := client.requester.Request(ctx, http.MethodDelete, client.DeleteTeamServiceAccountURL(id), nil)
if err != nil {
return err
}
defer response.Body.Close()

if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNotFound {
return client.ErrorFromResponse(response)
}

return nil
}

func (client *Client) GetTeamServiceAccountURL(id int) string {
return fmt.Sprintf(ServiceAccountPath, client.config.url, id)
}

func (client *Client) CreateTeamServiceAccountURL() string {
return fmt.Sprintf(ServiceAccountsPath, client.config.url)
}

func (client *Client) UpdateTeamServiceAccountURL(id int) string {
return fmt.Sprintf(ServiceAccountPath, client.config.url, id)
}

func (client *Client) DeleteTeamServiceAccountURL(id int) string {
return fmt.Sprintf(ServiceAccountDeletePath, client.config.url, id)
}
1 change: 1 addition & 0 deletions sysdig/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func Provider() *schema.Provider {
"sysdig_user": resourceSysdigUser(),
"sysdig_group_mapping": resourceSysdigGroupMapping(),
"sysdig_group_mapping_config": resourceSysdigGroupMappingConfig(),
"sysdig_team_service_account": resourceSysdigTeamServiceAccount(),

"sysdig_secure_custom_policy": resourceSysdigSecureCustomPolicy(),
"sysdig_secure_managed_policy": resourceSysdigSecureManagedPolicy(),
Expand Down
198 changes: 198 additions & 0 deletions sysdig/resource_sysdig_team_service_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package sysdig

import (
"context"
v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"strconv"
"time"
)

func resourceSysdigTeamServiceAccount() *schema.Resource {
timeout := 5 * time.Minute
return &schema.Resource{
ReadContext: resourceSysdigTeamServiceAccountRead,
CreateContext: resourceSysdigTeamServiceAccountCreate,
UpdateContext: resourceSysdigTeamServiceAccountUpdate,
DeleteContext: resourceSysdigTeamServiceAccountDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(timeout),
Update: schema.DefaultTimeout(timeout),
Read: schema.DefaultTimeout(timeout),
Delete: schema.DefaultTimeout(timeout),
},
Schema: map[string]*schema.Schema{
SchemaNameKey: {
Type: schema.TypeString,
Required: true,
},
SchemaRoleKey: {
Type: schema.TypeString,
Optional: true,
Default: "ROLE_TEAM_READ",
},
SchemaExpirationDateKey: {
Type: schema.TypeInt,
Required: true,
},
SchemaTeamIDKey: {
Type: schema.TypeInt,
Required: true,
},
SchemaSystemRoleKey: {
Type: schema.TypeString,
Optional: true,
Default: "ROLE_SERVICE_ACCOUNT",
},
SchemaCreatedDateKey: {
Type: schema.TypeInt,
Computed: true,
},
SchemaApiKeyKey: {
Type: schema.TypeString,
Computed: true,
Sensitive: true,
},
},
}
}

func resourceSysdigTeamServiceAccountRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client, err := m.(SysdigClients).sysdigCommonClientV2()
if err != nil {
diag.FromErr(err)
}

id, err := strconv.Atoi(d.Id())
if err != nil {
diag.FromErr(err)
}

teamServiceAccount, err := client.GetTeamServiceAccountByID(ctx, id)
if err != nil {
if err == v2.TeamServiceAccountNotFound {
d.SetId("")
return nil
}
return diag.FromErr(err)
}

err = teamServiceAccountToResourceData(teamServiceAccount, d)
if err != nil {
return diag.FromErr(err)
}

return nil
}

func resourceSysdigTeamServiceAccountCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var err error

client, err := m.(SysdigClients).sysdigCommonClientV2()
if err != nil {
return diag.FromErr(err)
}

teamServiceAccount := teamServiceAccountFromResourceData(d)
teamServiceAccount, err = client.CreateTeamServiceAccount(ctx, teamServiceAccount)
if err != nil {
return diag.FromErr(err)
}

d.SetId(strconv.Itoa(teamServiceAccount.ID))

resourceSysdigTeamServiceAccountRead(ctx, d, m)

return nil
}

func resourceSysdigTeamServiceAccountUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var err error

client, err := m.(SysdigClients).sysdigCommonClientV2()
if err != nil {
return diag.FromErr(err)
}

teamServiceAccount := teamServiceAccountFromResourceData(d)
id, err := strconv.Atoi(d.Id())
if err != nil {
return diag.FromErr(err)
}

teamServiceAccount.ID = id
_, err = client.UpdateTeamServiceAccount(ctx, teamServiceAccount, id)
if err != nil {
return diag.FromErr(err)
}

resourceSysdigTeamServiceAccountRead(ctx, d, m)

return nil
}

func resourceSysdigTeamServiceAccountDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client, err := m.(SysdigClients).sysdigCommonClientV2()
if err != nil {
return diag.FromErr(err)
}

id, err := strconv.Atoi(d.Id())
if err != nil {
return diag.FromErr(err)
}
err = client.DeleteTeamServiceAccount(ctx, id)
if err != nil {
return diag.FromErr(err)
}

return nil
}

func teamServiceAccountFromResourceData(d *schema.ResourceData) *v2.TeamServiceAccount {
return &v2.TeamServiceAccount{
Name: d.Get(SchemaNameKey).(string),
TeamRole: d.Get(SchemaRoleKey).(string),
ExpirationDate: int64(d.Get(SchemaExpirationDateKey).(int) * 1000),
TeamId: d.Get(SchemaTeamIDKey).(int),
SystemRole: d.Get(SchemaSystemRoleKey).(string),
ApiKey: d.Get(SchemaApiKeyKey).(string),
}
}

func teamServiceAccountToResourceData(teamServiceAccount *v2.TeamServiceAccount, d *schema.ResourceData) error {
err := d.Set(SchemaNameKey, teamServiceAccount.Name)
if err != nil {
return err
}
err = d.Set(SchemaRoleKey, teamServiceAccount.TeamRole)
if err != nil {
return err
}
err = d.Set(SchemaExpirationDateKey, teamServiceAccount.ExpirationDate/1000)
if err != nil {
return err
}
err = d.Set(SchemaTeamIDKey, teamServiceAccount.TeamId)
if err != nil {
return err
}
err = d.Set(SchemaSystemRoleKey, teamServiceAccount.SystemRole)
if err != nil {
return err
}
err = d.Set(SchemaCreatedDateKey, teamServiceAccount.DateCreated)
if err != nil {
return err
}
err = d.Set(SchemaApiKeyKey, teamServiceAccount.ApiKey)
if err != nil {
return err
}

return nil
}
Loading

0 comments on commit 41345d3

Please sign in to comment.