Skip to content

Commit

Permalink
feat(secure-onboarding): Adding AccountFeature Resource (#508)
Browse files Browse the repository at this point in the history
* SSPROD-38949: Adding support for AccountFeature resource

* updating method type

* feat(secure-onboarding): Adding AccountFeature Resource

Change summary:
----------------
- Adding new Account Feature resource with schema and CRUD operations
  in parity with Cloudauth Account and AccountComponent resource.
- Adding the respective client and support for new resource type.
- Added TF ACC tests for the new resource type.
- Added docs md for the new resource.

* Remove redundant client implementation between create & update

---------

Co-authored-by: Haresh Suresh <[email protected]>
  • Loading branch information
ravinadhruve10 and haresh-suresh authored May 2, 2024
1 parent cbc8239 commit f681575
Show file tree
Hide file tree
Showing 11 changed files with 594 additions and 4 deletions.
1 change: 1 addition & 0 deletions sysdig/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,5 @@ const (
SchemaCloudProviderTenantId = "provider_tenant_id"
SchemaCloudProviderAlias = "provider_alias"
SchemaAccountId = "account_id"
SchemaFeatureFlags = "flags"
)
1 change: 0 additions & 1 deletion sysdig/internal/client/v2/cloudauth_account_component.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
const (
cloudauthAccountComponentsPath = "%s/api/cloudauth/v1/accounts/%s/components" // POST
cloudauthAccountComponentPath = "%s/api/cloudauth/v1/accounts/%s/components/%s/%s" // GET, PUT, DEL
// getCloudauthAccountPath = "%s/api/cloudauth/v1/accounts/%s?decrypt=%s" // does GET require decryption?
)

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

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

const (
cloudauthAccountFeaturePath = "%s/api/cloudauth/v1/accounts/%s/feature/%s" // GET, PUT, DEL
)

type CloudauthAccountFeatureSecureInterface interface {
Base
CreateOrUpdateCloudauthAccountFeatureSecure(ctx context.Context, accountID, featureType string, cloudAccountFeature *CloudauthAccountFeatureSecure) (*CloudauthAccountFeatureSecure, string, error)
GetCloudauthAccountFeatureSecure(ctx context.Context, accountID, featureType string) (*CloudauthAccountFeatureSecure, string, error)
DeleteCloudauthAccountFeatureSecure(ctx context.Context, accountID, featureType string) (string, error)
}

// both create and update makes a PUT call to backend
func (client *Client) CreateOrUpdateCloudauthAccountFeatureSecure(ctx context.Context, accountID, featureType string, cloudAccountFeature *CloudauthAccountFeatureSecure) (
*CloudauthAccountFeatureSecure, string, error) {
payload, err := client.marshalCloudauthProto(cloudAccountFeature)
if err != nil {
return nil, "", err
}

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

if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
errStatus, err := client.ErrorAndStatusFromResponse(response)
return nil, errStatus, err
}

cloudauthAccountFeature := &CloudauthAccountFeatureSecure{}
err = client.unmarshalCloudauthProto(response.Body, cloudauthAccountFeature)
if err != nil {
return nil, "", err
}
return cloudauthAccountFeature, "", nil
}

func (client *Client) GetCloudauthAccountFeatureSecure(ctx context.Context, accountID, featureType string) (*CloudauthAccountFeatureSecure, string, error) {
response, err := client.requester.Request(ctx, http.MethodGet, client.cloudauthAccountFeatureURL(accountID, featureType), nil)
if err != nil {
return nil, "", err
}
defer response.Body.Close()

if response.StatusCode != http.StatusOK {
errStatus, err := client.ErrorAndStatusFromResponse(response)
return nil, errStatus, err
}

cloudauthAccountFeature := &CloudauthAccountFeatureSecure{}
err = client.unmarshalCloudauthProto(response.Body, cloudauthAccountFeature)
if err != nil {
return nil, "", err
}
return cloudauthAccountFeature, "", nil
}

func (client *Client) DeleteCloudauthAccountFeatureSecure(ctx context.Context, accountID, featureType string) (string, error) {
response, err := client.requester.Request(ctx, http.MethodDelete, client.cloudauthAccountFeatureURL(accountID, featureType), nil)
if err != nil {
return "", err
}
defer response.Body.Close()

if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusOK {
return client.ErrorAndStatusFromResponse(response)
}
return "", nil
}

func (client *Client) cloudauthAccountFeatureURL(accountID string, featureType string) string {
return fmt.Sprintf(cloudauthAccountFeaturePath, client.config.url, accountID, featureType)
}
4 changes: 4 additions & 0 deletions sysdig/internal/client/v2/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,10 @@ type CloudauthAccountComponentSecure struct {
cloudauth.AccountComponent
}

type CloudauthAccountFeatureSecure struct {
cloudauth.AccountFeature
}

type ScanningPolicy struct {
ID string `json:"id,omitempty"`
Version string `json:"version,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 @@ -47,6 +47,7 @@ type SysdigSecure interface {
CloudauthAccountSecureInterface
OrganizationSecureInterface
CloudauthAccountComponentSecureInterface
CloudauthAccountFeatureSecureInterface
}

func (sr *SysdigRequest) Request(ctx context.Context, method string, url string, payload io.Reader) (*http.Response, error) {
Expand Down
1 change: 1 addition & 0 deletions sysdig/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ func (p *SysdigProvider) Provider() *schema.Provider {
"sysdig_secure_scanning_policy_assignment": resourceSysdigSecureScanningPolicyAssignment(),
"sysdig_secure_cloud_auth_account": resourceSysdigSecureCloudauthAccount(),
"sysdig_secure_cloud_auth_account_component": resourceSysdigSecureCloudauthAccountComponent(),
"sysdig_secure_cloud_auth_account_feature": resourceSysdigSecureCloudauthAccountFeature(),

"sysdig_monitor_silence_rule": resourceSysdigMonitorSilenceRule(),
"sysdig_monitor_alert_downtime": resourceSysdigMonitorAlertDowntime(),
Expand Down
4 changes: 2 additions & 2 deletions sysdig/resource_sysdig_secure_cloud_auth_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
declare common schemas used across resources here
*/
var (
accountComponents = &schema.Resource{
accountComponent = &schema.Resource{
Schema: map[string]*schema.Schema{
SchemaType: {
Type: schema.TypeString,
Expand Down Expand Up @@ -166,7 +166,7 @@ func resourceSysdigSecureCloudauthAccount() *schema.Resource {
SchemaComponent: {
Type: schema.TypeSet,
Optional: true,
Elem: accountComponents,
Elem: accountComponent,
},
SchemaOrganizationIDKey: {
Type: schema.TypeString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func getAccountComponentSchema() map[string]*schema.Schema {
},
}

for field, schema := range accountComponents.Schema {
for field, schema := range accountComponent.Schema {
componentSchema[field] = schema
}
return componentSchema
Expand Down
248 changes: 248 additions & 0 deletions sysdig/resource_sysdig_secure_cloud_auth_account_feature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package sysdig

import (
"context"
"errors"
"fmt"
"strings"
"time"

v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2"
cloudauth "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2/cloudauth/go"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceSysdigSecureCloudauthAccountFeature() *schema.Resource {
timeout := 5 * time.Minute

return &schema.Resource{
CreateContext: resourceSysdigSecureCloudauthAccountFeatureCreate,
UpdateContext: resourceSysdigSecureCloudauthAccountFeatureUpdate,
ReadContext: resourceSysdigSecureCloudauthAccountFeatureRead,
DeleteContext: resourceSysdigSecureCloudauthAccountFeatureDelete,
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: getAccountFeatureSchema(),
}
}

func getAccountFeatureSchema() map[string]*schema.Schema {
// though the schema fields are already defined in cloud_auth_account resource, for AccountFeature
// calls they are required fields. Also, account_id & flags are needed additionally.
featureSchema := map[string]*schema.Schema{
SchemaAccountId: {
Type: schema.TypeString,
Required: true,
},
SchemaType: {
Type: schema.TypeString,
Required: true,
},
SchemaEnabled: {
Type: schema.TypeBool,
Required: true,
},
SchemaComponents: {
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
SchemaFeatureFlags: {
Type: schema.TypeMap,
Optional: true,
},
}

return featureSchema
}

func getSecureCloudauthAccountFeatureClient(client SysdigClients) (v2.CloudauthAccountFeatureSecureInterface, error) {
return client.sysdigSecureClientV2()
}

func resourceSysdigSecureCloudauthAccountFeatureCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, err := getSecureCloudauthAccountFeatureClient((meta.(SysdigClients)))
if err != nil {
return diag.FromErr(err)
}

accountId := data.Get(SchemaAccountId).(string)
cloudauthAccountFeature, errStatus, err := client.CreateOrUpdateCloudauthAccountFeatureSecure(
ctx, accountId, data.Get(SchemaType).(string), cloudauthAccountFeatureFromResourceData(data))
if err != nil {
return diag.Errorf("Error creating resource: %s %s", errStatus, err)
}

// using tuple 'accountId/featureType' as TF resource identifier
data.SetId(accountId + "/" + cloudauthAccountFeature.GetType().String())
err = data.Set(SchemaAccountId, accountId)
if err != nil {
return diag.FromErr(err)
}

return nil
}

func resourceSysdigSecureCloudauthAccountFeatureRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, err := getSecureCloudauthAccountFeatureClient((meta.(SysdigClients)))
if err != nil {
return diag.FromErr(err)
}

cloudauthAccountFeature, errStatus, err := client.GetCloudauthAccountFeatureSecure(
ctx, data.Get(SchemaAccountId).(string), data.Get(SchemaType).(string))

if err != nil {
if strings.Contains(errStatus, "404") {
return nil
}
return diag.Errorf("Error reading resource: %s %s", errStatus, err)
}

err = cloudauthAccountFeatureToResourceData(data, cloudauthAccountFeature)
if err != nil {
return diag.FromErr(err)
}

return nil
}

func resourceSysdigSecureCloudauthAccountFeatureUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, err := getSecureCloudauthAccountFeatureClient(meta.(SysdigClients))
if err != nil {
return diag.FromErr(err)
}

accountId := data.Get(SchemaAccountId).(string)
existingCloudAccountFeature, errStatus, err := client.GetCloudauthAccountFeatureSecure(
ctx, accountId, data.Get(SchemaType).(string))
if err != nil {
if strings.Contains(errStatus, "404") {
return nil
}
return diag.Errorf("Error reading resource: %s %s", errStatus, err)
}

newCloudAccountFeature := cloudauthAccountFeatureFromResourceData(data)

// validate and reject non-updatable resource schema fields upfront
err = validateCloudauthAccountFeatureUpdate(existingCloudAccountFeature, newCloudAccountFeature)
if err != nil {
return diag.Errorf("Error updating resource: %s", err)
}

_, errStatus, err = client.CreateOrUpdateCloudauthAccountFeatureSecure(
ctx, accountId, data.Get(SchemaType).(string), newCloudAccountFeature)
if err != nil {
if strings.Contains(errStatus, "404") {
return nil
}
return diag.Errorf("Error updating resource: %s %s", errStatus, err)
}

return nil
}

func resourceSysdigSecureCloudauthAccountFeatureDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, err := getSecureCloudauthAccountFeatureClient(meta.(SysdigClients))
if err != nil {
return diag.FromErr(err)
}

errStatus, err := client.DeleteCloudauthAccountFeatureSecure(
ctx, data.Get(SchemaAccountId).(string), data.Get(SchemaType).(string))
if err != nil {
if strings.Contains(errStatus, "404") {
return nil
}
return diag.Errorf("Error deleting resource: %s %s", errStatus, err)
}

return nil
}

/*
This function validates and restricts any fields not allowed to be updated during resource updates.
*/
func validateCloudauthAccountFeatureUpdate(existingFeature *v2.CloudauthAccountFeatureSecure, newFeature *v2.CloudauthAccountFeatureSecure) error {
if existingFeature.Type != newFeature.Type {
errorInvalidResourceUpdate := fmt.Sprintf("Bad Request. Updating restricted fields not allowed: %s", []string{"type"})
return errors.New(errorInvalidResourceUpdate)
}

return nil
}

func getFeatureComponentsList(data *schema.ResourceData) []string {
componentsList := []string{}
componentsResourceList := data.Get(SchemaComponents).([]interface{})
for _, componentID := range componentsResourceList {
componentsList = append(componentsList, componentID.(string))
}
return componentsList
}

func getFeatureFlags(data *schema.ResourceData) map[string]string {
featureFlags := map[string]string{}
flagsResource := data.Get(SchemaFeatureFlags).(map[string]interface{})
for name, value := range flagsResource {
featureFlags[name] = value.(string)
}
return featureFlags
}

func cloudauthAccountFeatureFromResourceData(data *schema.ResourceData) *v2.CloudauthAccountFeatureSecure {
cloudAccountFeature := &v2.CloudauthAccountFeatureSecure{
AccountFeature: cloudauth.AccountFeature{
Type: cloudauth.Feature(cloudauth.Feature_value[data.Get(SchemaType).(string)]),
Enabled: data.Get(SchemaEnabled).(bool),
Components: getFeatureComponentsList(data),
Flags: getFeatureFlags(data),
},
}

return cloudAccountFeature
}

func cloudauthAccountFeatureToResourceData(data *schema.ResourceData, cloudAccountFeature *v2.CloudauthAccountFeatureSecure) error {

accountId := data.Get(SchemaAccountId).(string)
data.SetId(accountId + "/" + cloudAccountFeature.GetType().String())

err := data.Set(SchemaAccountId, accountId)
if err != nil {
return err
}

err = data.Set(SchemaType, cloudAccountFeature.GetType().String())
if err != nil {
return err
}

err = data.Set(SchemaEnabled, cloudAccountFeature.GetEnabled())
if err != nil {
return err
}

err = data.Set(SchemaComponents, cloudAccountFeature.GetComponents())
if err != nil {
return err
}

err = data.Set(SchemaFeatureFlags, cloudAccountFeature.GetFlags())
if err != nil {
return err
}

return nil
}
Loading

0 comments on commit f681575

Please sign in to comment.