From 3bc4ad3b95fc7ac235c0f5cea49b81fdee7ab1b5 Mon Sep 17 00:00:00 2001 From: Axel Ismirlian Date: Wed, 18 Sep 2024 11:25:44 -0500 Subject: [PATCH] [R] Add NSG Rule --- ibm/acctest/acctest.go | 94 +-- ibm/flex/structures.go | 16 + ibm/provider/provider.go | 1 + ...ta_source_ibm_pi_network_security_group.go | 1 - ibm/service/power/ibm_pi_constants.go | 19 + ...urce_ibm_pi_network_security_group_rule.go | 573 ++++++++++++++++++ ...ibm_pi_network_security_group_rule_test.go | 191 ++++++ ..._network_security_group_rule.html.markdown | 136 +++++ 8 files changed, 996 insertions(+), 35 deletions(-) create mode 100644 ibm/service/power/resource_ibm_pi_network_security_group_rule.go create mode 100644 ibm/service/power/resource_ibm_pi_network_security_group_rule_test.go create mode 100644 website/docs/r/pi_network_security_group_rule.html.markdown diff --git a/ibm/acctest/acctest.go b/ibm/acctest/acctest.go index 28cb43f35b..89223b83ed 100644 --- a/ibm/acctest/acctest.go +++ b/ibm/acctest/acctest.go @@ -203,40 +203,43 @@ var ( // For Power Colo var ( - Pi_auxiliary_volume_name string - Pi_cloud_instance_id string - Pi_dhcp_id string - Pi_host_group_id string - Pi_host_id string - Pi_image string - Pi_image_bucket_access_key string - Pi_image_bucket_file_name string - Pi_image_bucket_name string - Pi_image_bucket_region string - Pi_image_bucket_secret_key string - Pi_instance_name string - Pi_key_name string - Pi_network_name string - Pi_network_security_group_id string - Pi_placement_group_name string - Pi_replication_volume_name string - Pi_resource_group_id string - Pi_sap_image string - Pi_shared_processor_pool_id string - Pi_snapshot_id string - Pi_spp_placement_group_id string - Pi_target_storage_tier string - Pi_volume_clone_task_id string - Pi_volume_group_id string - Pi_volume_group_name string - Pi_volume_id string - Pi_volume_name string - Pi_volume_onboarding_id string - Pi_volume_onboarding_source_crn string - PiCloudConnectionName string - PiSAPProfileID string - PiStoragePool string - PiStorageType string + Pi_auxiliary_volume_name string + Pi_cloud_instance_id string + Pi_dhcp_id string + Pi_host_group_id string + Pi_host_id string + Pi_image string + Pi_image_bucket_access_key string + Pi_image_bucket_file_name string + Pi_image_bucket_name string + Pi_image_bucket_region string + Pi_image_bucket_secret_key string + Pi_instance_name string + Pi_key_name string + Pi_network_name string + Pi_network_security_group_id string + Pi_network_security_group_rule_id string + Pi_placement_group_name string + Pi_remote_id string + Pi_remote_type string + Pi_replication_volume_name string + Pi_resource_group_id string + Pi_sap_image string + Pi_shared_processor_pool_id string + Pi_snapshot_id string + Pi_spp_placement_group_id string + Pi_target_storage_tier string + Pi_volume_clone_task_id string + Pi_volume_group_id string + Pi_volume_group_name string + Pi_volume_id string + Pi_volume_name string + Pi_volume_onboarding_id string + Pi_volume_onboarding_source_crn string + PiCloudConnectionName string + PiSAPProfileID string + PiStoragePool string + PiStorageType string ) var ( @@ -1067,6 +1070,13 @@ func init() { Pi_network_security_group_id = "terraform-test-power" fmt.Println("[INFO] Set the environment variable PI_NETWORK_SECURITY_GROUP_ID for testing ibm_pi_network_security_group resource else it is set to default value 'terraform-test-power'") } + + Pi_network_security_group_rule_id = os.Getenv("PI_NETWORK_SECURITY_GROUP_RULE_ID") + if Pi_network_security_group_rule_id == "" { + Pi_network_security_group_rule_id = "terraform-test-power" + fmt.Println("[INFO] Set the environment variable PI_NETWORK_SECURITY_GROUP_RULE_ID for testing ibm_pi_network_security_group resource else it is set to default value 'terraform-test-power'") + } + Pi_volume_name = os.Getenv("PI_VOLUME_NAME") if Pi_volume_name == "" { Pi_volume_name = "terraform-test-power" @@ -1156,21 +1166,37 @@ func init() { Pi_placement_group_name = "tf-pi-placement-group" fmt.Println("[WARN] Set the environment variable PI_PLACEMENT_GROUP_NAME for testing ibm_pi_placement_group resource else it is set to default value 'tf-pi-placement-group'") } + + Pi_remote_id = os.Getenv("PI_REMOTE_ID") + if Pi_remote_id == "" { + Pi_remote_id = "terraform-test-power" + fmt.Println("[WARN] Set the environment variable PI_REMOTE_ID for testing ibm_pi_network_security_group resource else it is set to default value 'terraform-test-power'") + } + + Pi_remote_type = os.Getenv("PI_REMOTE_TYPE") + if Pi_remote_type == "" { + Pi_remote_type = "terraform-test-power" + fmt.Println("[WARN] Set the environment variable PI_REMOTE_TYPE for testing ibm_pi_network_security_group resource else it is set to default value 'terraform-test-power'") + } + Pi_spp_placement_group_id = os.Getenv("PI_SPP_PLACEMENT_GROUP_ID") if Pi_spp_placement_group_id == "" { Pi_spp_placement_group_id = "tf-pi-spp-placement-group" fmt.Println("[WARN] Set the environment variable PI_SPP_PLACEMENT_GROUP_ID for testing ibm_pi_spp_placement_group resource else it is set to default value 'tf-pi-spp-placement-group'") } + PiStoragePool = os.Getenv("PI_STORAGE_POOL") if PiStoragePool == "" { PiStoragePool = "terraform-test-power" fmt.Println("[INFO] Set the environment variable PI_STORAGE_POOL for testing ibm_pi_storage_pool_capacity else it is set to default value 'terraform-test-power'") } + PiStorageType = os.Getenv("PI_STORAGE_TYPE") if PiStorageType == "" { PiStorageType = "terraform-test-power" fmt.Println("[INFO] Set the environment variable PI_STORAGE_TYPE for testing ibm_pi_storage_type_capacity else it is set to default value 'terraform-test-power'") } + // Added for resource capture instance testing Pi_capture_storage_image_path = os.Getenv("PI_CAPTURE_STORAGE_IMAGE_PATH") if Pi_capture_storage_image_path == "" { diff --git a/ibm/flex/structures.go b/ibm/flex/structures.go index da354497e6..3aa1bbc5c2 100644 --- a/ibm/flex/structures.go +++ b/ibm/flex/structures.go @@ -127,6 +127,22 @@ func FlattenIntList(list []int) []interface{} { return vs } +func ExpandInt64List(input []interface{}) []int64 { + vs := make([]int64, len(input)) + for i, v := range input { + vs[i] = v.(int64) + } + return vs +} + +func FlattenInt64List(list []int64) []interface{} { + vs := make([]interface{}, len(list)) + for i, v := range list { + vs[i] = v + } + return vs +} + func NewStringSet(f schema.SchemaSetFunc, in []string) *schema.Set { var out = make([]interface{}, len(in), len(in)) for i, v := range in { diff --git a/ibm/provider/provider.go b/ibm/provider/provider.go index 64ddc51598..eac53d9dcd 100644 --- a/ibm/provider/provider.go +++ b/ibm/provider/provider.go @@ -1302,6 +1302,7 @@ func Provider() *schema.Provider { "ibm_pi_network_port_attach": power.ResourceIBMPINetworkPortAttach(), "ibm_pi_network_security_group_action": power.ResourceIBMPINetworkSecurityGroupAction(), "ibm_pi_network_security_group_member": power.ResourceIBMPINetworkSecurityGroupMember(), + "ibm_pi_network_security_group_rule": power.ResourceIBMPINetworkSecurityGroupRule(), "ibm_pi_network_security_group": power.ResourceIBMPINetworkSecurityGroup(), "ibm_pi_network": power.ResourceIBMPINetwork(), "ibm_pi_placement_group": power.ResourceIBMPIPlacementGroup(), diff --git a/ibm/service/power/data_source_ibm_pi_network_security_group.go b/ibm/service/power/data_source_ibm_pi_network_security_group.go index 8a81bf3716..576b072559 100644 --- a/ibm/service/power/data_source_ibm_pi_network_security_group.go +++ b/ibm/service/power/data_source_ibm_pi_network_security_group.go @@ -251,7 +251,6 @@ func networkSecurityGroupRuleToMap(rule *models.NetworkSecurityGroupRule) map[st ruleMap[Attr_Protocol] = []map[string]interface{}{protocolMap} remoteMap := networkSecurityGroupRuleRemoteToMap(rule.Remote) - ruleMap[Attr_Remote] = []map[string]interface{}{remoteMap} if rule.SourcePort != nil { diff --git a/ibm/service/power/ibm_pi_constants.go b/ibm/service/power/ibm_pi_constants.go index f64349551a..3753a982f6 100644 --- a/ibm/service/power/ibm_pi_constants.go +++ b/ibm/service/power/ibm_pi_constants.go @@ -20,6 +20,7 @@ const ( Arg_DeploymentTarget = "pi_deployment_target" Arg_DeploymentType = "pi_deployment_type" Arg_Description = "pi_description" + Arg_DestinationPorts = "pi_destination_ports" Arg_DhcpID = "pi_dhcp_id" Arg_DhcpName = "pi_dhcp_name" Arg_DhcpSnatEnabled = "pi_dhcp_snat_enabled" @@ -48,6 +49,7 @@ const ( Arg_NetworkName = "pi_network_name" Arg_NetworkSecurityGroupID = "pi_network_security_group_id" Arg_NetworkSecurityGroupMemberID = "pi_network_security_group_member_id" + Arg_NetworkSecurityGroupRuleID = "pi_network_security_group_rule_id" Arg_PIInstanceSharedProcessorPool = "pi_shared_processor_pool" Arg_PinPolicy = "pi_pin_policy" Arg_PlacementGroupID = "pi_placement_group_id" @@ -56,9 +58,11 @@ const ( Arg_Plan = "pi_plan" Arg_Processors = "pi_processors" Arg_ProcType = "pi_proc_type" + Arg_Protocol = "pi_protocol" Arg_PVMInstanceActionType = "pi_action" Arg_PVMInstanceHealthStatus = "pi_health_status" Arg_PVMInstanceId = "pi_instance_id" + Arg_Remote = "pi_remote" Arg_Remove = "pi_remove" Arg_Replicants = "pi_replicants" Arg_ReplicationEnabled = "pi_replication_enabled" @@ -77,6 +81,7 @@ const ( Arg_SharedProcessorPoolReservedCores = "pi_shared_processor_pool_reserved_cores" Arg_SnapshotID = "pi_snapshot_id" Arg_SnapShotName = "pi_snap_shot_name" + Arg_SourcePorts = "pi_source_ports" Arg_SPPPlacementGroupID = "pi_spp_placement_group_id" Arg_SPPPlacementGroupName = "pi_spp_placement_group_name" Arg_SPPPlacementGroupPolicy = "pi_spp_placement_group_policy" @@ -437,6 +442,8 @@ const ( // Allowed Values Affinity = "affinity" + All = "all" + Allow = "allow" AntiAffinity = "anti-affinity" Attach = "attach" BYOL = "byol" @@ -444,19 +451,27 @@ const ( Critical = "CRITICAL" CUSTOM_VIRTUAL_CORES = "custom-virtualcores" Dedicated = "dedicated" + DefaultNAG = "default-network-address-group" + Deny = "deny" DeploymentTypeEpic = "EPIC" DeploymentTypeVMNoStorage = "VMNoStorage" + DestinationUnreach = "destination-unreach" DHCPVlan = "dhcp-vlan" Disable = "disable" + Echo = "echo" + EchoReply = "echo-reply" Enable = "enable" Hana = "Hana" Hard = "hard" Host = "host" HostGroup = "hostGroup" + ICMP = "icmp" IPV4_Address = "ipv4-address" + NAG = "network-address-group" Netweaver = "Netweaver" Network_Interface = "network-interface" None = "none" + NSG = "network-security-group" OK = "OK" PER = "power-edge-router" Prefix = "prefix" @@ -466,7 +481,11 @@ const ( SAP = "SAP" Shared = "shared" Soft = "soft" + SourceQuench = "source-quench" Suffix = "suffix" + TCP = "tcp" + TimeExceeded = "time-exceeded" + UDP = "udp" Vlan = "vlan" vSCSI = "vSCSI" Warning = "WARNING" diff --git a/ibm/service/power/resource_ibm_pi_network_security_group_rule.go b/ibm/service/power/resource_ibm_pi_network_security_group_rule.go new file mode 100644 index 0000000000..d2ed52c62e --- /dev/null +++ b/ibm/service/power/resource_ibm_pi_network_security_group_rule.go @@ -0,0 +1,573 @@ +// Copyright IBM Corp. 2024 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package power + +import ( + "context" + "fmt" + "time" + + "github.com/IBM-Cloud/power-go-client/clients/instance" + "github.com/IBM-Cloud/power-go-client/power/models" + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/conns" + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/flex" + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/validate" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func ResourceIBMPINetworkSecurityGroupRule() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceIBMPINetworkSecurityGroupRuleCreate, + ReadContext: resourceIBMPINetworkSecurityGroupRuleRead, + DeleteContext: resourceIBMPINetworkSecurityGroupRuleDelete, + Importer: &schema.ResourceImporter{}, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + // Arguments + Arg_Action: { + ConflictsWith: []string{Arg_NetworkSecurityGroupRuleID}, + Description: "The action to take if the rule matches network traffic.", + ForceNew: true, + Optional: true, + Type: schema.TypeString, + ValidateFunc: validate.ValidateAllowedStringValues([]string{Allow, Deny}), + }, + Arg_CloudInstanceID: { + Description: "The GUID of the service instance associated with an account.", + ForceNew: true, + Required: true, + Type: schema.TypeString, + ValidateFunc: validation.NoZeroValues, + }, + Arg_DestinationPorts: { + ConflictsWith: []string{Arg_NetworkSecurityGroupRuleID}, + Description: "Destination port ranges.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_Maximum: { + Default: 65535, + Description: "The end of the port range, if applicable. If the value is not present then the default value of 65535 will be the maximum port number.", + Optional: true, + Type: schema.TypeInt, + }, + Attr_Minimum: { + Default: 1, + Description: "The start of the port range, if applicable. If the value is not present then the default value of 1 will be the minimum port number.", + Optional: true, + Type: schema.TypeInt, + }, + }, + }, + ForceNew: true, + MaxItems: 1, + Optional: true, + Type: schema.TypeList, + }, + Arg_NetworkSecurityGroupID: { + Description: "The unique identifier of the network security group.", + ForceNew: true, + Required: true, + Type: schema.TypeString, + }, + Arg_NetworkSecurityGroupRuleID: { + ConflictsWith: []string{Arg_Action, Arg_DestinationPorts, Arg_Protocol, Arg_Remote, Arg_SourcePorts}, + Description: "The network security group rule id to remove.", + ForceNew: true, + Optional: true, + Type: schema.TypeString, + }, + Arg_Protocol: { + ConflictsWith: []string{Arg_NetworkSecurityGroupRuleID}, + Description: "The protocol of the network traffic.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_ICMPType: { + Description: "If icmp type, a ICMP packet type affected by ICMP rules and if not present then all types are matched.", + Optional: true, + Type: schema.TypeString, + ValidateFunc: validate.ValidateAllowedStringValues([]string{All, DestinationUnreach, Echo, EchoReply, SourceQuench, TimeExceeded}), + }, + Attr_TCPFlags: { + Description: "If tcp type, the list of TCP flags and if not present then all flags are matched.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_Flag: { + Description: "TCP flag.", + Required: true, + Type: schema.TypeString, + }, + }, + }, + Optional: true, + Type: schema.TypeList, + }, + Attr_Type: { + Description: "The protocol of the network traffic.", + Required: true, + Type: schema.TypeString, + ValidateFunc: validate.ValidateAllowedStringValues([]string{All, ICMP, TCP, UDP}), + }, + }, + }, + ForceNew: true, + MaxItems: 1, + Optional: true, + Type: schema.TypeList, + }, + Arg_Remote: { + ConflictsWith: []string{Arg_NetworkSecurityGroupRuleID}, + Description: "The protocol of the network traffic.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_ID: { + Description: "The ID of the remote network address group or network security group the rules apply to. Not required for default-network-address-group.", + Optional: true, + Type: schema.TypeString, + }, + Attr_Type: { + Description: "The type of remote group (MAC addresses, IP addresses, CIDRs, external CIDRs) that are the originators of rule's network traffic to match.", + Optional: true, + Type: schema.TypeString, + ValidateFunc: validate.ValidateAllowedStringValues([]string{DefaultNAG, NAG, NSG}), + }, + }, + }, + ForceNew: true, + MaxItems: 1, + Optional: true, + Type: schema.TypeList, + }, + Arg_SourcePorts: { + ConflictsWith: []string{Arg_NetworkSecurityGroupRuleID}, + Description: "Source port ranges.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_Maximum: { + Default: 65535, + Description: "The end of the port range, if applicable. If the value is not present then the default value of 65535 will be the maximum port number.", + Optional: true, + Type: schema.TypeInt, + }, + Attr_Minimum: { + Default: 1, + Description: "The start of the port range, if applicable. If the value is not present then the default value of 1 will be the minimum port number.", + Optional: true, + Type: schema.TypeInt, + }, + }, + }, + ForceNew: true, + MaxItems: 1, + Optional: true, + Type: schema.TypeList, + }, + + // Attributes + Attr_CRN: { + Computed: true, + Description: "The network security group's crn.", + Type: schema.TypeString, + }, + Attr_Members: { + Computed: true, + Description: "The list of IPv4 addresses and, or network interfaces in the network security group.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_ID: { + Computed: true, + Description: "The ID of the member in a network security group.", + Type: schema.TypeString, + }, + Attr_MacAddress: { + Computed: true, + Description: "The mac address of a network interface included if the type is network-interface.", + Type: schema.TypeString, + }, + Attr_Target: { + Computed: true, + Description: "If ipv4-address type, then IPv4 address or if network-interface type, then network interface ID.", + Type: schema.TypeString, + }, + Attr_Type: { + Computed: true, + Description: "The type of member.", + Type: schema.TypeString, + }, + }, + }, + Type: schema.TypeList, + }, + Attr_Name: { + Computed: true, + Description: "The name of the network security group.", + Type: schema.TypeString, + }, + Attr_NetworkSecurityGroupID: { + Computed: true, + Description: "The unique identifier of the network security group.", + Type: schema.TypeString, + }, + Attr_Rules: { + Computed: true, + Description: "The list of rules in the network security group.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_Action: { + Computed: true, + Description: "The action to take if the rule matches network traffic.", + Type: schema.TypeString, + }, + Attr_DestinationPort: { + Computed: true, + Description: "Destination port ranges.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_Maximum: { + Computed: true, + Description: "The end of the port range, if applicable. If the value is not present then the default value of 65535 will be the maximum port number.", + Type: schema.TypeFloat, + }, + Attr_Minimum: { + Computed: true, + Description: "The start of the port range, if applicable. If the value is not present then the default value of 1 will be the minimum port number.", + Type: schema.TypeFloat, + }, + }, + }, + Type: schema.TypeList, + }, + Attr_ID: { + Computed: true, + Description: "The ID of the rule in a network security group.", + Type: schema.TypeString, + }, + Attr_Protocol: { + Computed: true, + Description: "The list of protocol.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_ICMPType: { + Computed: true, + Description: "If icmp type, a ICMP packet type affected by ICMP rules and if not present then all types are matched.", + Type: schema.TypeString, + }, + Attr_TCPFlags: { + Computed: true, + Description: "If tcp type, the list of TCP flags and if not present then all flags are matched.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_Flag: { + Computed: true, + Description: "TCP flag.", + Type: schema.TypeString, + }, + }, + }, + Type: schema.TypeList, + }, + Attr_Type: { + Computed: true, + Description: "The protocol of the network traffic.", + Type: schema.TypeString, + }, + }, + }, + Type: schema.TypeList, + }, + Attr_Remote: { + Computed: true, + Description: "List of remote.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_ID: { + Computed: true, + Description: "The ID of the remote network address group or network security group the rules apply to. Not required for default-network-address-group.", + Type: schema.TypeString, + }, + Attr_Type: { + Computed: true, + Description: "The type of remote group the rules apply to.", + Type: schema.TypeString, + }, + }, + }, + Type: schema.TypeList, + }, + Attr_SourcePort: { + Computed: true, + Description: "Source port ranges.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + Attr_Maximum: { + Computed: true, + Description: "The end of the port range, if applicable. If the value is not present then the default value of 65535 will be the maximum port number.", + Type: schema.TypeFloat, + }, + Attr_Minimum: { + Computed: true, + Description: "The start of the port range, if applicable. If the value is not present then the default value of 1 will be the minimum port number.", + Type: schema.TypeFloat, + }, + }, + }, + Type: schema.TypeList, + }, + }, + }, + Type: schema.TypeList, + }, + }, + } +} + +func resourceIBMPINetworkSecurityGroupRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sess, err := meta.(conns.ClientSession).IBMPISession() + if err != nil { + return diag.FromErr(err) + } + + cloudInstanceID := d.Get(Arg_CloudInstanceID).(string) + nsgClient := instance.NewIBMIPINetworkSecurityGroupClient(ctx, sess, cloudInstanceID) + nsgID := d.Get(Arg_NetworkSecurityGroupID).(string) + + if v, ok := d.GetOk(Arg_NetworkSecurityGroupRuleID); ok { + ruleID := v.(string) + err := nsgClient.DeleteRule(nsgID, ruleID) + if err != nil { + return diag.FromErr(err) + } + _, err = isWaitForIBMPINetworkSecurityGroupRuleRemove(ctx, nsgClient, nsgID, ruleID, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return diag.FromErr(err) + } + d.SetId(fmt.Sprintf("%s/%s", cloudInstanceID, nsgID)) + } else { + action := d.Get(Arg_Action).(string) + + networkSecurityGroupAddRule := models.NetworkSecurityGroupAddRule{ + Action: &action, + } + + // Add protocol + protocol := d.Get(Arg_Protocol + ".0").(map[string]interface{}) + networkSecurityGroupAddRule.Protocol = networkSecurityGroupRuleMapToProtocol(protocol) + + // Add remote + remote := d.Get(Arg_Remote + ".0").(map[string]interface{}) + networkSecurityGroupAddRule.Remote = networkSecurityGroupRuleMapToRemote(remote) + + // Optional fields + destinationPort := d.Get(Arg_DestinationPorts + ".0").(map[string]interface{}) + networkSecurityGroupAddRule.DestinationPorts = networkSecurityGroupRuleMapToPort(destinationPort) + + sourcePort := d.Get(Arg_SourcePorts + ".0").(map[string]interface{}) + networkSecurityGroupAddRule.SourcePorts = networkSecurityGroupRuleMapToPort(sourcePort) + + networkSecurityGroup, err := nsgClient.AddRule(nsgID, &networkSecurityGroupAddRule) + ruleID := *networkSecurityGroup.ID + if err != nil { + return diag.FromErr(err) + } + _, err = isWaitForIBMPINetworkSecurityGroupRuleAdd(ctx, nsgClient, nsgID, ruleID, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return diag.FromErr(err) + } + d.SetId(fmt.Sprintf("%s/%s/%s", cloudInstanceID, nsgID, ruleID)) + } + + return resourceIBMPINetworkSecurityGroupRuleRead(ctx, d, meta) +} + +func resourceIBMPINetworkSecurityGroupRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sess, err := meta.(conns.ClientSession).IBMPISession() + if err != nil { + return diag.FromErr(err) + } + cloudInstanceID, nsgID, err := splitID(d.Id()) + if err != nil { + return diag.FromErr(err) + } + nsgClient := instance.NewIBMIPINetworkSecurityGroupClient(ctx, sess, cloudInstanceID) + networkSecurityGroup, err := nsgClient.Get(nsgID) + if err != nil { + return diag.FromErr(err) + } + d.Set(Attr_Name, networkSecurityGroup.Name) + d.Set(Attr_CRN, networkSecurityGroup.Crn) + + if len(networkSecurityGroup.Members) > 0 { + members := []map[string]interface{}{} + for _, mbr := range networkSecurityGroup.Members { + mbrMap := networkSecurityGroupMemberToMap(mbr) + members = append(members, mbrMap) + } + d.Set(Attr_Members, members) + } else { + d.Set(Attr_Members, []string{}) + } + + d.Set(Attr_NetworkSecurityGroupID, networkSecurityGroup.ID) + if len(networkSecurityGroup.Rules) > 0 { + rules := []map[string]interface{}{} + for _, rule := range networkSecurityGroup.Rules { + ruleMap := networkSecurityGroupRuleToMap(rule) + rules = append(rules, ruleMap) + } + d.Set(Attr_Rules, rules) + } else { + d.Set(Attr_Rules, []string{}) + } + return nil +} + +func resourceIBMPINetworkSecurityGroupRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + ids, err := flex.IdParts(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + if len(ids) == 3 { + cloudInstanceID := ids[0] + nsgID := ids[1] + ruleID := ids[2] + + sess, err := meta.(conns.ClientSession).IBMPISession() + if err != nil { + return diag.FromErr(err) + } + nsgClient := instance.NewIBMIPINetworkSecurityGroupClient(ctx, sess, cloudInstanceID) + + err = nsgClient.DeleteRule(nsgID, ruleID) + if err != nil { + return diag.FromErr(err) + } + + _, err = isWaitForIBMPINetworkSecurityGroupRuleRemove(ctx, nsgClient, nsgID, ruleID, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return diag.FromErr(err) + } + } + d.SetId("") + return nil +} + +func isWaitForIBMPINetworkSecurityGroupRuleAdd(ctx context.Context, client *instance.IBMPINetworkSecurityGroupClient, id, ruleID string, timeout time.Duration) (interface{}, error) { + + stateConf := &retry.StateChangeConf{ + Pending: []string{State_Pending}, + Target: []string{State_Available}, + Refresh: isIBMPINetworkSecurityGroupRuleAddRefreshFunc(client, id, ruleID), + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: time.Minute, + } + + return stateConf.WaitForStateContext(ctx) +} + +func isIBMPINetworkSecurityGroupRuleAddRefreshFunc(client *instance.IBMPINetworkSecurityGroupClient, id, ruleID string) retry.StateRefreshFunc { + + return func() (interface{}, string, error) { + networkSecurityGroup, err := client.Get(id) + if err != nil { + return nil, "", err + } + + if networkSecurityGroup.Rules != nil { + for _, rule := range networkSecurityGroup.Rules { + if *rule.ID == ruleID { + return networkSecurityGroup, State_Available, nil + } + + } + } + return networkSecurityGroup, State_Pending, nil + } +} + +func isWaitForIBMPINetworkSecurityGroupRuleRemove(ctx context.Context, client *instance.IBMPINetworkSecurityGroupClient, id, ruleID string, timeout time.Duration) (interface{}, error) { + + stateConf := &retry.StateChangeConf{ + Pending: []string{State_Pending}, + Target: []string{State_Removed}, + Refresh: isIBMPINetworkSecurityGroupRuleRemoveRefreshFunc(client, id, ruleID), + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: time.Minute, + } + + return stateConf.WaitForStateContext(ctx) +} + +func isIBMPINetworkSecurityGroupRuleRemoveRefreshFunc(client *instance.IBMPINetworkSecurityGroupClient, id, ruleID string) retry.StateRefreshFunc { + + return func() (interface{}, string, error) { + networkSecurityGroup, err := client.Get(id) + if err != nil { + return nil, "", err + } + + if networkSecurityGroup.Rules != nil { + foundRule := false + for _, rule := range networkSecurityGroup.Rules { + if *rule.ID == ruleID { + foundRule = true + return networkSecurityGroup, State_Pending, nil + } + } + if !foundRule { + return networkSecurityGroup, State_Removed, nil + } + } + return networkSecurityGroup, State_Pending, nil + } +} + +func networkSecurityGroupRuleMapToPort(portMap map[string]interface{}) *models.NetworkSecurityGroupRulePort { + networkSecurityGroupRulePort := models.NetworkSecurityGroupRulePort{} + if portMap[Attr_Maximum] != nil { + networkSecurityGroupRulePort.Maximum = int64(portMap[Attr_Maximum].(int)) + } + if portMap[Attr_Minimum] != nil { + networkSecurityGroupRulePort.Minimum = int64(portMap[Attr_Minimum].(int)) + } + return &networkSecurityGroupRulePort +} + +func networkSecurityGroupRuleMapToRemote(remoteMap map[string]interface{}) *models.NetworkSecurityGroupRuleRemote { + networkSecurityGroupRuleRemote := models.NetworkSecurityGroupRuleRemote{} + if remoteMap[Attr_ID].(string) != "" { + networkSecurityGroupRuleRemote.ID = remoteMap[Attr_ID].(string) + } + networkSecurityGroupRuleRemote.Type = remoteMap[Attr_Type].(string) + return &networkSecurityGroupRuleRemote +} + +func networkSecurityGroupRuleMapToProtocol(protocolMap map[string]interface{}) *models.NetworkSecurityGroupRuleProtocol { + networkSecurityGroupRuleProtocol := models.NetworkSecurityGroupRuleProtocol{} + networkSecurityGroupRuleProtocol.Type = protocolMap[Attr_Type].(string) + + if networkSecurityGroupRuleProtocol.Type == ICMP { + icmpType := protocolMap[Attr_ICMPType].(string) + networkSecurityGroupRuleProtocol.IcmpType = &icmpType + } else if networkSecurityGroupRuleProtocol.Type == TCP { + tcpMaps := protocolMap[Attr_TCPFlags].([]interface{}) + networkSecurityGroupRuleProtocolTCPFlagArray := []*models.NetworkSecurityGroupRuleProtocolTCPFlag{} + for _, tcpMap := range tcpMaps { + flag := tcpMap.(map[string]interface{}) + networkSecurityGroupRuleProtocolTCPFlag := models.NetworkSecurityGroupRuleProtocolTCPFlag{} + networkSecurityGroupRuleProtocolTCPFlag.Flag = flag[Attr_Flag].(string) + networkSecurityGroupRuleProtocolTCPFlagArray = append(networkSecurityGroupRuleProtocolTCPFlagArray, &networkSecurityGroupRuleProtocolTCPFlag) + } + networkSecurityGroupRuleProtocol.TCPFlags = networkSecurityGroupRuleProtocolTCPFlagArray + } + + return &networkSecurityGroupRuleProtocol +} diff --git a/ibm/service/power/resource_ibm_pi_network_security_group_rule_test.go b/ibm/service/power/resource_ibm_pi_network_security_group_rule_test.go new file mode 100644 index 0000000000..f6234b2abf --- /dev/null +++ b/ibm/service/power/resource_ibm_pi_network_security_group_rule_test.go @@ -0,0 +1,191 @@ +// Copyright IBM Corp. 2024 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package power_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/IBM-Cloud/power-go-client/clients/instance" + acc "github.com/IBM-Cloud/terraform-provider-ibm/ibm/acctest" + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/conns" + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/service/power" +) + +func TestAccIBMPINetworkSecurityGroupRuleBasic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMPINetworkSecurityGroupRuleConfigAddRule(), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMPINetworkSecurityGroupRuleExists("ibm_pi_network_security_group_rule.network_security_group_rule"), + resource.TestCheckResourceAttrSet("ibm_pi_network_security_group_rule.network_security_group_rule", power.Arg_NetworkSecurityGroupID), + ), + }, + }, + }) +} + +func TestAccIBMPINetworkSecurityGroupRuleTCP(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMPINetworkSecurityGroupRuleConfigAddRuleTCP(), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMPINetworkSecurityGroupRuleExists("ibm_pi_network_security_group_rule.network_security_group_rule"), + resource.TestCheckResourceAttrSet("ibm_pi_network_security_group_rule.network_security_group_rule", power.Arg_NetworkSecurityGroupID), + ), + }, + }, + }) +} + +func TestAccIBMPINetworkSecurityGroupRuleRemove(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMPINetworkSecurityGroupRuleConfigRemoveRule(), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMPINetworkSecurityGroupRuleRemoved("ibm_pi_network_security_group_rule.network_security_group_rule", acc.Pi_network_security_group_rule_id), + resource.TestCheckResourceAttrSet("ibm_pi_network_security_group_rule.network_security_group_rule", power.Arg_NetworkSecurityGroupID), + ), + }, + }, + }) +} + +func testAccCheckIBMPINetworkSecurityGroupRuleConfigAddRule() string { + return fmt.Sprintf(` + resource "ibm_pi_network_security_group_rule" "network_security_group_rule" { + pi_cloud_instance_id = "%[1]s" + pi_network_security_group_id = "%[2]s" + pi_action = "allow" + pi_protocol { + type = "all" + } + pi_remote { + id = "%[3]s" + type = "%[4]s" + } + }`, acc.Pi_cloud_instance_id, acc.Pi_network_security_group_id, acc.Pi_remote_id, acc.Pi_remote_type) +} + +func testAccCheckIBMPINetworkSecurityGroupRuleConfigAddRuleTCP() string { + return fmt.Sprintf(` + resource "ibm_pi_network_security_group_rule" "network_security_group_rule" { + pi_cloud_instance_id = "%[1]s" + pi_network_security_group_id = "%[2]s" + pi_action = "allow" + pi_destination_ports { + minimum = 1200 + maximum = 37466 + } + pi_source_ports { + minimum = 1000 + maximum = 19500 + } + pi_protocol { + tcp_flags { + flag = "ack" + } + tcp_flags { + flag = "syn" + } + tcp_flags { + flag = "psh" + } + type = "tcp" + } + pi_remote { + id = "%[3]s" + type = "%[4]s" + } + }`, acc.Pi_cloud_instance_id, acc.Pi_network_security_group_id, acc.Pi_remote_id, acc.Pi_remote_type) +} + +func testAccCheckIBMPINetworkSecurityGroupRuleConfigRemoveRule() string { + return fmt.Sprintf(` + resource "ibm_pi_network_security_group_rule" "network_security_group_rule" { + pi_cloud_instance_id = "%[1]s" + pi_network_security_group_id = "%[2]s" + pi_network_security_group_rule_id = "%[3]s" + }`, acc.Pi_cloud_instance_id, acc.Pi_network_security_group_id, acc.Pi_network_security_group_rule_id) +} + +func testAccCheckIBMPINetworkSecurityGroupRuleExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return errors.New("No Record ID is set") + } + sess, err := acc.TestAccProvider.Meta().(conns.ClientSession).IBMPISession() + if err != nil { + return err + } + cloudInstanceID, nsgID, err := splitID(rs.Primary.ID) + if err != nil { + return err + } + nsgClient := instance.NewIBMIPINetworkSecurityGroupClient(context.Background(), sess, cloudInstanceID) + _, err = nsgClient.Get(nsgID) + if err != nil { + return err + } + return nil + } +} + +func testAccCheckIBMPINetworkSecurityGroupRuleRemoved(n string, ruleID string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return errors.New("No Record ID is set") + } + sess, err := acc.TestAccProvider.Meta().(conns.ClientSession).IBMPISession() + if err != nil { + return err + } + cloudInstanceID, nsgID, err := splitID(rs.Primary.ID) + if err != nil { + return err + } + nsgClient := instance.NewIBMIPINetworkSecurityGroupClient(context.Background(), sess, cloudInstanceID) + networkSecurityGroup, err := nsgClient.Get(nsgID) + if err != nil { + return err + } + foundRule := false + if networkSecurityGroup.Rules != nil { + for _, rule := range networkSecurityGroup.Rules { + if *rule.ID == ruleID { + foundRule = true + break + } + } + } + if foundRule { + return fmt.Errorf("NSG rule still exists") + } + return nil + } +} diff --git a/website/docs/r/pi_network_security_group_rule.html.markdown b/website/docs/r/pi_network_security_group_rule.html.markdown new file mode 100644 index 0000000000..376789a47b --- /dev/null +++ b/website/docs/r/pi_network_security_group_rule.html.markdown @@ -0,0 +1,136 @@ +--- +layout: "ibm" +page_title: "IBM : ibm_pi_network_security_group_rule" +description: |- + Manages pi_network_security_group_rule. +subcategory: "Power Systems" +--- + +# ibm_pi_network_security_group_rule + +Add or remove a network security group rule. + +## Example Usage + +```terraform + resource "ibm_pi_network_security_group_rule" "network_security_group_rule" { + pi_cloud_instance_id = "" + pi_network_security_group_id = "" + pi_action = "allow" + pi_destination_ports { + minimum = 1200 + maximum = 37466 + } + pi_source_ports { + minimum = 1000 + maximum = 19500 + } + pi_protocol { + tcp_flags { + flag = "ack" + } + tcp_flags { + flag = "syn" + } + tcp_flags { + flag = "psh" + } + type = "tcp" + } + pi_remote { + id = "" + type = "network-security-group" + } + } +``` + +### Notes + +- Please find [supported Regions](https://cloud.ibm.com/apidocs/power-cloud#endpoint) for endpoints. +- If a Power cloud instance is provisioned at `lon04`, The provider level attributes should be as follows: + - `region` - `lon` + - `zone` - `lon04` + +Example usage: + + ```terraform + provider "ibm" { + region = "lon" + zone = "lon04" + } + ``` + +## Argument Reference + +Review the argument references that you can specify for your resource. + +- `pi_action` - (Optional, String) The action to take if the rule matches network traffic. Supported values are: `allow`, `deny`. Required if `pi_network_security_group_rule_id` is not provided. +- `pi_cloud_instance_id` - (Required, String) The GUID of the service instance associated with an account. +- `pi_destination_port` - (Optional, List) The list of destination port. + + Nested schema for `pi_destination_port`: + - `maximum` - (Optional, Int) The end of the port range, if applicable. If the value is not present then the default value of 65535 will be the maximum port number. + - `minimum` - (Optional, Int) The start of the port range, if applicable. If the value is not present then the default value of 1 will be the minimum port number. +- `pi_network_security_group_id` - (Required, String) The unique identifier of the network security group. +- `pi_network_security_group_rule_id` - (Optional, String) The network security group rule id to remove. Required if none of the other optional fields are provided. +- `pi_protocol` - (Optional, List) The list of protocol. Required if `pi_network_security_group_rule_id` is not provided. + + Nested schema for `pi_protocol`: + - `icmp_type` - (Optional, String) If icmp type, a ICMP packet type affected by ICMP rules and if not present then all types are matched. Supported values are: `all`, `destination-unreach`, `echo`, `echo-reply`, `source-quench`, `time-exceeded`. + - `tcp_flags` - (Optional, String) If tcp type, the list of TCP flags and if not present then all flags are matched. Supported values are: `syn`, `ack`, `fin`, `rst`, `urg`, `psh`, `wnd`, `chk`, `seq`. + - `type` - (Required, String) The protocol of the network traffic. Supported values are: `icmp`, `tcp`, `udp`, `all`. +- `pi_remote` - (Optional, List) List of remote. Required if `pi_network_security_group_rule_id` is not provided. + + Nested schema for `pi_remote`: + - `id` - (Optional, String) The id of the remote network address group or network security group the rules apply to. Not required for default-network-address-group. + - `type` - (Optional, String) The type of remote group the rules apply to. Supported values are: `network-security-group`, `network-address-group`, `default-network-address-group`. +- `pi_source_port` - (Optional, List) List of source port + + Nested schema for `pi_source_port`: + - `maximum` - (Optional, Int) The end of the port range, if applicable. If the value is not present then the default value of 65535 will be the maximum port number. + - `minimum` - (Optional, Int) The start of the port range, if applicable. If the value is not present then the default value of 1 will be the minimum port number. + +- `pi_name` - (Optional, String) The name of the network security group rule. Required if `pi_network_security_group_rule_id` is not provided. + +## Attribute Reference + +In addition to all argument reference list, you can access the following attribute reference after your resource is created. + +- `crn` - (String) The network security group's crn. + +- `members` - (List) The list of IPv4 addresses and\or network interfaces in the network security group. + + Nested schema for `members`: + - `id` - (String) The id of the member in a network security group. + - `mac_address` - (String) The mac address of a network interface included if the type is `network-interface`. + - `target` - (String) If `ipv4-address` type, then IPv4 address or if `network-interface` type, then network interface id. + - `type` - (String) The type of member. Supported values are: `ipv4-address`, `network-interface`. + +- `network_security_group_id` -(String) The unique identifier of the network security group. +- `rules` - (List) The list of rules in the network security group. + + Nested schema for `rules`: + - `action` - (String) The action to take if the rule matches network traffic. Supported values are: `allow`, `deny`. + - `destination_port` - (List) The list of destination port. + + Nested schema for `destination_port`: + - `maximum` - (Int) The end of the port range, if applicable. If the value is not present then the default value of 65535 will be the maximum port number. + - `minimum` - (Int) The start of the port range, if applicable. If the value is not present then the default value of 1 will be the minimum port number. + - `id` - (String) The id of the rule in a network security group. + - `name` - (String) The unique name of the network security group rule. + - `protocol` - (List) The list of protocol. + + Nested schema for `protocol`: + - `icmp_type` - (String) If icmp type, a ICMP packet type affected by ICMP rules and if not present then all types are matched. Supported values are: `all`, `destination-unreach`, `echo`, `echo-reply`, `source-quench`, `time-exceeded`. + - `tcp_flags` - (String) If tcp type, the list of TCP flags and if not present then all flags are matched. Supported values are: `syn`, `ack`, `fin`, `rst`, `urg`, `psh`, `wnd`, `chk`, `seq`. + - `type` - (String) The protocol of the network traffic. Supported values are: `icmp`, `tcp`, `udp`, `all`. + - `remote` - (List) List of remote. + + Nested schema for `remote`: + - `id` - (String) The id of the remote network address group or network security group the rules apply to. Not required for default-network-address-group. + - `type` - (String) The type of remote group the rules apply to. Supported values are: `network-security-group`, `network-address-group`, `default-network-address-group`. + - `source_port` - (List) List of source port + + Nested schema for `source_port`: + - `maximum` - (Int) The end of the port range, if applicable. If the value is not present then the default value of 65535 will be the maximum port number. + - `minimum` - (Int) The start of the port range, if applicable. If the value is not present then the default value of 1 will be the minimum port number.