diff --git a/cmd/examples/network/main.go b/cmd/examples/network/main.go index e443b0a..23d996d 100644 --- a/cmd/examples/network/main.go +++ b/cmd/examples/network/main.go @@ -5,410 +5,457 @@ import ( "fmt" "log" "os" + "time" "github.com/MagaluCloud/mgc-sdk-go/client" "github.com/MagaluCloud/mgc-sdk-go/helpers" "github.com/MagaluCloud/mgc-sdk-go/network" ) +const ( + defaultZone = "a" + defaultTimeout = 30 * time.Second +) + func main() { - ExampleListVPCs() - ExamplePublicIPs() - ExampleSecurityGroups() - ExampleSecurityGroupRules() - ExampleSubnetPools() - ExampleSubnets() - ExamplePorts() - id := ExampleCreateVPC() - ExampleManageVPC(id) - ExampleManageSubnets(id) - ExampleManagePorts(id) - ExampleDeleteVPC(id) -} - -func ExampleListVPCs() { + networkClient := createNetworkClient() + + fmt.Println("\n=== VPC Examples ===") + demoVPCOperations(networkClient) + + fmt.Println("\n=== Subnet Examples ===") + demoSubnetOperations(networkClient) + + fmt.Println("\n=== Subnet Pool Examples ===") + demoSubnetPoolOperations(networkClient) + + fmt.Println("\n=== Security Group Examples ===") + demoSecurityGroupOperations(networkClient) + + fmt.Println("\n=== Security Group Rule Examples ===") + demoSecurityGroupRuleOperations(networkClient) + + fmt.Println("\n=== Public IP Examples ===") + demoPublicIPOperations(networkClient) + + fmt.Println("\n=== Port Examples ===") + demoPortOperations(networkClient) +} + +func createNetworkClient() *network.NetworkClient { apiToken := os.Getenv("MGC_API_TOKEN") if apiToken == "" { log.Fatal("MGC_API_TOKEN environment variable is not set") } c := client.NewMgcClient(apiToken) - networkClient := network.New(c) + return network.New(c) +} + +func getContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), defaultTimeout) +} + +func demoVPCOperations(networkClient *network.NetworkClient) { + listVPCs(networkClient) + + vpcID := createVPC(networkClient) + getVPCDetails(networkClient, vpcID) + updateVPC(networkClient, vpcID) + deleteVPC(networkClient, vpcID) +} - // List VPCs with pagination and expansion - vpcs, err := networkClient.VPCs().List(context.Background()) +func listVPCs(networkClient *network.NetworkClient) { + ctx, cancel := getContext() + defer cancel() + vpcs, err := networkClient.VPCs().List(ctx) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to list VPCs: %v", err) } - // Print VPC details + fmt.Printf("Found %d VPCs\n", len(vpcs)) for _, vpc := range vpcs { fmt.Printf("VPC: %s (ID: %s)\n", *vpc.Name, *vpc.ID) fmt.Printf(" Status: %s\n", vpc.Status) fmt.Printf(" External Network: %s\n", *vpc.ExternalNetwork) fmt.Printf(" Created At: %s\n", vpc.CreatedAt) fmt.Printf(" Subnets: %v\n", vpc.Subnets) - fmt.Printf(" Security Groups: %v\n", vpc.SecurityGroups) + fmt.Printf(" Security Groups: %v\n\n", vpc.SecurityGroups) } } -func ExampleCreateVPC() string { - apiToken := os.Getenv("MGC_API_TOKEN") - if apiToken == "" { - log.Fatal("MGC_API_TOKEN environment variable is not set") - } - c := client.NewMgcClient(apiToken) - networkClient := network.New(c) +func createVPC(networkClient *network.NetworkClient) string { + ctx, cancel := getContext() + defer cancel() - // Create a new VPC createReq := network.CreateVPCRequest{ - Name: "my-test-vpc", - Description: helpers.StrPtr("Test VPC created via SDK"), + Name: "example-vpc", + Description: helpers.StrPtr("VPC created via SDK example"), } - id, err := networkClient.VPCs().Create(context.Background(), createReq) + id, err := networkClient.VPCs().Create(ctx, createReq) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to create VPC: %v", err) } fmt.Printf("Created VPC with ID: %s\n", id) return id } -func ExampleManageVPC(id string) { - apiToken := os.Getenv("MGC_API_TOKEN") - if apiToken == "" { - log.Fatal("MGC_API_TOKEN environment variable is not set") - } - c := client.NewMgcClient(apiToken) - networkClient := network.New(c) - ctx := context.Background() +func getVPCDetails(networkClient *network.NetworkClient, vpcID string) { + ctx, cancel := getContext() + defer cancel() - // Get VPC details - vpc, err := networkClient.VPCs().Get(ctx, id) + vpc, err := networkClient.VPCs().Get(ctx, vpcID) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to get VPC details: %v", err) } - fmt.Printf("VPC: %s (ID: %s)\n", *vpc.Name, *vpc.ID) - - // Rename the VPC - if err := networkClient.VPCs().Rename(ctx, *vpc.ID, "new-vpc-name"); err != nil { - log.Fatal(err) - } - fmt.Println("VPC renamed successfully") + fmt.Printf("VPC Details for %s:\n", vpcID) + fmt.Printf(" Name: %s\n", *vpc.Name) + fmt.Printf(" Status: %s\n", vpc.Status) + fmt.Printf(" External Network: %s\n", *vpc.ExternalNetwork) + fmt.Printf(" Created At: %s\n\n", vpc.CreatedAt) } -func ExampleManageSubnets(vpcID string) { - apiToken := os.Getenv("MGC_API_TOKEN") - if apiToken == "" { - log.Fatal("MGC_API_TOKEN environment variable is not set") - } - c := client.NewMgcClient(apiToken) - networkClient := network.New(c) - ctx := context.Background() +func updateVPC(networkClient *network.NetworkClient, vpcID string) { + ctx, cancel := getContext() + defer cancel() - // List subnets - subnets, err := networkClient.VPCs().ListSubnets(ctx, vpcID) - if err != nil { - log.Fatal(err) + newName := "updated-example-vpc" + if err := networkClient.VPCs().Rename(ctx, vpcID, newName); err != nil { + log.Fatalf("Failed to update VPC: %v", err) } - fmt.Printf("Subnets in VPC %s:\n", vpcID) - for _, subnet := range subnets { - fmt.Printf(" Subnet: %s (CIDR: %s)\n", subnet.ID, subnet.CIDRBlock) - } + fmt.Printf("VPC %s renamed to '%s'\n", vpcID, newName) +} - // Create a new subnet - createSubnetReq := network.SubnetCreateRequest{ - Name: "my-subnet", - CIDRBlock: "192.168.1.0/24", - Description: helpers.StrPtr("Test subnet created via SDK"), - } +func deleteVPC(networkClient *network.NetworkClient, vpcID string) { + ctx, cancel := getContext() + defer cancel() - subnetID, err := networkClient.VPCs().CreateSubnet(ctx, vpcID, createSubnetReq) - if err != nil { - log.Fatal(err) + if err := networkClient.VPCs().Delete(ctx, vpcID); err != nil { + log.Fatalf("Failed to delete VPC: %v", err) } - fmt.Printf("Created subnet with ID: %s\n", subnetID) + fmt.Printf("VPC %s deleted successfully\n", vpcID) } -func ExampleManagePorts(vpcID string) { - apiToken := os.Getenv("MGC_API_TOKEN") - if apiToken == "" { - log.Fatal("MGC_API_TOKEN environment variable is not set") - } - c := client.NewMgcClient(apiToken) - networkClient := network.New(c) - ctx := context.Background() +func demoSubnetOperations(networkClient *network.NetworkClient) { + vpcID := createVPC(networkClient) + defer cleanupVPC(networkClient, vpcID) - // List ports - ports, err := networkClient.VPCs().ListPorts(ctx, vpcID, true, network.ListOptions{}) - if err != nil { - log.Fatal(err) - } + subnetID := createSubnet(networkClient, vpcID) - fmt.Printf("Ports in VPC %s:\n", vpcID) - if ports.Ports != nil { - for _, port := range *ports.Ports { - fmt.Printf(" Port: %s\n", *port.ID) - } + listSubnets(networkClient, vpcID) + + getSubnetDetails(networkClient, subnetID) + + updateSubnet(networkClient, subnetID) + + deleteSubnet(networkClient, subnetID) +} + +func createSubnet(networkClient *network.NetworkClient, vpcID string) string { + ctx, cancel := getContext() + defer cancel() + + createReq := network.SubnetCreateRequest{ + Name: "example-subnet", + CIDRBlock: "192.168.1.0/24", + Description: helpers.StrPtr("Subnet created via SDK example"), } - // Create a new port - createPortReq := network.PortCreateRequest{ - Name: "my-port", - HasPIP: helpers.BoolPtr(true), - HasSG: helpers.BoolPtr(true), + options := network.SubnetCreateOptions{ + Zone: helpers.StrPtr(defaultZone), } - portID, err := networkClient.VPCs().CreatePort(ctx, vpcID, createPortReq) + subnetID, err := networkClient.VPCs().CreateSubnet(ctx, vpcID, createReq, options) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to create subnet: %v", err) } - fmt.Printf("Created port with ID: %s\n", portID) + fmt.Printf("Created subnet %s in VPC %s\n", subnetID, vpcID) + return subnetID } -func ExampleDeleteVPC(id string) { - apiToken := os.Getenv("MGC_API_TOKEN") - if apiToken == "" { - log.Fatal("MGC_API_TOKEN environment variable is not set") - } - c := client.NewMgcClient(apiToken) - networkClient := network.New(c) +func listSubnets(networkClient *network.NetworkClient, vpcID string) { + ctx, cancel := getContext() + defer cancel() - if err := networkClient.VPCs().Delete(context.Background(), id); err != nil { - log.Fatal(err) + subnets, err := networkClient.VPCs().ListSubnets(ctx, vpcID) + if err != nil { + log.Fatalf("Failed to list subnets: %v", err) } - fmt.Println("VPC deleted successfully") -} - -func ExampleSubnets() { - apiToken := os.Getenv("MGC_API_TOKEN") - if apiToken == "" { - log.Fatal("MGC_API_TOKEN environment variable is not set") + fmt.Printf("Subnets in VPC %s:\n", vpcID) + for _, subnet := range subnets { + fmt.Printf(" Subnet: %s (CIDR: %s)\n", subnet.ID, subnet.CIDRBlock) + fmt.Printf(" Name: %s\n", *subnet.Name) + fmt.Printf(" Zone: %s\n\n", subnet.Zone) } - c := client.NewMgcClient(apiToken) - networkClient := network.New(c) - ctx := context.Background() +} - // Example subnet ID - replace with an actual subnet ID - subnetID := "030d0e77-e9f9-4af8-99db-067eba6826c0" +func getSubnetDetails(networkClient *network.NetworkClient, subnetID string) { + ctx, cancel := getContext() + defer cancel() - // Get subnet details subnet, err := networkClient.Subnets().Get(ctx, subnetID) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to get subnet details: %v", err) } - fmt.Printf("Subnet Details:\n") - fmt.Printf(" ID: %s\n", subnet.ID) + fmt.Printf("Subnet Details for %s:\n", subnetID) fmt.Printf(" Name: %s\n", *subnet.Name) fmt.Printf(" CIDR Block: %s\n", subnet.CIDRBlock) fmt.Printf(" Gateway IP: %s\n", subnet.GatewayIP) fmt.Printf(" IP Version: %s\n", subnet.IPVersion) fmt.Printf(" Zone: %s\n", subnet.Zone) - fmt.Printf(" DNS Nameservers: %v\n", subnet.DNSNameservers) + fmt.Printf(" DNS Nameservers: %v\n\n", subnet.DNSNameservers) +} + +func updateSubnet(networkClient *network.NetworkClient, subnetID string) { + ctx, cancel := getContext() + defer cancel() - // Update subnet DNS nameservers updateReq := network.SubnetPatchRequest{ DNSNameservers: &[]string{"8.8.8.8", "8.8.4.4"}, } updatedSubnet, err := networkClient.Subnets().Update(ctx, subnetID, updateReq) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to update subnet: %v", err) } - fmt.Printf("Updated subnet ID: %s\n", updatedSubnet.ID) - // Delete subnet + fmt.Printf("Updated subnet %s with new DNS servers\n", updatedSubnet.ID) +} + +func deleteSubnet(networkClient *network.NetworkClient, subnetID string) { + ctx, cancel := getContext() + defer cancel() + if err := networkClient.Subnets().Delete(ctx, subnetID); err != nil { - log.Fatal(err) + log.Fatalf("Failed to delete subnet: %v", err) } - fmt.Println("Subnet deleted successfully") + + fmt.Printf("Subnet %s deleted successfully\n", subnetID) } -func ExampleSubnetPools() { - apiToken := os.Getenv("MGC_API_TOKEN") - if apiToken == "" { - log.Fatal("MGC_API_TOKEN environment variable is not set") - } - c := client.NewMgcClient(apiToken) - networkClient := network.New(c) - ctx := context.Background() +func demoSubnetPoolOperations(networkClient *network.NetworkClient) { + listSubnetPools(networkClient) + + poolID := createSubnetPool(networkClient) + + getSubnetPoolDetails(networkClient, poolID) + + cidrInfo := bookCIDR(networkClient, poolID) + + unbookCIDR(networkClient, poolID, cidrInfo.CIDR) + + deleteSubnetPool(networkClient, poolID) +} + +func listSubnetPools(networkClient *network.NetworkClient) { + ctx, cancel := getContext() + defer cancel() - // List subnet pools pools, err := networkClient.SubnetPools().List(ctx, network.ListOptions{ Limit: helpers.IntPtr(10), Offset: helpers.IntPtr(0), }) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to list subnet pools: %v", err) } - fmt.Println("Available Subnet Pools:") + fmt.Printf("Found %d subnet pools\n", len(pools)) for _, pool := range pools { fmt.Printf(" ID: %s\n", pool.ID) fmt.Printf(" Name: %s\n", pool.Name) fmt.Printf(" CIDR: %s\n", *pool.CIDR) - fmt.Printf(" Is Default: %v\n", pool.IsDefault) - fmt.Printf(" Description: %s\n\n", *pool.Description) + fmt.Printf(" Is Default: %v\n\n", pool.IsDefault) } +} + +func createSubnetPool(networkClient *network.NetworkClient) string { + ctx, cancel := getContext() + defer cancel() - // Create a new subnet pool createReq := network.CreateSubnetPoolRequest{ - Name: "my-subnet-pool", - Description: "Test subnet pool created via SDK", + Name: "example-subnet-pool", + Description: "Subnet pool created via SDK example", CIDR: helpers.StrPtr("192.168.0.0/16"), } poolID, err := networkClient.SubnetPools().Create(ctx, createReq) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to create subnet pool: %v", err) } + fmt.Printf("Created subnet pool with ID: %s\n", poolID) + return poolID +} + +func getSubnetPoolDetails(networkClient *network.NetworkClient, poolID string) { + ctx, cancel := getContext() + defer cancel() - // Get subnet pool details pool, err := networkClient.SubnetPools().Get(ctx, poolID) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to get subnet pool details: %v", err) } - fmt.Printf("\nSubnet Pool Details:\n") - fmt.Printf(" ID: %s\n", pool.ID) + fmt.Printf("Subnet Pool Details for %s:\n", poolID) fmt.Printf(" Name: %s\n", pool.Name) fmt.Printf(" CIDR: %s\n", *pool.CIDR) fmt.Printf(" IP Version: %d\n", pool.IPVersion) - fmt.Printf(" Created At: %s\n", pool.CreatedAt) + fmt.Printf(" Created At: %s\n\n", pool.CreatedAt) +} + +func bookCIDR(networkClient *network.NetworkClient, poolID string) network.BookCIDRResponse { + ctx, cancel := getContext() + defer cancel() - // Book a CIDR from the pool bookReq := network.BookCIDRRequest{ - Mask: helpers.IntPtr(24), // Request a /24 subnet + Mask: helpers.IntPtr(24), } bookedCIDR, err := networkClient.SubnetPools().BookCIDR(ctx, poolID, bookReq) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to book CIDR: %v", err) } - fmt.Printf("\nBooked CIDR: %s\n", bookedCIDR.CIDR) - // Unbook the CIDR + fmt.Printf("Booked CIDR: %s from pool %s\n", bookedCIDR.CIDR, poolID) + return *bookedCIDR +} + +func unbookCIDR(networkClient *network.NetworkClient, poolID, cidr string) { + ctx, cancel := getContext() + defer cancel() + unbookReq := network.UnbookCIDRRequest{ - CIDR: bookedCIDR.CIDR, + CIDR: cidr, } if err := networkClient.SubnetPools().UnbookCIDR(ctx, poolID, unbookReq); err != nil { - log.Fatal(err) + log.Fatalf("Failed to unbook CIDR: %v", err) } - fmt.Printf("Unbooked CIDR: %s\n", bookedCIDR.CIDR) - // Delete the subnet pool + fmt.Printf("Unbooked CIDR: %s from pool %s\n", cidr, poolID) +} + +func deleteSubnetPool(networkClient *network.NetworkClient, poolID string) { + ctx, cancel := getContext() + defer cancel() + if err := networkClient.SubnetPools().Delete(ctx, poolID); err != nil { - log.Fatal(err) + log.Fatalf("Failed to delete subnet pool: %v", err) } - fmt.Println("Subnet pool deleted successfully") + + fmt.Printf("Subnet pool %s deleted successfully\n", poolID) } -func ExampleSecurityGroups() { - apiToken := os.Getenv("MGC_API_TOKEN") - if apiToken == "" { - log.Fatal("MGC_API_TOKEN environment variable is not set") - } - c := client.NewMgcClient(apiToken) - networkClient := network.New(c) - ctx := context.Background() +func demoSecurityGroupOperations(networkClient *network.NetworkClient) { + listSecurityGroups(networkClient) + + sgID := createSecurityGroup(networkClient) + + getSecurityGroupDetails(networkClient, sgID) + + deleteSecurityGroup(networkClient, sgID) +} + +func listSecurityGroups(networkClient *network.NetworkClient) { + ctx, cancel := getContext() + defer cancel() - // List security groups securityGroups, err := networkClient.SecurityGroups().List(ctx) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to list security groups: %v", err) } - fmt.Println("Available Security Groups:") + fmt.Printf("Found %d security groups\n", len(securityGroups)) for _, sg := range securityGroups { fmt.Printf(" ID: %s\n", *sg.ID) fmt.Printf(" Name: %s\n", *sg.Name) - fmt.Printf(" Description: %s\n", *sg.Description) fmt.Printf(" VPC ID: %s\n", *sg.VPCID) - fmt.Printf(" Status: %s\n", sg.Status) - fmt.Printf(" Is Default: %v\n", sg.IsDefault) - fmt.Printf(" Created At: %s\n\n", sg.CreatedAt) + fmt.Printf(" Is Default: %v\n\n", sg.IsDefault) } +} + +func createSecurityGroup(networkClient *network.NetworkClient) string { + ctx, cancel := getContext() + defer cancel() - // Create a new security group createReq := network.SecurityGroupCreateRequest{ - Name: "my-security-group", - Description: helpers.StrPtr("Test security group created via SDK"), + Name: "example-security-group", + Description: helpers.StrPtr("Security group created via SDK example"), } sgID, err := networkClient.SecurityGroups().Create(ctx, createReq) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to create security group: %v", err) } + fmt.Printf("Created security group with ID: %s\n", sgID) + return sgID +} + +func getSecurityGroupDetails(networkClient *network.NetworkClient, sgID string) { + ctx, cancel := getContext() + defer cancel() - // Get security group details sg, err := networkClient.SecurityGroups().Get(ctx, sgID) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to get security group details: %v", err) } - fmt.Printf("\nSecurity Group Details:\n") - fmt.Printf(" ID: %s\n", *sg.ID) + fmt.Printf("Security Group Details for %s:\n", sgID) fmt.Printf(" Name: %s\n", *sg.Name) fmt.Printf(" Description: %s\n", *sg.Description) fmt.Printf(" VPC ID: %s\n", *sg.VPCID) fmt.Printf(" Status: %s\n", sg.Status) - // Print security rules if any exist - if sg.Rules != nil { - fmt.Println("\nSecurity Rules:") + if sg.Rules != nil && len(*sg.Rules) > 0 { + fmt.Println(" Rules:") for _, rule := range *sg.Rules { - fmt.Printf(" Direction: %s\n", *rule.Direction) - fmt.Printf(" Protocol: %s\n", *rule.Protocol) - if rule.PortRangeMin != nil { - fmt.Printf(" Port Range Min: %d\n", *rule.PortRangeMin) - } - if rule.PortRangeMax != nil { - fmt.Printf(" Port Range Max: %d\n", *rule.PortRangeMax) - } - fmt.Printf(" Remote IP Prefix: %s\n", *rule.RemoteIPPrefix) - fmt.Printf(" Description: %s\n\n", *rule.Description) + fmt.Printf(" Direction: %s, Protocol: %s\n", *rule.Direction, *rule.Protocol) } + } else { + fmt.Println(" No rules defined") } + fmt.Println() +} + +func deleteSecurityGroup(networkClient *network.NetworkClient, sgID string) { + ctx, cancel := getContext() + defer cancel() - // Delete the security group if err := networkClient.SecurityGroups().Delete(ctx, sgID); err != nil { - log.Fatal(err) + log.Fatalf("Failed to delete security group: %v", err) } - fmt.Println("Security group deleted successfully") + + fmt.Printf("Security group %s deleted successfully\n", sgID) } -func ExampleSecurityGroupRules() { - apiToken := os.Getenv("MGC_API_TOKEN") - if apiToken == "" { - log.Fatal("MGC_API_TOKEN environment variable is not set") - } - c := client.NewMgcClient(apiToken) - networkClient := network.New(c) - ctx := context.Background() +func demoSecurityGroupRuleOperations(networkClient *network.NetworkClient) { + sgID := createSecurityGroup(networkClient) + defer deleteSecurityGroup(networkClient, sgID) - // First, create a security group for the rules - sgCreateReq := network.SecurityGroupCreateRequest{ - Name: "test-sg-with-rules", - Description: helpers.StrPtr("Security group for testing rules"), - } + sshRuleID := createSSHSecurityRule(networkClient, sgID) + httpsRuleID := createHTTPSSecurityRule(networkClient, sgID) - sgID, err := networkClient.SecurityGroups().Create(ctx, sgCreateReq) - if err != nil { - log.Fatal(err) - } - fmt.Printf("Created security group with ID: %s\n", sgID) + listSecurityGroupRules(networkClient, sgID) + + getSecurityGroupRuleDetails(networkClient, sshRuleID) + + deleteSecurityGroupRule(networkClient, sshRuleID) + deleteSecurityGroupRule(networkClient, httpsRuleID) +} + +func createSSHSecurityRule(networkClient *network.NetworkClient, sgID string) string { + ctx, cancel := getContext() + defer cancel() - // Create an inbound rule allowing SSH access sshRule := network.RuleCreateRequest{ Direction: helpers.StrPtr("ingress"), PortRangeMin: helpers.IntPtr(22), @@ -419,13 +466,19 @@ func ExampleSecurityGroupRules() { Description: helpers.StrPtr("Allow SSH access"), } - sshRuleID, err := networkClient.Rules().Create(ctx, sgID, sshRule) + ruleID, err := networkClient.Rules().Create(ctx, sgID, sshRule) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to create SSH security rule: %v", err) } - fmt.Printf("Created SSH rule with ID: %s\n", sshRuleID) - // Create an inbound rule allowing HTTPS access + fmt.Printf("Created SSH rule with ID: %s in security group %s\n", ruleID, sgID) + return ruleID +} + +func createHTTPSSecurityRule(networkClient *network.NetworkClient, sgID string) string { + ctx, cancel := getContext() + defer cancel() + httpsRule := network.RuleCreateRequest{ Direction: helpers.StrPtr("ingress"), PortRangeMin: helpers.IntPtr(443), @@ -436,266 +489,298 @@ func ExampleSecurityGroupRules() { Description: helpers.StrPtr("Allow HTTPS access"), } - httpsRuleID, err := networkClient.Rules().Create(ctx, sgID, httpsRule) + ruleID, err := networkClient.Rules().Create(ctx, sgID, httpsRule) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to create HTTPS security rule: %v", err) } - fmt.Printf("Created HTTPS rule with ID: %s\n", httpsRuleID) - // List all rules in the security group + fmt.Printf("Created HTTPS rule with ID: %s in security group %s\n", ruleID, sgID) + return ruleID +} + +func listSecurityGroupRules(networkClient *network.NetworkClient, sgID string) { + ctx, cancel := getContext() + defer cancel() + rules, err := networkClient.Rules().List(ctx, sgID) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to list security group rules: %v", err) } - fmt.Printf("\nRules in security group %s:\n", sgID) + fmt.Printf("Rules in security group %s:\n", sgID) for _, rule := range rules { - fmt.Printf("Rule ID: %s\n", *rule.ID) - fmt.Printf(" Direction: %s\n", *rule.Direction) - fmt.Printf(" Protocol: %s\n", *rule.Protocol) + fmt.Printf(" Rule ID: %s\n", *rule.ID) + fmt.Printf(" Direction: %s\n", *rule.Direction) + fmt.Printf(" Protocol: %s\n", *rule.Protocol) if rule.PortRangeMin != nil { - fmt.Printf(" Port Range: %d-%d\n", *rule.PortRangeMin, *rule.PortRangeMax) + fmt.Printf(" Port Range: %d-%d\n", *rule.PortRangeMin, *rule.PortRangeMax) } - fmt.Printf(" Remote IP Prefix: %s\n", *rule.RemoteIPPrefix) - fmt.Printf(" Description: %s\n", *rule.Description) - fmt.Printf(" Status: %s\n\n", rule.Status) + fmt.Printf(" Remote IP Prefix: %s\n\n", *rule.RemoteIPPrefix) } +} + +func getSecurityGroupRuleDetails(networkClient *network.NetworkClient, ruleID string) { + ctx, cancel := getContext() + defer cancel() - // Get details of a specific rule - ruleDetails, err := networkClient.Rules().Get(ctx, sshRuleID) + rule, err := networkClient.Rules().Get(ctx, ruleID) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to get security group rule details: %v", err) } - fmt.Printf("SSH Rule Details:\n") - fmt.Printf(" ID: %s\n", *ruleDetails.ID) - fmt.Printf(" Direction: %s\n", *ruleDetails.Direction) - fmt.Printf(" Protocol: %s\n", *ruleDetails.Protocol) - fmt.Printf(" Port: %d\n", *ruleDetails.PortRangeMin) - fmt.Printf(" Remote IP Prefix: %s\n", *ruleDetails.RemoteIPPrefix) + fmt.Printf("Security Rule Details for %s:\n", ruleID) + fmt.Printf(" Direction: %s\n", *rule.Direction) + fmt.Printf(" Protocol: %s\n", *rule.Protocol) + if rule.PortRangeMin != nil { + fmt.Printf(" Port Range: %d-%d\n", *rule.PortRangeMin, *rule.PortRangeMax) + } + fmt.Printf(" Remote IP Prefix: %s\n", *rule.RemoteIPPrefix) + fmt.Printf(" Description: %s\n\n", *rule.Description) +} - // Clean up - delete rules and security group - fmt.Println("\nCleaning up resources...") +func deleteSecurityGroupRule(networkClient *network.NetworkClient, ruleID string) { + ctx, cancel := getContext() + defer cancel() - // Delete rules - if err := networkClient.Rules().Delete(ctx, sshRuleID); err != nil { - log.Fatal(err) + if err := networkClient.Rules().Delete(ctx, ruleID); err != nil { + log.Fatalf("Failed to delete security group rule: %v", err) } - fmt.Printf("Deleted SSH rule: %s\n", sshRuleID) - if err := networkClient.Rules().Delete(ctx, httpsRuleID); err != nil { - log.Fatal(err) - } - fmt.Printf("Deleted HTTPS rule: %s\n", httpsRuleID) + fmt.Printf("Security rule %s deleted successfully\n", ruleID) +} - // Delete security group - if err := networkClient.SecurityGroups().Delete(ctx, sgID); err != nil { - log.Fatal(err) - } - fmt.Printf("Deleted security group: %s\n", sgID) +func demoPublicIPOperations(networkClient *network.NetworkClient) { + vpcID := createVPC(networkClient) + defer cleanupVPC(networkClient, vpcID) + + portID := createPort(networkClient, vpcID, "test-port-for-pip", true) + + listPublicIPs(networkClient) + + pipID := createPublicIP(networkClient, vpcID) + + getPublicIPDetails(networkClient, pipID) + + attachPublicIPToPort(networkClient, pipID, portID) + + detachPublicIPFromPort(networkClient, pipID, portID) + + deletePublicIP(networkClient, pipID) } -func ExamplePublicIPs() { - apiToken := os.Getenv("MGC_API_TOKEN") - if apiToken == "" { - log.Fatal("MGC_API_TOKEN environment variable is not set") - } - c := client.NewMgcClient(apiToken) - networkClient := network.New(c) - ctx := context.Background() +func listPublicIPs(networkClient *network.NetworkClient) { + ctx, cancel := getContext() + defer cancel() - // List all public IPs publicIPs, err := networkClient.PublicIPs().List(ctx) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to list public IPs: %v", err) } - fmt.Println("Available Public IPs:") - for _, pip := range publicIPs { + fmt.Printf("Found %d public IPs\n", len(publicIPs)) + for i, pip := range publicIPs { + if i >= 5 { + fmt.Printf("And %d more...\n", len(publicIPs)-5) + break + } fmt.Printf(" ID: %s\n", *pip.ID) fmt.Printf(" Public IP: %s\n", *pip.PublicIP) - fmt.Printf(" VPC ID: %s\n", *pip.VPCID) - fmt.Printf(" Port ID: %s\n", *pip.PortID) - fmt.Printf(" Status: %s\n", *pip.Status) - fmt.Printf(" Created At: %s\n\n", pip.CreatedAt) - } - - // Create a VPC and port for testing public IP operations - vpcReq := network.CreateVPCRequest{ - Name: "test-vpc-for-pip", - Description: helpers.StrPtr("VPC for testing public IP operations"), - } - - vpcID, err := networkClient.VPCs().Create(ctx, vpcReq) - if err != nil { - log.Fatal(err) - } - fmt.Printf("Created VPC with ID: %s\n", vpcID) - - // Create a port in the VPC - portReq := network.PortCreateRequest{ - Name: "test-port-for-pip", - HasPIP: helpers.BoolPtr(true), - HasSG: helpers.BoolPtr(false), + fmt.Printf(" VPC ID: %s\n\n", *pip.VPCID) } +} - portID, err := networkClient.VPCs().CreatePort(ctx, vpcID, portReq) - if err != nil { - log.Fatal(err) - } - fmt.Printf("Created port with ID: %s\n", portID) +func createPublicIP(networkClient *network.NetworkClient, vpcID string) string { + ctx, cancel := getContext() + defer cancel() - // Create a public IP pipReq := network.PublicIPCreateRequest{ - Description: "Test public IP created via SDK", + Description: "Public IP created via SDK example", } pipID, err := networkClient.VPCs().CreatePublicIP(ctx, vpcID, pipReq) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to create public IP: %v", err) } - fmt.Printf("Created public IP with ID: %s\n", pipID) - // Get public IP details + fmt.Printf("Created public IP with ID: %s in VPC %s\n", pipID, vpcID) + return pipID +} + +func getPublicIPDetails(networkClient *network.NetworkClient, pipID string) { + ctx, cancel := getContext() + defer cancel() + pip, err := networkClient.PublicIPs().Get(ctx, pipID) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to get public IP details: %v", err) } - fmt.Printf("\nPublic IP Details:\n") - fmt.Printf(" ID: %s\n", *pip.ID) + fmt.Printf("Public IP Details for %s:\n", pipID) fmt.Printf(" Public IP: %s\n", *pip.PublicIP) fmt.Printf(" Description: %s\n", *pip.Description) fmt.Printf(" Status: %s\n", *pip.Status) + fmt.Printf(" VPC ID: %s\n", *pip.VPCID) + if pip.PortID != nil { + fmt.Printf(" Port ID: %s\n", *pip.PortID) + } else { + fmt.Println(" Not attached to any port") + } + fmt.Println() +} + +func attachPublicIPToPort(networkClient *network.NetworkClient, pipID, portID string) { + ctx, cancel := getContext() + defer cancel() - // Attach public IP to port if err := networkClient.PublicIPs().AttachToPort(ctx, pipID, portID); err != nil { - log.Fatal(err) + log.Fatalf("Failed to attach public IP to port: %v", err) } + fmt.Printf("Attached public IP %s to port %s\n", pipID, portID) +} - // Get updated public IP details - pip, err = networkClient.PublicIPs().Get(ctx, pipID) - if err != nil { - log.Fatal(err) - } - fmt.Printf("Public IP %s is now attached to port %s\n", *pip.PublicIP, *pip.PortID) +func detachPublicIPFromPort(networkClient *network.NetworkClient, pipID, portID string) { + ctx, cancel := getContext() + defer cancel() - // Detach public IP from port if err := networkClient.PublicIPs().DetachFromPort(ctx, pipID, portID); err != nil { - log.Fatal(err) + log.Fatalf("Failed to detach public IP from port: %v", err) } + fmt.Printf("Detached public IP %s from port %s\n", pipID, portID) +} - // Clean up resources - fmt.Println("\nCleaning up resources...") +func deletePublicIP(networkClient *network.NetworkClient, pipID string) { + ctx, cancel := getContext() + defer cancel() - // Delete public IP if err := networkClient.PublicIPs().Delete(ctx, pipID); err != nil { - log.Fatal(err) + log.Fatalf("Failed to delete public IP: %v", err) } - fmt.Printf("Deleted public IP: %s\n", pipID) - // Delete VPC (this will also delete the port) - if err := networkClient.VPCs().Delete(ctx, vpcID); err != nil { - log.Fatal(err) - } - fmt.Printf("Deleted VPC: %s\n", vpcID) + fmt.Printf("Public IP %s deleted successfully\n", pipID) } -func ExamplePorts() { - apiToken := os.Getenv("MGC_API_TOKEN") - if apiToken == "" { - log.Fatal("MGC_API_TOKEN environment variable is not set") - } - c := client.NewMgcClient(apiToken) - networkClient := network.New(c) - ctx := context.Background() +func demoPortOperations(networkClient *network.NetworkClient) { + vpcID := createVPC(networkClient) + defer cleanupVPC(networkClient, vpcID) - // List all ports - ports, err := networkClient.Ports().List(ctx) - if err != nil { - log.Fatal(err) - } - - fmt.Println("Available Ports:") - for _, port := range ports { - fmt.Printf(" ID: %s\n", *port.ID) - fmt.Printf(" Name: %s\n", *port.Name) - fmt.Printf(" VPC ID: %s\n", *port.VPCID) - fmt.Printf(" Security Groups: %v\n", *port.SecurityGroups) - fmt.Printf(" Created At: %s\n", *port.CreatedAt) - - // Print IP addresses - if port.IPAddress != nil { - fmt.Println(" IP Addresses:") - for _, ip := range *port.IPAddress { - fmt.Printf(" %s (Subnet: %s)\n", ip.IPAddress, ip.SubnetID) - } - } + sgID := createSecurityGroup(networkClient) + defer deleteSecurityGroup(networkClient, sgID) - // Print public IPs if any - if port.PublicIP != nil { - fmt.Println(" Public IPs:") - for _, pip := range *port.PublicIP { - fmt.Printf(" %s (ID: %s)\n", *pip.PublicIP, *pip.PublicIPID) - } - } - fmt.Println() + portID := createPort(networkClient, vpcID, "example-port", false) + + getPortDetails(networkClient, portID) + + attachSecurityGroupToPort(networkClient, portID, sgID) + detachSecurityGroupFromPort(networkClient, portID, sgID) + + listPorts(networkClient, vpcID) + + deletePort(networkClient, portID) +} + +func createPort(networkClient *network.NetworkClient, vpcID, name string, hasPIP bool) string { + ctx, cancel := getContext() + defer cancel() + + portReq := network.PortCreateRequest{ + Name: name, + HasPIP: helpers.BoolPtr(hasPIP), + HasSG: helpers.BoolPtr(true), } - // Create a security group for testing - sgCreateReq := network.SecurityGroupCreateRequest{ - Name: "test-sg-for-port", - Description: helpers.StrPtr("Security group for testing port operations"), + options := network.PortCreateOptions{ + Zone: helpers.StrPtr(defaultZone), } - sgID, err := networkClient.SecurityGroups().Create(ctx, sgCreateReq) + portID, err := networkClient.VPCs().CreatePort(ctx, vpcID, portReq, options) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to create port: %v", err) } - fmt.Printf("Created security group with ID: %s\n", sgID) - // Get port details by ID (using first port from list if available) - if ports != nil || len(ports) > 0 { - portDetails, err := networkClient.Ports().Get(ctx, *ports[0].ID) - if err != nil { - log.Fatal(err) - } + fmt.Printf("Created port with ID: %s in VPC %s\n", portID, vpcID) + return portID +} - fmt.Printf("\nPort Details:\n") - fmt.Printf(" ID: %s\n", *portDetails.ID) - fmt.Printf(" Name: %s\n", *portDetails.Name) - fmt.Printf(" Description: %s\n", *portDetails.Description) - fmt.Printf(" VPC ID: %s\n", *portDetails.VPCID) - fmt.Printf(" Security Groups: %v\n", *portDetails.SecurityGroups) +func getPortDetails(networkClient *network.NetworkClient, portID string) { + ctx, cancel := getContext() + defer cancel() - // Attach security group to port - if err := networkClient.Ports().AttachSecurityGroup(ctx, *portDetails.ID, sgID); err != nil { - log.Fatal(err) - } - fmt.Printf("Attached security group %s to port %s\n", sgID, *portDetails.ID) + port, err := networkClient.Ports().Get(ctx, portID) + if err != nil { + log.Fatalf("Failed to get port details: %v", err) + } - // Get updated port details - portDetails, err = networkClient.Ports().Get(ctx, *portDetails.ID) - if err != nil { - log.Fatal(err) + fmt.Printf("Port Details for %s:\n", portID) + fmt.Printf(" Name: %s\n", *port.Name) + fmt.Printf(" VPC ID: %s\n", *port.VPCID) + fmt.Printf(" Security Groups: %v\n", *port.SecurityGroups) + if port.IPAddress != nil { + fmt.Println(" IP Addresses:") + for _, ip := range *port.IPAddress { + fmt.Printf(" %s (Subnet: %s)\n", ip.IPAddress, ip.SubnetID) + } + } + if port.PublicIP != nil { + fmt.Println(" Public IPs:") + for _, pip := range *port.PublicIP { + fmt.Printf(" %s (ID: %s)\n", *pip.PublicIP, *pip.PublicIPID) } - fmt.Printf("Updated security groups: %v\n", portDetails.SecurityGroups) + } + fmt.Println() +} + +func attachSecurityGroupToPort(networkClient *network.NetworkClient, portID, sgID string) { + ctx, cancel := getContext() + defer cancel() - // Detach security group from port - if err := networkClient.Ports().DetachSecurityGroup(ctx, *portDetails.ID, sgID); err != nil { - log.Fatal(err) + if err := networkClient.Ports().AttachSecurityGroup(ctx, portID, sgID); err != nil { + log.Fatalf("Failed to attach security group to port: %v", err) + } + + fmt.Printf("Attached security group %s to port %s\n", sgID, portID) +} + +func detachSecurityGroupFromPort(networkClient *network.NetworkClient, portID, sgID string) { + ctx, cancel := getContext() + defer cancel() + + if err := networkClient.Ports().DetachSecurityGroup(ctx, portID, sgID); err != nil { + log.Fatalf("Failed to detach security group from port: %v", err) + } + + fmt.Printf("Detached security group %s from port %s\n", sgID, portID) +} + +func listPorts(networkClient *network.NetworkClient, vpcID string) { + ctx, cancel := getContext() + defer cancel() + + ports, err := networkClient.VPCs().ListPorts(ctx, vpcID, true, network.ListOptions{}) + if err != nil { + log.Fatalf("Failed to list ports: %v", err) + } + + fmt.Printf("Ports in VPC %s:\n", vpcID) + if ports.Ports != nil { + for _, port := range *ports.Ports { + fmt.Printf(" Port: %s\n", *port.ID) } - fmt.Printf("Detached security group %s from port %s\n", sgID, *portDetails.ID) } +} - // Clean up resources - fmt.Println("\nCleaning up resources...") +func deletePort(networkClient *network.NetworkClient, portID string) { + ctx, cancel := getContext() + defer cancel() - // Delete security group - if err := networkClient.SecurityGroups().Delete(ctx, sgID); err != nil { - log.Fatal(err) + if err := networkClient.Ports().Delete(ctx, portID); err != nil { + log.Fatalf("Failed to delete port: %v", err) } - fmt.Printf("Deleted security group: %s\n", sgID) + + fmt.Printf("Port %s deleted successfully\n", portID) +} + +func cleanupVPC(networkClient *network.NetworkClient, vpcID string) { + deleteVPC(networkClient, vpcID) } diff --git a/network/subnets.go b/network/subnets.go index 0464405..2c005e7 100644 --- a/network/subnets.go +++ b/network/subnets.go @@ -51,6 +51,10 @@ type ( SubnetPoolID *string `json:"subnetpool_id,omitempty"` } + SubnetCreateOptions struct { + Zone *string `json:"zone,omitempty"` + } + SubnetPatchRequest struct { DNSNameservers *[]string `json:"dns_nameservers,omitempty"` } diff --git a/network/vpcs.go b/network/vpcs.go index aa15a21..a49a1d8 100644 --- a/network/vpcs.go +++ b/network/vpcs.go @@ -80,6 +80,10 @@ type ( SecurityGroups *[]string `json:"security_groups_id,omitempty"` } + PortCreateOptions struct { + Zone *string `json:"zone,omitempty"` + } + PublicIPCreateRequest struct { Description string `json:"description,omitempty"` } @@ -197,7 +201,7 @@ type VPCService interface { ListPorts(ctx context.Context, vpcID string, detailed bool, opts ListOptions) (*PortsList, error) // CreatePort creates a new port in a VPC - CreatePort(ctx context.Context, vpcID string, req PortCreateRequest) (string, error) + CreatePort(ctx context.Context, vpcID string, req PortCreateRequest, opts PortCreateOptions) (string, error) // ListPublicIPs returns all public IPs for a VPC ListPublicIPs(ctx context.Context, vpcID string) ([]PublicIPDb, error) @@ -209,7 +213,7 @@ type VPCService interface { ListSubnets(ctx context.Context, vpcID string) ([]SubnetResponse, error) // CreateSubnet creates a new subnet in a VPC - CreateSubnet(ctx context.Context, vpcID string, req SubnetCreateRequest) (string, error) + CreateSubnet(ctx context.Context, vpcID string, req SubnetCreateRequest, opts SubnetCreateOptions) (string, error) } type vpcService struct { @@ -313,19 +317,21 @@ func (s *vpcService) ListPorts(ctx context.Context, vpcID string, detailed bool, } // CreatePort creates a new network port in the specified VPC -func (s *vpcService) CreatePort(ctx context.Context, vpcID string, req PortCreateRequest) (string, error) { - result, err := mgc_http.ExecuteSimpleRequestWithRespBody[PortCreateResponse]( - ctx, - s.client.newRequest, - s.client.GetConfig(), - http.MethodPost, - fmt.Sprintf("/v0/vpcs/%s/ports", vpcID), - req, - nil, - ) +func (s *vpcService) CreatePort(ctx context.Context, vpcID string, req PortCreateRequest, opts PortCreateOptions) (string, error) { + nreq, err := s.client.newRequest(ctx, http.MethodPost, fmt.Sprintf("/v0/vpcs/%s/ports", vpcID), req) + if err != nil { + return "", err + } + + if opts.Zone != nil { + nreq.Header.Set("x-zone", *opts.Zone) + } + + result, err := mgc_http.Do(s.client.GetConfig(), ctx, nreq, &PortCreateResponse{}) if err != nil { return "", err } + return result.ID, nil } @@ -381,16 +387,17 @@ func (s *vpcService) ListSubnets(ctx context.Context, vpcID string) ([]SubnetRes } // CreateSubnet creates a new subnet in the specified VPC -func (s *vpcService) CreateSubnet(ctx context.Context, vpcID string, req SubnetCreateRequest) (string, error) { - result, err := mgc_http.ExecuteSimpleRequestWithRespBody[SubnetCreateResponse]( - ctx, - s.client.newRequest, - s.client.GetConfig(), - http.MethodPost, - fmt.Sprintf("/v0/vpcs/%s/subnets", vpcID), - req, - nil, - ) +func (s *vpcService) CreateSubnet(ctx context.Context, vpcID string, req SubnetCreateRequest, opts SubnetCreateOptions) (string, error) { + nreq, err := s.client.newRequest(ctx, http.MethodPost, fmt.Sprintf("/v0/vpcs/%s/subnets", vpcID), req) + if err != nil { + return "", err + } + + if opts.Zone != nil { + nreq.Header.Set("x-zone", *opts.Zone) + } + + result, err := mgc_http.Do(s.client.GetConfig(), ctx, nreq, &SubnetCreateResponse{}) if err != nil { return "", err } diff --git a/network/vpcs_test.go b/network/vpcs_test.go index 7373a16..903f44c 100644 --- a/network/vpcs_test.go +++ b/network/vpcs_test.go @@ -232,6 +232,42 @@ func TestVPCService_ListPorts(t *testing.T) { wantCount: 2, wantErr: false, }, + { + name: "ports list with sorting", + vpcID: "vpc1", + detailed: true, + opts: ListOptions{ + Sort: helpers.StrPtr("name:asc"), + }, + response: `{ + "ports": [ + {"id": "port1", "name": "app-port"}, + {"id": "port2", "name": "web-port"} + ] + }`, + statusCode: http.StatusOK, + wantCount: 2, + wantErr: false, + }, + { + name: "ports list with all query parameters", + vpcID: "vpc1", + detailed: true, + opts: ListOptions{ + Limit: helpers.IntPtr(5), + Offset: helpers.IntPtr(10), + Sort: helpers.StrPtr("created_at:desc"), + }, + response: `{ + "ports": [ + {"id": "port1", "name": "newest-port"}, + {"id": "port2", "name": "older-port"} + ] + }`, + statusCode: http.StatusOK, + wantCount: 2, + wantErr: false, + }, } for _, tt := range tests { @@ -246,6 +282,12 @@ func TestVPCService_ListPorts(t *testing.T) { if tt.opts.Limit != nil { assertEqual(t, strconv.Itoa(*tt.opts.Limit), query.Get("_limit")) } + if tt.opts.Offset != nil { + assertEqual(t, strconv.Itoa(*tt.opts.Offset), query.Get("_offset")) + } + if tt.opts.Sort != nil { + assertEqual(t, *tt.opts.Sort, query.Get("_sort")) + } assertEqual(t, strconv.FormatBool(tt.detailed), query.Get("detailed")) w.Header().Set("Content-Type", "application/json") @@ -278,6 +320,7 @@ func TestVPCService_CreatePort(t *testing.T) { name string vpcID string request PortCreateRequest + opts PortCreateOptions response string statusCode int wantID string @@ -292,6 +335,9 @@ func TestVPCService_CreatePort(t *testing.T) { Subnets: &[]string{"subnet1"}, SecurityGroups: &[]string{"sg1"}, }, + opts: PortCreateOptions{ + Zone: helpers.StrPtr("zone1"), + }, response: `{"id": "port-new"}`, statusCode: http.StatusCreated, wantID: "port-new", @@ -303,6 +349,7 @@ func TestVPCService_CreatePort(t *testing.T) { request: PortCreateRequest{ Name: "invalid-port", }, + opts: PortCreateOptions{}, response: `{"error": "subnets required"}`, statusCode: http.StatusBadRequest, wantErr: true, @@ -317,6 +364,11 @@ func TestVPCService_CreatePort(t *testing.T) { assertEqual(t, fmt.Sprintf("/network/v0/vpcs/%s/ports", tt.vpcID), r.URL.Path) assertEqual(t, http.MethodPost, r.Method) + // Check for the x-zone header if provided + if tt.opts.Zone != nil { + assertEqual(t, *tt.opts.Zone, r.Header.Get("x-zone")) + } + if !tt.wantErr { var req PortCreateRequest err := json.NewDecoder(r.Body).Decode(&req) @@ -331,7 +383,108 @@ func TestVPCService_CreatePort(t *testing.T) { defer server.Close() client := testVPCClient(server.URL) - id, err := client.CreatePort(context.Background(), tt.vpcID, tt.request) + id, err := client.CreatePort(context.Background(), tt.vpcID, tt.request, tt.opts) + + if tt.wantErr { + assertError(t, err) + return + } + + assertNoError(t, err) + assertEqual(t, tt.wantID, id) + }) + } +} + +func TestVPCService_CreatePort_AdditionalCases(t *testing.T) { + tests := []struct { + name string + vpcID string + request PortCreateRequest + opts PortCreateOptions + response string + statusCode int + wantID string + wantErr bool + }{ + { + name: "create with specific security groups", + vpcID: "vpc1", + request: PortCreateRequest{ + Name: "app-port", + HasPIP: helpers.BoolPtr(false), + HasSG: helpers.BoolPtr(true), + Subnets: &[]string{"subnet1"}, + SecurityGroups: &[]string{"sg1", "sg2"}, + }, + opts: PortCreateOptions{}, + response: `{"id": "port-secure"}`, + statusCode: http.StatusCreated, + wantID: "port-secure", + wantErr: false, + }, + { + name: "create port with zone header", + vpcID: "vpc1", + request: PortCreateRequest{ + Name: "zoned-port", + Subnets: &[]string{"subnet1"}, + HasPIP: helpers.BoolPtr(false), + }, + opts: PortCreateOptions{ + Zone: helpers.StrPtr("zone-a"), + }, + response: `{"id": "port-zoned"}`, + statusCode: http.StatusCreated, + wantID: "port-zoned", + wantErr: false, + }, + { + name: "server error", + vpcID: "vpc1", + request: PortCreateRequest{ + Name: "error-port", + Subnets: &[]string{"subnet1"}, + }, + opts: PortCreateOptions{}, + response: `{"error": "internal server error"}`, + statusCode: http.StatusInternalServerError, + wantErr: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assertEqual(t, fmt.Sprintf("/network/v0/vpcs/%s/ports", tt.vpcID), r.URL.Path) + assertEqual(t, http.MethodPost, r.Method) + + // Check for zone header if set + if tt.opts.Zone != nil { + assertEqual(t, *tt.opts.Zone, r.Header.Get("x-zone")) + } + + if !tt.wantErr { + var req PortCreateRequest + err := json.NewDecoder(r.Body).Decode(&req) + assertNoError(t, err) + + // Check security groups if provided + if req.SecurityGroups != nil && tt.request.SecurityGroups != nil { + assertEqualSlice(t, *tt.request.SecurityGroups, *req.SecurityGroups) + } + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(tt.statusCode) + w.Write([]byte(tt.response)) + })) + defer server.Close() + + client := testVPCClient(server.URL) + id, err := client.CreatePort(context.Background(), tt.vpcID, tt.request, tt.opts) if tt.wantErr { assertError(t, err) @@ -534,6 +687,7 @@ func TestVPCService_CreateSubnet(t *testing.T) { name string vpcID string request SubnetCreateRequest + opts SubnetCreateOptions response string statusCode int wantID string @@ -547,6 +701,9 @@ func TestVPCService_CreateSubnet(t *testing.T) { CIDRBlock: "10.0.0.0/24", IPVersion: 4, }, + opts: SubnetCreateOptions{ + Zone: helpers.StrPtr("zone2"), + }, response: `{"id": "subnet-new"}`, statusCode: http.StatusCreated, wantID: "subnet-new", @@ -559,6 +716,7 @@ func TestVPCService_CreateSubnet(t *testing.T) { Name: "invalid", CIDRBlock: "invalid", }, + opts: SubnetCreateOptions{}, response: `{"error": "invalid CIDR"}`, statusCode: http.StatusBadRequest, wantErr: true, @@ -573,6 +731,11 @@ func TestVPCService_CreateSubnet(t *testing.T) { assertEqual(t, fmt.Sprintf("/network/v0/vpcs/%s/subnets", tt.vpcID), r.URL.Path) assertEqual(t, http.MethodPost, r.Method) + // Check for the x-zone header if provided + if tt.opts.Zone != nil { + assertEqual(t, *tt.opts.Zone, r.Header.Get("x-zone")) + } + var req SubnetCreateRequest err := json.NewDecoder(r.Body).Decode(&req) assertNoError(t, err) @@ -586,7 +749,121 @@ func TestVPCService_CreateSubnet(t *testing.T) { defer server.Close() client := testVPCClient(server.URL) - id, err := client.CreateSubnet(context.Background(), tt.vpcID, tt.request) + id, err := client.CreateSubnet(context.Background(), tt.vpcID, tt.request, tt.opts) + + if tt.wantErr { + assertError(t, err) + return + } + + assertNoError(t, err) + assertEqual(t, tt.wantID, id) + }) + } +} + +func TestVPCService_CreateSubnet_AdditionalCases(t *testing.T) { + tests := []struct { + name string + vpcID string + request SubnetCreateRequest + opts SubnetCreateOptions + response string + statusCode int + wantID string + wantErr bool + }{ + { + name: "create IPv6 subnet", + vpcID: "vpc1", + request: SubnetCreateRequest{ + Name: "ipv6-subnet", + CIDRBlock: "2001:db8::/64", + IPVersion: 6, + Description: helpers.StrPtr("IPv6 subnet"), + }, + opts: SubnetCreateOptions{}, + response: `{"id": "subnet-ipv6"}`, + statusCode: http.StatusCreated, + wantID: "subnet-ipv6", + wantErr: false, + }, + { + name: "create subnet in specific zone", + vpcID: "vpc1", + request: SubnetCreateRequest{ + Name: "zone-subnet", + CIDRBlock: "10.1.0.0/24", + IPVersion: 4, + Description: helpers.StrPtr("Zoned subnet"), + }, + opts: SubnetCreateOptions{ + Zone: helpers.StrPtr("zone-b"), + }, + response: `{"id": "subnet-zoned"}`, + statusCode: http.StatusCreated, + wantID: "subnet-zoned", + wantErr: false, + }, + { + name: "overlapping CIDR error", + vpcID: "vpc1", + request: SubnetCreateRequest{ + Name: "overlap-subnet", + CIDRBlock: "10.0.0.0/24", + IPVersion: 4, + }, + opts: SubnetCreateOptions{}, + response: `{"error": "CIDR overlaps with existing subnet"}`, + statusCode: http.StatusConflict, + wantErr: true, + }, + { + name: "invalid IP version", + vpcID: "vpc1", + request: SubnetCreateRequest{ + Name: "invalid-subnet", + CIDRBlock: "10.0.0.0/24", + IPVersion: 5, // Invalid IP version + }, + opts: SubnetCreateOptions{}, + response: `{"error": "invalid IP version: must be 4 or 6"}`, + statusCode: http.StatusBadRequest, + wantErr: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assertEqual(t, fmt.Sprintf("/network/v0/vpcs/%s/subnets", tt.vpcID), r.URL.Path) + assertEqual(t, http.MethodPost, r.Method) + + // Check for zone header if set + if tt.opts.Zone != nil { + assertEqual(t, *tt.opts.Zone, r.Header.Get("x-zone")) + } + + var req SubnetCreateRequest + err := json.NewDecoder(r.Body).Decode(&req) + assertNoError(t, err) + assertEqual(t, tt.request.Name, req.Name) + assertEqual(t, tt.request.CIDRBlock, req.CIDRBlock) + assertEqual(t, tt.request.IPVersion, req.IPVersion) + if tt.request.Description != nil { + assertEqual(t, *tt.request.Description, *req.Description) + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(tt.statusCode) + w.Write([]byte(tt.response)) + })) + defer server.Close() + + client := testVPCClient(server.URL) + id, err := client.CreateSubnet(context.Background(), tt.vpcID, tt.request, tt.opts) if tt.wantErr { assertError(t, err)