diff --git a/sysdig/internal/client/v2/model.go b/sysdig/internal/client/v2/model.go index 320c03c98..5b8f41304 100644 --- a/sysdig/internal/client/v2/model.go +++ b/sysdig/internal/client/v2/model.go @@ -1154,6 +1154,11 @@ type PostureZoneRequest struct { Scopes []PostureZoneScope `json:"scopes"` } +type ZonePoliciesRequest struct { + ZoneID int `json:"zoneId"` + PolicyIDs []int `json:"policyIds"` +} + type PostureZoneResponse struct { Data PostureZone `json:"data"` } diff --git a/sysdig/internal/client/v2/posture_zones.go b/sysdig/internal/client/v2/posture_zones.go index 263d83b1c..7b6d9d07f 100644 --- a/sysdig/internal/client/v2/posture_zones.go +++ b/sysdig/internal/client/v2/posture_zones.go @@ -7,8 +7,9 @@ import ( ) const ( - PostureZonesPath = "%s/api/cspm/v1/policy/zones" - PostureZonePath = "%s/api/cspm/v1/policy/zones/%d" + PostureZonesPath = "%s/api/cspm/v1/policy/zones" + PostureZonePath = "%s/api/cspm/v1/policy/zones/%d" + PostureZonePoliciesPath = "%s/api/cspm/v1/policy/zone-policies" ) type PostureZoneInterface interface { @@ -16,6 +17,7 @@ type PostureZoneInterface interface { CreateOrUpdatePostureZone(ctx context.Context, z *PostureZoneRequest) (*PostureZone, string, error) GetPostureZone(ctx context.Context, id int) (*PostureZone, error) DeletePostureZone(ctx context.Context, id int) error + BindZoneToPolicies(ctx context.Context, r *ZonePoliciesRequest) error } func (client *Client) CreateOrUpdatePostureZone(ctx context.Context, r *PostureZoneRequest) (*PostureZone, string, error) { @@ -76,6 +78,27 @@ func (client *Client) DeletePostureZone(ctx context.Context, id int) error { return nil } +func (client *Client) BindZoneToPolicies(ctx context.Context, r *ZonePoliciesRequest) error { + + payload, err := Marshal(r) + if err != nil { + return err + } + + response, err := client.requester.Request(ctx, http.MethodPost, client.getZonePoliciesURL(), payload) + if err != nil { + return err + } + + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return client.ErrorFromResponse(response) + } + + return nil +} + func (client *Client) createPostureZoneURL() string { return fmt.Sprintf(PostureZonesPath, client.config.url) } @@ -83,3 +106,7 @@ func (client *Client) createPostureZoneURL() string { func (client *Client) getPostureZoneURL(id int) string { return fmt.Sprintf(PostureZonePath, client.config.url, id) } + +func (client *Client) getZonePoliciesURL() string { + return fmt.Sprintf(PostureZonePoliciesPath, client.config.url) +} diff --git a/sysdig/resource_sysdig_secure_posture_zone.go b/sysdig/resource_sysdig_secure_posture_zone.go index dd4f3d6b0..7d77ad90b 100644 --- a/sysdig/resource_sysdig_secure_posture_zone.go +++ b/sysdig/resource_sysdig_secure_posture_zone.go @@ -2,6 +2,8 @@ package sysdig import ( "context" + "fmt" + "github.com/rs/zerolog/log" "strconv" "time" @@ -14,8 +16,8 @@ func resourceSysdigSecurePostureZone() *schema.Resource { timeout := 5 * time.Minute return &schema.Resource{ - CreateContext: resourceCreateOrUpdatePostureZone, - UpdateContext: resourceCreateOrUpdatePostureZone, + CreateContext: resourceCreatePostureZone, + UpdateContext: resourceUpdatePostureZone, DeleteContext: resourceSysdigSecurePostureZoneDelete, ReadContext: resourceSysdigSecurePostureZoneRead, Importer: &schema.ResourceImporter{ @@ -105,48 +107,119 @@ func getPostureZoneClient(c SysdigClients) (v2.PostureZoneInterface, error) { return client, nil } -func resourceCreateOrUpdatePostureZone(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - policiesData := d.Get(SchemaPolicyIDsKey).(*schema.Set).List() - policies := make([]string, len(policiesData)) - for i, p := range policiesData { - policies[i] = strconv.Itoa(p.(int)) +func resourceCreatePostureZone(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + policies, err := getPolicies(d) + if err != nil { + return diag.FromErr(err) } - scopesList := d.Get(SchemaScopesKey).(*schema.Set).List() - scopes := make([]v2.PostureZoneScope, 0) - if len(scopesList) > 0 { - scopeList := scopesList[0].(map[string]interface{})[SchemaScopeKey].(*schema.Set).List() - for _, attr := range scopeList { - s := attr.(map[string]interface{}) - scopes = append(scopes, v2.PostureZoneScope{ - TargetType: s[SchemaTargetTypeKey].(string), - Rules: s[SchemaRulesKey].(string), - }) - } + scopes, err := getScopes(d) + if err != nil { + return diag.FromErr(err) } - req := &v2.PostureZoneRequest{ - ID: d.Id(), + zoneClient, err := getZoneClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + postureZoneClient, err := getPostureZoneClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + zoneRequest := &v2.ZoneRequest{ Name: d.Get(SchemaNameKey).(string), Description: d.Get(SchemaDescriptionKey).(string), - PolicyIDs: policies, Scopes: scopes, } - zoneClient, err := getPostureZoneClient(meta.(SysdigClients)) + zone, err := zoneClient.CreateZone(ctx, zoneRequest) + if err != nil { + return diag.Errorf("Error creating resource: %s", err) + } + + policyIDs, err := convertPoliciesToInt(policies) if err != nil { return diag.FromErr(err) } - zone, errStatus, err := zoneClient.CreateOrUpdatePostureZone(ctx, req) + req := &v2.ZonePoliciesRequest{ + ZoneID: zone.ID, + PolicyIDs: policyIDs, + } + + err = postureZoneClient.BindZoneToPolicies(ctx, req) if err != nil { - return diag.Errorf("Error creating resource: %s %s", errStatus, err) + log.Err(err).Int("zone_id", zone.ID).Msg("Error attaching zone to policies... deleting created zone") + err2 := zoneClient.DeleteZone(ctx, zone.ID) + if err2 != nil { + return diag.Errorf("Error deleting zone [zone ID = %d] after failed attaching to policies: %s", zone.ID, err2) + } + return diag.Errorf("Error attaching zone to policies: %s", err) } - d.SetId(zone.ID) + d.SetId(strconv.Itoa(zone.ID)) + return resourceSysdigSecurePostureZoneRead(ctx, d, meta) +} - resourceSysdigSecurePostureZoneRead(ctx, d, meta) - return nil +func resourceUpdatePostureZone(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + policies, err := getPolicies(d) + if err != nil { + return diag.FromErr(err) + } + + scopes, err := getScopes(d) + if err != nil { + return diag.FromErr(err) + } + + zoneClient, err := getZoneClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + postureZoneClient, err := getPostureZoneClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.Errorf("Error updating posture zone resource, ID is not integer: %s", d.Id()) + } + + zoneRequest := &v2.ZoneRequest{ + ID: id, + Name: d.Get(SchemaNameKey).(string), + Description: d.Get(SchemaDescriptionKey).(string), + Scopes: scopes, + } + + if d.HasChange(SchemaNameKey) || d.HasChange(SchemaDescriptionKey) || d.HasChange(SchemaScopesKey) { + _, err = zoneClient.UpdateZone(ctx, zoneRequest) + if err != nil { + return diag.Errorf("Error updating resource: %s", err) + } + } + + policyIDs, err := convertPoliciesToInt(policies) + if err != nil { + return diag.FromErr(err) + } + + if d.HasChange(SchemaPolicyIDsKey) { + req := &v2.ZonePoliciesRequest{ + ZoneID: id, + PolicyIDs: policyIDs, + } + + err = postureZoneClient.BindZoneToPolicies(ctx, req) + if err != nil { + log.Err(err).Int("zone_id", id).Msg("Error attaching zone to policies") + return diag.Errorf("Error attaching zone to policies: %s", err) + } + } + + return resourceSysdigSecurePostureZoneRead(ctx, d, meta) } func resourceSysdigSecurePostureZoneRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -225,7 +298,11 @@ func resourceSysdigSecurePostureZoneRead(ctx context.Context, d *schema.Resource } func resourceSysdigSecurePostureZoneDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client, err := getPostureZoneClient(meta.(SysdigClients)) + postureClient, err := getPostureZoneClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + zoneClient, err := getZoneClient(meta.(SysdigClients)) if err != nil { return diag.FromErr(err) } @@ -235,10 +312,62 @@ func resourceSysdigSecurePostureZoneDelete(ctx context.Context, d *schema.Resour return diag.FromErr(err) } - err = client.DeletePostureZone(ctx, id) + err = removeZoneFromPolicies(ctx, postureClient, id) if err != nil { - return diag.FromErr(err) + return diag.Errorf("Error removing zone from policies: %s", err) + } + + err = zoneClient.DeleteZone(ctx, id) + if err != nil { + + return diag.Errorf("Error deleting zone: %s", err) } return nil } + +func removeZoneFromPolicies(ctx context.Context, client v2.PostureZoneInterface, zoneID int) error { + req := &v2.ZonePoliciesRequest{ + ZoneID: zoneID, + PolicyIDs: []int{}, + } + + return client.BindZoneToPolicies(ctx, req) +} + +func getPolicies(d *schema.ResourceData) ([]string, error) { + policiesData := d.Get(SchemaPolicyIDsKey).(*schema.Set).List() + policies := make([]string, len(policiesData)) + for i, p := range policiesData { + policies[i] = strconv.Itoa(p.(int)) + } + return policies, nil +} + +func getScopes(d *schema.ResourceData) ([]v2.ZoneScope, error) { + scopesList := d.Get(SchemaScopesKey).(*schema.Set).List() + scopes := make([]v2.ZoneScope, 0) + if len(scopesList) > 0 { + scopeList := scopesList[0].(map[string]interface{})[SchemaScopeKey].(*schema.Set).List() + for _, attr := range scopeList { + s := attr.(map[string]interface{}) + scopes = append(scopes, v2.ZoneScope{ + TargetType: s[SchemaTargetTypeKey].(string), + Rules: s[SchemaRulesKey].(string), + }) + } + } + return scopes, nil +} + +func convertPoliciesToInt(policies []string) ([]int, error) { + policyIDs := make([]int, len(policies)) + for i, p := range policies { + id, err := strconv.Atoi(p) + if err != nil { + return nil, fmt.Errorf("error converting policy ID to int: %s", err) + } + policyIDs[i] = id + } + return policyIDs, nil +}