diff --git a/Makefile b/Makefile index 5fb85082..ecdb1244 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,11 @@ test/pango: codegen assets cd $(GENERATED_OUT_PATH)/pango && \ go test -v ./... +.PHONY: test/pango-movement +test/pango-movement: codegen assets + cd $(GENERATED_OUT_PATH)/pango && \ + go test -v ./movement/ + .PHONY: test/pango-example test/pango-example: cd $(GENERATED_OUT_PATH)/pango && \ diff --git a/assets/terraform/internal/manager/uuid.go b/assets/terraform/internal/manager/uuid.go index 4d7e7614..3e806ec8 100644 --- a/assets/terraform/internal/manager/uuid.go +++ b/assets/terraform/internal/manager/uuid.go @@ -644,7 +644,7 @@ func (o *UuidObjectManager[E, L, S]) ReadMany(ctx context.Context, location L, s for idx, elt := range stateEntries { stateEntriesByName[elt.EntryName()] = uuidObjectWithState[E]{ Entry: elt, - State: entryUnknown, + State: entryMissing, StateIdx: idx, } } @@ -661,10 +661,19 @@ func (o *UuidObjectManager[E, L, S]) ReadMany(ctx context.Context, location L, s } common := make([]E, commonCount) + var stateEntriesMissing bool for _, elt := range stateEntriesByName { if elt.State == entryOk { common[elt.StateIdx] = elt.Entry } + + if elt.State == entryMissing { + stateEntriesMissing = true + } + } + + if stateEntriesMissing { + return common, false, nil } actions, err := movement.MoveGroup(position, stateEntries, existing) diff --git a/assets/terraform/internal/manager/uuid_test.go b/assets/terraform/internal/manager/uuid_test.go index 01653297..da936bc9 100644 --- a/assets/terraform/internal/manager/uuid_test.go +++ b/assets/terraform/internal/manager/uuid_test.go @@ -179,6 +179,36 @@ var _ = Describe("Server", func() { Expect(processed).To(MatchEntries(entries)) }) }) + + Context("when some of the entries were removed from the server", func() { + BeforeEach(func() { + initial = []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "3", Value: "C"}} + client = NewMockUuidClient(initial) + service = NewMockUuidService[*MockUuidObject, MockLocation](client) + var ok bool + if mockService, ok = service.(*MockUuidService[*MockUuidObject, MockLocation]); !ok { + panic("failed to cast service to mockService") + } + manager = sdkmanager.NewUuidObjectManager(client, service, batchSize, MockUuidSpecifier, MockUuidMatcher) + + }) + + It("should recreate missing entries on the server based on the state", func() { + entries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "2", Value: "B"}, {Name: "3", Value: "C"}} + + processed, moveRequired, err := manager.ReadMany(ctx, location, entries, sdkmanager.NonExhaustive, movement.PositionLast{}) + + Expect(err).ToNot(HaveOccurred()) + Expect(moveRequired).To(BeFalse()) + Expect(processed).To(HaveLen(2)) + + processed, err = manager.UpdateMany(ctx, location, []string{}, processed, entries, sdkmanager.NonExhaustive, movement.PositionLast{}) + Expect(client.list()).To(HaveLen(3)) + Expect(err).ToNot(HaveOccurred()) + Expect(processed).To(HaveLen(3)) + Expect(processed).To(MatchEntries(entries)) + }) + }) }) Context("initially has some entries", func() { diff --git a/assets/terraform/test/resource_security_policy_rules_test.go b/assets/terraform/test/resource_security_policy_rules_test.go index 5abb3676..2f7372d7 100644 --- a/assets/terraform/test/resource_security_policy_rules_test.go +++ b/assets/terraform/test/resource_security_policy_rules_test.go @@ -940,6 +940,73 @@ func TestAccSecurityPolicyRules_Hierarchy_UniqueNames(t *testing.T) { testAccSecurityPolicyRules_Hierarchy(t, parentRules, childRules) } +const securityPolicyRules_UpdateMissing_Tmpl = ` +variable "prefix" { type = string } +variable "rule_names" { type = list(string) } + +resource "panos_security_policy_rules" "policy" { + location = { device_group = { name = format("%s-dg", var.prefix) }} + + position = { where = "first" } + + rules = [ + for index, name in var.rule_names: { + name = name + + source_zones = ["any"] + source_addresses = ["any"] + + destination_zones = ["any"] + destination_addresses = ["any"] + + services = ["any"] + applications = ["any"] + } + ] +} +` + +func TestAccSecurityPolicyRules_UpdateMissing(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + rules := []string{"rule-1", "rule-2", "rule-3"} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + + }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: securityPolicyRules_UpdateMissing_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "rule_names": config.ListVariable(withPrefix(prefix, rules)...), + }, + ConfigStateChecks: []statecheck.StateCheck{ + ExpectServerSecurityRulesOrder(prefix, rules), + }, + }, + { + Config: securityPolicyRules_UpdateMissing_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "rule_names": config.ListVariable(withPrefix(prefix, rules)...), + }, + PreConfig: func() { + DeleteServerSecurityRules(prefix, []string{"rule-2"}) + }, + ConfigStateChecks: []statecheck.StateCheck{ + ExpectServerSecurityRulesOrder(prefix, rules), + }, + }, + }, + }) +} + func mergeConfigs(configs ...string) string { return strings.Join(configs, "\n") } diff --git a/assets/terraform/test/resource_security_policy_test.go b/assets/terraform/test/resource_security_policy_test.go index e38a641c..85e355ec 100644 --- a/assets/terraform/test/resource_security_policy_test.go +++ b/assets/terraform/test/resource_security_policy_test.go @@ -18,6 +18,25 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) +func DeleteServerSecurityRules(prefix string, ruleNames []string) { + location := security.NewDeviceGroupLocation() + location.DeviceGroup.DeviceGroup = fmt.Sprintf("%s-dg", prefix) + + service := security.NewService(sdkClient) + + var names []string + for _, elt := range ruleNames { + names = append(names, prefixed(prefix, elt)) + } + + err := service.Delete(context.TODO(), *location, names...) + if err != nil { + panic("failed to delete entries from the server") + } + + return +} + type expectServerSecurityRulesOrder struct { Location security.Location Prefix string @@ -516,24 +535,11 @@ func TestAccSecurityPolicyOrdering(t *testing.T) { rulesInitial := []string{"rule-1", "rule-2", "rule-3", "rule-4", "rule-5"} rulesReordered := []string{"rule-2", "rule-1", "rule-3", "rule-4", "rule-5"} - prefixed := func(name string) string { - return fmt.Sprintf("%s-%s", prefix, name) - } - - withPrefix := func(rules []string) []config.Variable { - var result []config.Variable - for _, elt := range rules { - result = append(result, config.StringVariable(prefixed(elt))) - } - - return result - } - stateExpectedRuleName := func(idx int, value string) statecheck.StateCheck { return statecheck.ExpectKnownValue( "panos_security_policy.policy", tfjsonpath.New("rules").AtSliceIndex(idx).AtMapKey("name"), - knownvalue.StringExact(prefixed(value)), + knownvalue.StringExact(prefixed(prefix, value)), ) } @@ -541,7 +547,7 @@ func TestAccSecurityPolicyOrdering(t *testing.T) { return plancheck.ExpectKnownValue( "panos_security_policy.policy", tfjsonpath.New("rules").AtSliceIndex(idx).AtMapKey("name"), - knownvalue.StringExact(prefixed(value)), + knownvalue.StringExact(prefixed(prefix, value)), ) } @@ -573,7 +579,7 @@ func TestAccSecurityPolicyOrdering(t *testing.T) { Config: securityPolicyOrderingTmpl, ConfigVariables: map[string]config.Variable{ "prefix": config.StringVariable(prefix), - "rule_names": config.ListVariable(withPrefix(rulesInitial)...), + "rule_names": config.ListVariable(withPrefix(prefix, rulesInitial)...), }, ConfigStateChecks: []statecheck.StateCheck{ stateExpectedRuleName(0, "rule-1"), @@ -589,7 +595,7 @@ func TestAccSecurityPolicyOrdering(t *testing.T) { Config: securityPolicyOrderingTmpl, ConfigVariables: map[string]config.Variable{ "prefix": config.StringVariable(prefix), - "rule_names": config.ListVariable(withPrefix(rulesInitial)...), + "rule_names": config.ListVariable(withPrefix(prefix, rulesInitial)...), }, ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ @@ -601,7 +607,7 @@ func TestAccSecurityPolicyOrdering(t *testing.T) { Config: securityPolicyOrderingTmpl, ConfigVariables: map[string]config.Variable{ "prefix": config.StringVariable(prefix), - "rule_names": config.ListVariable(withPrefix(rulesReordered)...), + "rule_names": config.ListVariable(withPrefix(prefix, rulesReordered)...), }, ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ @@ -641,6 +647,71 @@ func TestAccSecurityPolicyOrdering(t *testing.T) { }) } +const securityPolicy_UpdateMissing_Tmpl = ` +variable "prefix" { type = string } +variable "rule_names" { type = list(string) } + +resource "panos_security_policy_rules" "policy" { + location = { device_group = { name = format("%s-dg", var.prefix) }} + + rules = [ + for index, name in var.rule_names: { + name = name + + source_zones = ["any"] + source_addresses = ["any"] + + destination_zones = ["any"] + destination_addresses = ["any"] + + services = ["any"] + applications = ["any"] + } + ] +} +` + +func TestAccSecurityPolicy_UpdateMissing(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + rules := []string{"rule-1", "rule-2", "rule-3"} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + + }, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: securityPolicyRules_UpdateMissing_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "rule_names": config.ListVariable(withPrefix(prefix, rules)...), + }, + ConfigStateChecks: []statecheck.StateCheck{ + ExpectServerSecurityRulesOrder(prefix, rules), + }, + }, + { + Config: securityPolicyRules_UpdateMissing_Tmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + "rule_names": config.ListVariable(withPrefix(prefix, rules)...), + }, + PreConfig: func() { + DeleteServerSecurityRules(prefix, []string{"rule-2"}) + }, + ConfigStateChecks: []statecheck.StateCheck{ + ExpectServerSecurityRulesOrder(prefix, rules), + }, + }, + }, + }) +} + func securityPolicyPreCheck(prefix string) { service := security.NewService(sdkClient) ctx := context.TODO()