diff --git a/doc/ovhcloud_cloud_network_private.md b/doc/ovhcloud_cloud_network_private.md index 57c71c0e..25de677c 100644 --- a/doc/ovhcloud_cloud_network_private.md +++ b/doc/ovhcloud_cloud_network_private.md @@ -5,7 +5,8 @@ Manage private networks in the given cloud project ### Options ``` - -h, --help help for private + -h, --help help for private + --region string Filter by region or specify the region of the network ``` ### Options inherited from parent commands @@ -32,9 +33,7 @@ Manage private networks in the given cloud project * [ovhcloud cloud network](ovhcloud_cloud_network.md) - Manage networks in the given cloud project * [ovhcloud cloud network private create](ovhcloud_cloud_network_private_create.md) - Create a private network in the given cloud project * [ovhcloud cloud network private delete](ovhcloud_cloud_network_private_delete.md) - Delete a specific private network -* [ovhcloud cloud network private edit](ovhcloud_cloud_network_private_edit.md) - Edit the given private network * [ovhcloud cloud network private get](ovhcloud_cloud_network_private_get.md) - Get a specific private network * [ovhcloud cloud network private list](ovhcloud_cloud_network_private_list.md) - List your private networks -* [ovhcloud cloud network private region](ovhcloud_cloud_network_private_region.md) - Manage regions in a specific private network * [ovhcloud cloud network private subnet](ovhcloud_cloud_network_private_subnet.md) - Manage subnets in a specific private network diff --git a/doc/ovhcloud_cloud_network_private_create.md b/doc/ovhcloud_cloud_network_private_create.md index 56bcf6d6..f8421bb1 100644 --- a/doc/ovhcloud_cloud_network_private_create.md +++ b/doc/ovhcloud_cloud_network_private_create.md @@ -49,26 +49,14 @@ ovhcloud cloud network private create [flags] ### Options ``` - --editor Use a text editor to define parameters - --from-file string File containing parameters - --gateway-model string Gateway model (s, m, l, xl, 2xl, 3xl) - --gateway-name string Name of the gateway - -h, --help help for create - --init-file string Create a file with example parameters - --name string Name of the private network - --replace Replace parameters file if it already exists - --subnet-allocation-pools strings Allocation pools for the subnet in format start:end - --subnet-cidr string CIDR of the subnet - --subnet-dns-name-servers strings DNS name servers for the subnet - --subnet-enable-dhcp Enable DHCP for the subnet - --subnet-enable-gateway-ip Set a gateway ip for the subnet - --subnet-gateway-ip string Gateway IP address for the subnet - --subnet-host-routes strings Host routes for the subnet in format destination:nextHop - --subnet-ip-version int IP version (4 or 6) - --subnet-name string Name of the subnet - --subnet-use-default-public-dns-resolver Use default DNS resolver for the subnet - --vlan-id int VLAN ID for the private network - --wait Wait for network creation to be done before exiting + --editor Use a text editor to define parameters + --from-file string File containing parameters + -h, --help help for create + --init-file string Create a file with example parameters + --name string Name of the private network + --replace Replace parameters file if it already exists + --vlan-id int VLAN ID for the private network + --wait Wait for network creation to be done before exiting ``` ### Options inherited from parent commands @@ -88,6 +76,7 @@ ovhcloud cloud network private create [flags] --output '{"newKey": oldKey, "otherKey": nested.field}' (to extract and rename fields in an object) --output 'name+","+type' (to extract and concatenate fields in a string) --output '(nbFieldA + nbFieldB) * 10' (to compute values from numeric fields) + --region string Filter by region or specify the region of the network ``` ### SEE ALSO diff --git a/doc/ovhcloud_cloud_network_private_delete.md b/doc/ovhcloud_cloud_network_private_delete.md index 319e9c9b..396378ad 100644 --- a/doc/ovhcloud_cloud_network_private_delete.md +++ b/doc/ovhcloud_cloud_network_private_delete.md @@ -29,6 +29,7 @@ ovhcloud cloud network private delete [flags] --output '{"newKey": oldKey, "otherKey": nested.field}' (to extract and rename fields in an object) --output 'name+","+type' (to extract and concatenate fields in a string) --output '(nbFieldA + nbFieldB) * 10' (to compute values from numeric fields) + --region string Filter by region or specify the region of the network ``` ### SEE ALSO diff --git a/doc/ovhcloud_cloud_network_private_get.md b/doc/ovhcloud_cloud_network_private_get.md index 12cbcc04..1e4f6cf9 100644 --- a/doc/ovhcloud_cloud_network_private_get.md +++ b/doc/ovhcloud_cloud_network_private_get.md @@ -29,6 +29,7 @@ ovhcloud cloud network private get [flags] --output '{"newKey": oldKey, "otherKey": nested.field}' (to extract and rename fields in an object) --output 'name+","+type' (to extract and concatenate fields in a string) --output '(nbFieldA + nbFieldB) * 10' (to compute values from numeric fields) + --region string Filter by region or specify the region of the network ``` ### SEE ALSO diff --git a/doc/ovhcloud_cloud_network_private_list.md b/doc/ovhcloud_cloud_network_private_list.md index 189aba47..a7c84fbd 100644 --- a/doc/ovhcloud_cloud_network_private_list.md +++ b/doc/ovhcloud_cloud_network_private_list.md @@ -36,6 +36,7 @@ ovhcloud cloud network private list [flags] --output '{"newKey": oldKey, "otherKey": nested.field}' (to extract and rename fields in an object) --output 'name+","+type' (to extract and concatenate fields in a string) --output '(nbFieldA + nbFieldB) * 10' (to compute values from numeric fields) + --region string Filter by region or specify the region of the network ``` ### SEE ALSO diff --git a/doc/ovhcloud_cloud_network_private_subnet.md b/doc/ovhcloud_cloud_network_private_subnet.md index 5a44e751..44baf697 100644 --- a/doc/ovhcloud_cloud_network_private_subnet.md +++ b/doc/ovhcloud_cloud_network_private_subnet.md @@ -25,6 +25,7 @@ Manage subnets in a specific private network --output '{"newKey": oldKey, "otherKey": nested.field}' (to extract and rename fields in an object) --output 'name+","+type' (to extract and concatenate fields in a string) --output '(nbFieldA + nbFieldB) * 10' (to compute values from numeric fields) + --region string Filter by region or specify the region of the network ``` ### SEE ALSO @@ -32,7 +33,6 @@ Manage subnets in a specific private network * [ovhcloud cloud network private](ovhcloud_cloud_network_private.md) - Manage private networks in the given cloud project * [ovhcloud cloud network private subnet create](ovhcloud_cloud_network_private_subnet_create.md) - Create a subnet in the given private network * [ovhcloud cloud network private subnet delete](ovhcloud_cloud_network_private_subnet_delete.md) - Delete a specific subnet in a private network -* [ovhcloud cloud network private subnet edit](ovhcloud_cloud_network_private_subnet_edit.md) - Edit a specific subnet in a private network * [ovhcloud cloud network private subnet get](ovhcloud_cloud_network_private_subnet_get.md) - Get a specific subnet in a private network * [ovhcloud cloud network private subnet list](ovhcloud_cloud_network_private_subnet_list.md) - List subnets in a private network diff --git a/doc/ovhcloud_cloud_network_private_subnet_create.md b/doc/ovhcloud_cloud_network_private_subnet_create.md index 0be819f6..2f983e45 100644 --- a/doc/ovhcloud_cloud_network_private_subnet_create.md +++ b/doc/ovhcloud_cloud_network_private_subnet_create.md @@ -9,7 +9,7 @@ There are three ways to define the parameters: 1. Using only CLI flags: - ovhcloud cloud network private subnet create --network 192.168.1.0/24 --start 192.168.1.12 --end 192.168.1.24 --region GRA9 + ovhcloud cloud network private subnet create --region GRA9 --name MySubnet --cidr 192.168.1.0/24 --ip-version 4 2. Using a configuration file: @@ -28,7 +28,7 @@ There are three ways to define the parameters: In both cases, you can override the parameters in the given file using command line flags, for example: - ovhcloud cloud network private subnet create --from-file ./params.json --region BHS5 + ovhcloud cloud network private subnet create --from-file ./params.json --name MySubnet 3. Using your default text editor: @@ -39,7 +39,7 @@ There are three ways to define the parameters: Note that it is also possible to override values in the presented examples using command line flags like the following: - ovhcloud cloud network private subnet create --editor --region DE1 + ovhcloud cloud network private subnet create --editor --name MySubnet ``` @@ -49,17 +49,21 @@ ovhcloud cloud network private subnet create [flags] ### Options ``` - --dhcp Enable DHCP for the subnet - --editor Use a text editor to define parameters - --end string Last IP for this region (eg: 192.168.1.24) - --from-file string File containing parameters - -h, --help help for create - --init-file string Create a file with example parameters - --network string Global network CIDR (eg: 192.168.1.0/24) - --no-gateway Use this flag if you don't want to set a default gateway IP - --region string Region for the subnet - --replace Replace parameters file if it already exists - --start string First IP for this region (eg: 192.168.1.12) + --allocation-pools strings Allocation pools for the subnet in format start:end + --cidr string CIDR of the subnet (eg: 192.168.1.0/24) + --dns-name-servers strings DNS name servers for the subnet + --editor Use a text editor to define parameters + --enable-dhcp Enable DHCP for the subnet + --enable-gateway-ip Set a gateway IP for the subnet + --from-file string File containing parameters + --gateway-ip string Gateway IP address for the subnet + -h, --help help for create + --host-routes strings Host routes for the subnet in format destination:nextHop + --init-file string Create a file with example parameters + --ip-version int IP version (4 or 6) + --name string Name of the subnet + --replace Replace parameters file if it already exists + --use-default-public-dns-resolver Use default DNS resolver for the subnet ``` ### Options inherited from parent commands @@ -79,6 +83,7 @@ ovhcloud cloud network private subnet create [flags] --output '{"newKey": oldKey, "otherKey": nested.field}' (to extract and rename fields in an object) --output 'name+","+type' (to extract and concatenate fields in a string) --output '(nbFieldA + nbFieldB) * 10' (to compute values from numeric fields) + --region string Filter by region or specify the region of the network ``` ### SEE ALSO diff --git a/doc/ovhcloud_cloud_network_private_subnet_delete.md b/doc/ovhcloud_cloud_network_private_subnet_delete.md index 2b664659..13c768e0 100644 --- a/doc/ovhcloud_cloud_network_private_subnet_delete.md +++ b/doc/ovhcloud_cloud_network_private_subnet_delete.md @@ -29,6 +29,7 @@ ovhcloud cloud network private subnet delete [flags] --output '{"newKey": oldKey, "otherKey": nested.field}' (to extract and rename fields in an object) --output 'name+","+type' (to extract and concatenate fields in a string) --output '(nbFieldA + nbFieldB) * 10' (to compute values from numeric fields) + --region string Filter by region or specify the region of the network ``` ### SEE ALSO diff --git a/doc/ovhcloud_cloud_network_private_subnet_get.md b/doc/ovhcloud_cloud_network_private_subnet_get.md index 3b7802ad..b05038ed 100644 --- a/doc/ovhcloud_cloud_network_private_subnet_get.md +++ b/doc/ovhcloud_cloud_network_private_subnet_get.md @@ -29,6 +29,7 @@ ovhcloud cloud network private subnet get [flags] --output '{"newKey": oldKey, "otherKey": nested.field}' (to extract and rename fields in an object) --output 'name+","+type' (to extract and concatenate fields in a string) --output '(nbFieldA + nbFieldB) * 10' (to compute values from numeric fields) + --region string Filter by region or specify the region of the network ``` ### SEE ALSO diff --git a/doc/ovhcloud_cloud_network_private_subnet_list.md b/doc/ovhcloud_cloud_network_private_subnet_list.md index 130621c1..1e183a86 100644 --- a/doc/ovhcloud_cloud_network_private_subnet_list.md +++ b/doc/ovhcloud_cloud_network_private_subnet_list.md @@ -36,6 +36,7 @@ ovhcloud cloud network private subnet list [flags] --output '{"newKey": oldKey, "otherKey": nested.field}' (to extract and rename fields in an object) --output 'name+","+type' (to extract and concatenate fields in a string) --output '(nbFieldA + nbFieldB) * 10' (to compute values from numeric fields) + --region string Filter by region or specify the region of the network ``` ### SEE ALSO diff --git a/doc/ovhcloud_cloud_network_public.md b/doc/ovhcloud_cloud_network_public.md index a27258d2..2707156c 100644 --- a/doc/ovhcloud_cloud_network_public.md +++ b/doc/ovhcloud_cloud_network_public.md @@ -5,7 +5,8 @@ Manage public networks in the given cloud project ### Options ``` - -h, --help help for public + -h, --help help for public + --region string Filter by region or specify the region of the network ``` ### Options inherited from parent commands diff --git a/doc/ovhcloud_cloud_network_public_get.md b/doc/ovhcloud_cloud_network_public_get.md index 4402a03b..6bc057f8 100644 --- a/doc/ovhcloud_cloud_network_public_get.md +++ b/doc/ovhcloud_cloud_network_public_get.md @@ -29,6 +29,7 @@ ovhcloud cloud network public get [flags] --output '{"newKey": oldKey, "otherKey": nested.field}' (to extract and rename fields in an object) --output 'name+","+type' (to extract and concatenate fields in a string) --output '(nbFieldA + nbFieldB) * 10' (to compute values from numeric fields) + --region string Filter by region or specify the region of the network ``` ### SEE ALSO diff --git a/doc/ovhcloud_cloud_network_public_list.md b/doc/ovhcloud_cloud_network_public_list.md index 5e663e0c..f7f2906e 100644 --- a/doc/ovhcloud_cloud_network_public_list.md +++ b/doc/ovhcloud_cloud_network_public_list.md @@ -36,6 +36,7 @@ ovhcloud cloud network public list [flags] --output '{"newKey": oldKey, "otherKey": nested.field}' (to extract and rename fields in an object) --output 'name+","+type' (to extract and concatenate fields in a string) --output '(nbFieldA + nbFieldB) * 10' (to compute values from numeric fields) + --region string Filter by region or specify the region of the network ``` ### SEE ALSO diff --git a/internal/cmd/cloud_network.go b/internal/cmd/cloud_network.go index bce1668d..f70f49cc 100644 --- a/internal/cmd/cloud_network.go +++ b/internal/cmd/cloud_network.go @@ -24,6 +24,7 @@ func initCloudNetworkCommand(cloudCmd *cobra.Command) { Use: "private", Short: "Manage private networks in the given cloud project", } + privateNetworkCmd.PersistentFlags().StringVar(&cloud.CloudNetworkRegionFilter, "region", "", "Filter by region or specify the region of the network") networkCmd.AddCommand(privateNetworkCmd) privateNetworkListCmd := &cobra.Command{ @@ -34,22 +35,13 @@ func initCloudNetworkCommand(cloudCmd *cobra.Command) { } privateNetworkCmd.AddCommand(withFilterFlag(privateNetworkListCmd)) - privateNetworkCmd.AddCommand(&cobra.Command{ + privateNetworkGetCmd := &cobra.Command{ Use: "get ", Short: "Get a specific private network", Run: cloud.GetPrivateNetwork, Args: cobra.ExactArgs(1), - }) - - privateNetworkEditCmd := &cobra.Command{ - Use: "edit ", - Short: "Edit the given private network", - Args: cobra.ExactArgs(1), - Run: cloud.EditPrivateNetwork, } - privateNetworkEditCmd.Flags().StringVar(&cloud.CloudNetworkName, "name", "", "Name of the private network") - addInteractiveEditorFlag(privateNetworkEditCmd) - privateNetworkCmd.AddCommand(privateNetworkEditCmd) + privateNetworkCmd.AddCommand(privateNetworkGetCmd) privateNetworkCmd.AddCommand(getPrivateNetworkCreationCmd()) @@ -60,27 +52,6 @@ func initCloudNetworkCommand(cloudCmd *cobra.Command) { Args: cobra.ExactArgs(1), }) - // Private network region commands - privateNetworkRegionCmd := &cobra.Command{ - Use: "region", - Short: "Manage regions in a specific private network", - } - privateNetworkCmd.AddCommand(privateNetworkRegionCmd) - - privateNetworkRegionCmd.AddCommand(&cobra.Command{ - Use: "delete ", - Short: "Delete the given region from a private network", - Run: cloud.DeletePrivateNetworkRegion, - Args: cobra.ExactArgs(2), - }) - - privateNetworkRegionCmd.AddCommand(&cobra.Command{ - Use: "add ", - Short: "Add a region to a private network", - Run: cloud.AddPrivateNetworkRegion, - Args: cobra.ExactArgs(2), - }) - // Private network subnet commands privateNetworkSubnetCmd := &cobra.Command{ Use: "subnet", @@ -88,35 +59,25 @@ func initCloudNetworkCommand(cloudCmd *cobra.Command) { } privateNetworkCmd.AddCommand(privateNetworkSubnetCmd) - privateNetworkSubnetCmd.AddCommand(withFilterFlag(&cobra.Command{ + subnetListCmd := &cobra.Command{ Use: "list ", Aliases: []string{"ls"}, Short: "List subnets in a private network", Run: cloud.ListPrivateNetworkSubnets, Args: cobra.ExactArgs(1), - })) + } + privateNetworkSubnetCmd.AddCommand(withFilterFlag(subnetListCmd)) - privateNetworkSubnetCmd.AddCommand(&cobra.Command{ + subnetGetCmd := &cobra.Command{ Use: "get ", Short: "Get a specific subnet in a private network", Run: cloud.GetPrivateNetworkSubnet, Args: cobra.ExactArgs(2), - }) + } + privateNetworkSubnetCmd.AddCommand(subnetGetCmd) privateNetworkSubnetCmd.AddCommand(getSubnetCreationCmd()) - privateNetworkSubnetEditCmd := &cobra.Command{ - Use: "edit ", - Short: "Edit a specific subnet in a private network", - Run: cloud.EditPrivateNetworkSubnet, - Args: cobra.ExactArgs(2), - } - privateNetworkSubnetEditCmd.Flags().BoolVar(&cloud.CloudNetworkSubnetEditSpec.Dhcp, "enable-dhcp", false, "Enable DHCP (set to true if you don't want to set a default gateway IP)") - privateNetworkSubnetEditCmd.Flags().BoolVar(&cloud.CloudNetworkSubnetEditSpec.DisableGateway, "disable-gateway", false, "Set to true if you want to disable the default gateway") - privateNetworkSubnetEditCmd.Flags().StringVar(&cloud.CloudNetworkSubnetEditSpec.GatewayIp, "gateway-ip", "", "Gateway IP address") - addInteractiveEditorFlag(privateNetworkSubnetEditCmd) - privateNetworkSubnetCmd.AddCommand(privateNetworkSubnetEditCmd) - privateNetworkSubnetCmd.AddCommand(&cobra.Command{ Use: "delete ", Short: "Delete a specific subnet in a private network", @@ -129,6 +90,7 @@ func initCloudNetworkCommand(cloudCmd *cobra.Command) { Use: "public", Short: "Manage public networks in the given cloud project", } + publicNetworkCmd.PersistentFlags().StringVar(&cloud.CloudNetworkRegionFilter, "region", "", "Filter by region or specify the region of the network") networkCmd.AddCommand(publicNetworkCmd) publicNetworkListCmd := &cobra.Command{ @@ -139,12 +101,13 @@ func initCloudNetworkCommand(cloudCmd *cobra.Command) { } publicNetworkCmd.AddCommand(withFilterFlag(publicNetworkListCmd)) - publicNetworkCmd.AddCommand(&cobra.Command{ + publicNetworkGetCmd := &cobra.Command{ Use: "get ", Short: "Get a specific public network", Run: cloud.GetPublicNetwork, Args: cobra.ExactArgs(1), - }) + } + publicNetworkCmd.AddCommand(publicNetworkGetCmd) // Gateway commands gatewayCmd := &cobra.Command{ @@ -283,21 +246,6 @@ There are three ways to define the parameters: privateNetworkCreateCmd.Flags().StringVar(&cloud.CloudNetworkSpec.Name, "name", "", "Name of the private network") privateNetworkCreateCmd.Flags().IntVar(&cloud.CloudNetworkSpec.VlanId, "vlan-id", 0, "VLAN ID for the private network") - privateNetworkCreateCmd.Flags().StringVar(&cloud.CloudNetworkSpec.Gateway.Model, "gateway-model", "", "Gateway model (s, m, l, xl, 2xl, 3xl)") - privateNetworkCreateCmd.Flags().StringVar(&cloud.CloudNetworkSpec.Gateway.Name, "gateway-name", "", "Name of the gateway") - - privateNetworkCreateCmd.Flags().StringVar(&cloud.CloudNetworkSpec.Subnet.Name, "subnet-name", "", "Name of the subnet") - privateNetworkCreateCmd.Flags().StringVar(&cloud.CloudNetworkSpec.Subnet.Cidr, "subnet-cidr", "", "CIDR of the subnet") - privateNetworkCreateCmd.Flags().IntVar(&cloud.CloudNetworkSpec.Subnet.IPVersion, "subnet-ip-version", 0, "IP version (4 or 6)") - privateNetworkCreateCmd.Flags().BoolVar(&cloud.CloudNetworkSpec.Subnet.EnableDhcp, "subnet-enable-dhcp", false, "Enable DHCP for the subnet") - privateNetworkCreateCmd.Flags().BoolVar(&cloud.CloudNetworkSpec.Subnet.EnableGatewayIp, "subnet-enable-gateway-ip", false, "Set a gateway ip for the subnet") - privateNetworkCreateCmd.Flags().StringVar(&cloud.CloudNetworkSpec.Subnet.GatewayIp, "subnet-gateway-ip", "", "Gateway IP address for the subnet") - privateNetworkCreateCmd.Flags().BoolVar(&cloud.CloudNetworkSpec.Subnet.UseDefaultPublicDNSResolver, "subnet-use-default-public-dns-resolver", false, "Use default DNS resolver for the subnet") - - privateNetworkCreateCmd.Flags().StringSliceVar(&cloud.CloudNetworkSpec.Subnet.DnsNameServers, "subnet-dns-name-servers", nil, "DNS name servers for the subnet") - privateNetworkCreateCmd.Flags().StringSliceVar(&cloud.CloudNetworkSpec.Subnet.CliAllocationPools, "subnet-allocation-pools", nil, "Allocation pools for the subnet in format start:end") - privateNetworkCreateCmd.Flags().StringSliceVar(&cloud.CloudNetworkSpec.Subnet.CliHostRoutes, "subnet-host-routes", nil, "Host routes for the subnet in format destination:nextHop") - // Common flags for other means to define parameters addParameterFileFlags(privateNetworkCreateCmd, false, assets.CloudOpenapiSchema, "/cloud/project/{serviceName}/region/{regionName}/network", "post", cloud.PrivateNetworkCreationExample, nil) addInteractiveEditorFlag(privateNetworkCreateCmd) @@ -316,7 +264,7 @@ There are three ways to define the parameters: 1. Using only CLI flags: - ovhcloud cloud network private subnet create --network 192.168.1.0/24 --start 192.168.1.12 --end 192.168.1.24 --region GRA9 + ovhcloud cloud network private subnet create --region GRA9 --name MySubnet --cidr 192.168.1.0/24 --ip-version 4 2. Using a configuration file: @@ -335,7 +283,7 @@ There are three ways to define the parameters: In both cases, you can override the parameters in the given file using command line flags, for example: - ovhcloud cloud network private subnet create --from-file ./params.json --region BHS5 + ovhcloud cloud network private subnet create --from-file ./params.json --name MySubnet 3. Using your default text editor: @@ -346,21 +294,25 @@ There are three ways to define the parameters: Note that it is also possible to override values in the presented examples using command line flags like the following: - ovhcloud cloud network private subnet create --editor --region DE1 + ovhcloud cloud network private subnet create --editor --name MySubnet `, Run: cloud.CreatePrivateNetworkSubnet, Args: cobra.ExactArgs(1), } - privateNetworkSubnetCreateCmd.Flags().BoolVar(&cloud.CloudNetworkSubnetSpec.DHCP, "dhcp", false, "Enable DHCP for the subnet") - privateNetworkSubnetCreateCmd.Flags().BoolVar(&cloud.CloudNetworkSubnetSpec.NoGateway, "no-gateway", false, "Use this flag if you don't want to set a default gateway IP") - privateNetworkSubnetCreateCmd.Flags().StringVar(&cloud.CloudNetworkSubnetSpec.Network, "network", "", "Global network CIDR (eg: 192.168.1.0/24)") - privateNetworkSubnetCreateCmd.Flags().StringVar(&cloud.CloudNetworkSubnetSpec.Start, "start", "", "First IP for this region (eg: 192.168.1.12)") - privateNetworkSubnetCreateCmd.Flags().StringVar(&cloud.CloudNetworkSubnetSpec.End, "end", "", "Last IP for this region (eg: 192.168.1.24)") - privateNetworkSubnetCreateCmd.Flags().StringVar(&cloud.CloudNetworkSubnetSpec.Region, "region", "", "Region for the subnet") + privateNetworkSubnetCreateCmd.Flags().StringVar(&cloud.CloudNetworkSubnetSpec.Name, "name", "", "Name of the subnet") + privateNetworkSubnetCreateCmd.Flags().StringVar(&cloud.CloudNetworkSubnetSpec.Cidr, "cidr", "", "CIDR of the subnet (eg: 192.168.1.0/24)") + privateNetworkSubnetCreateCmd.Flags().IntVar(&cloud.CloudNetworkSubnetSpec.IPVersion, "ip-version", 0, "IP version (4 or 6)") + privateNetworkSubnetCreateCmd.Flags().BoolVar(&cloud.CloudNetworkSubnetSpec.EnableDhcp, "enable-dhcp", false, "Enable DHCP for the subnet") + privateNetworkSubnetCreateCmd.Flags().BoolVar(&cloud.CloudNetworkSubnetSpec.EnableGatewayIp, "enable-gateway-ip", false, "Set a gateway IP for the subnet") + privateNetworkSubnetCreateCmd.Flags().StringVar(&cloud.CloudNetworkSubnetSpec.GatewayIp, "gateway-ip", "", "Gateway IP address for the subnet") + privateNetworkSubnetCreateCmd.Flags().BoolVar(&cloud.CloudNetworkSubnetSpec.UseDefaultPublicDNSResolver, "use-default-public-dns-resolver", false, "Use default DNS resolver for the subnet") + privateNetworkSubnetCreateCmd.Flags().StringSliceVar(&cloud.CloudNetworkSubnetSpec.DnsNameServers, "dns-name-servers", nil, "DNS name servers for the subnet") + privateNetworkSubnetCreateCmd.Flags().StringSliceVar(&cloud.CloudNetworkSubnetSpec.CliAllocationPools, "allocation-pools", nil, "Allocation pools for the subnet in format start:end") + privateNetworkSubnetCreateCmd.Flags().StringSliceVar(&cloud.CloudNetworkSubnetSpec.CliHostRoutes, "host-routes", nil, "Host routes for the subnet in format destination:nextHop") // Common flags for other means to define parameters - addParameterFileFlags(privateNetworkSubnetCreateCmd, false, assets.CloudOpenapiSchema, "/cloud/project/{serviceName}/network/private/{networkId}/subnet", "post", cloud.PrivateNetworkSubnetCreationExample, nil) + addParameterFileFlags(privateNetworkSubnetCreateCmd, false, assets.CloudOpenapiSchema, "/cloud/project/{serviceName}/region/{regionName}/network/{networkId}/subnet", "post", cloud.PrivateNetworkSubnetCreationExample, nil) addInteractiveEditorFlag(privateNetworkSubnetCreateCmd) privateNetworkSubnetCreateCmd.MarkFlagsMutuallyExclusive("from-file", "editor") diff --git a/internal/cmd/cloud_network_test.go b/internal/cmd/cloud_network_test.go index 6dfeef58..343d7df9 100644 --- a/internal/cmd/cloud_network_test.go +++ b/internal/cmd/cloud_network_test.go @@ -19,17 +19,7 @@ func (ms *MockSuite) TestCloudPrivateNetworkCreateCmd(assert, require *td.T) { "https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/region/BHS5/network", tdhttpmock.JSONBody(td.JSON(` { - "gateway": { - "model": "s", - "name": "TestFromTheCLI" - }, - "name": "TestFromTheCLI", - "subnet": { - "cidr": "10.0.0.2/24", - "enableDhcp": false, - "enableGatewayIp": true, - "ipVersion": 4 - } + "name": "TestFromTheCLI" }`), ), httpmock.NewStringResponder(200, `{"id": "operation-12345"}`), @@ -66,23 +56,14 @@ func (ms *MockSuite) TestCloudPrivateNetworkCreateCmd(assert, require *td.T) { }`), ) - httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/network/private", - httpmock.NewStringResponder(200, `[ - { - "id": "pn-example", - "name": "TestFromTheCLI", - "vlanId": 1234, - "regions": [ - { - "region": "BHS5", - "status": "ACTIVE", - "openstackId": "80c1de3e-9b09-11f0-993b-0050568ce122" - } - ], - "type": "private", - "status": "ACTIVE" - } - ]`), + httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/region/BHS5/network/80c1de3e-9b09-11f0-993b-0050568ce122", + httpmock.NewStringResponder(200, `{ + "id": "80c1de3e-9b09-11f0-993b-0050568ce122", + "name": "TestFromTheCLI", + "region": "BHS5", + "visibility": "private", + "vlanId": 1234 + }`), ) httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/region/BHS5/network/80c1de3e-9b09-11f0-993b-0050568ce122/subnet", @@ -140,34 +121,34 @@ func (ms *MockSuite) TestCloudPrivateNetworkCreateCmd(assert, require *td.T) { ) out, err := cmd.Execute("cloud", "network", "private", "create", "BHS5", "--cloud-project", "fakeProjectID", - "--gateway-model", "s", "--gateway-name", "TestFromTheCLI", "--name", "TestFromTheCLI", "--subnet-cidr", - "10.0.0.2/24", "--subnet-ip-version", "4", "--wait", "--subnet-enable-gateway-ip", "-o", "yaml") + "--name", "TestFromTheCLI", "--wait", "-o", "yaml") require.CmpNoError(err) - assert.String(out, `details: - id: pn-example - openstackId: 80c1de3e-9b09-11f0-993b-0050568ce122 - region: BHS5 - subnets: - - gateways: - - id: e7045f34-8f2b-41a4-a734-97b7b0e323de - name: TestFromTheCLI - id: c59a3fdc-9b0f-11f0-ac97-0050568ce122 - name: TestFromTheCLI -message: '✅ Network pn-example created successfully (Openstack ID: 80c1de3e-9b09-11f0-993b-0050568ce122)' + assert.String(out, `message: ✅ Network 80c1de3e-9b09-11f0-993b-0050568ce122 created successfully in region + BHS5 `) } func (ms *MockSuite) TestCloudPrivateNetworkSubnetCreateCmd(assert, require *td.T) { + // findNetwork will look up the network by region + httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/region/BHS5/network/pn-123456", + httpmock.NewStringResponder(200, `{ + "id": "pn-123456", + "name": "test-network", + "region": "BHS5", + "visibility": "private", + "vlanId": 0 + }`), + ) + httpmock.RegisterMatcherResponder(http.MethodPost, - "https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/network/private/pn-123456/subnet", + "https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/region/BHS5/network/pn-123456/subnet", tdhttpmock.JSONBody(td.JSON(` { - "dhcp": false, - "end": "192.168.1.24", - "network": "192.168.1.0/24", - "noGateway": false, - "region": "BHS5", - "start": "192.168.1.12" + "name": "my-subnet", + "cidr": "192.168.1.0/24", + "ipVersion": 4, + "enableDhcp": true, + "enableGatewayIp": true }`), ), httpmock.NewStringResponder(200, ` @@ -175,21 +156,21 @@ func (ms *MockSuite) TestCloudPrivateNetworkSubnetCreateCmd(assert, require *td. "cidr": "192.168.1.0/24", "gatewayIp": "192.168.1.1", "id": "5e625f90-9ec3-11f0-9f75-0050568ce122", - "ipPools": [ + "name": "my-subnet", + "ipVersion": 4, + "dhcpEnabled": true, + "allocationPools": [ { - "dhcp": false, - "end": "192.168.1.24", - "network": "192.168.1.0/24", - "region": "BHS5", - "start": "192.168.1.12" + "start": "192.168.1.2", + "end": "192.168.1.254" } ] }`, ), ) - out, err := cmd.Execute("cloud", "network", "private", "subnet", "create", "pn-123456", "--cloud-project", "fakeProjectID", - "--network", "192.168.1.0/24", "--start", "192.168.1.12", "--end", "192.168.1.24", "--region", "BHS5", "-o", "json") + out, err := cmd.Execute("cloud", "network", "private", "subnet", "create", "pn-123456", "--region", "BHS5", "--cloud-project", "fakeProjectID", + "--name", "my-subnet", "--cidr", "192.168.1.0/24", "--ip-version", "4", "--enable-dhcp", "--enable-gateway-ip", "-o", "json") require.CmpNoError(err) assert.Cmp(json.RawMessage(out), td.JSON(`{ "message": "✅ Subnet 5e625f90-9ec3-11f0-9f75-0050568ce122 created successfully", @@ -197,13 +178,13 @@ func (ms *MockSuite) TestCloudPrivateNetworkSubnetCreateCmd(assert, require *td. "cidr": "192.168.1.0/24", "gatewayIp": "192.168.1.1", "id": "5e625f90-9ec3-11f0-9f75-0050568ce122", - "ipPools": [ + "name": "my-subnet", + "ipVersion": 4, + "dhcpEnabled": true, + "allocationPools": [ { - "dhcp": false, - "end": "192.168.1.24", - "network": "192.168.1.0/24", - "region": "BHS5", - "start": "192.168.1.12" + "start": "192.168.1.2", + "end": "192.168.1.254" } ] } diff --git a/internal/services/browser/api.go b/internal/services/browser/api.go index ae8d38a8..43d1b79d 100644 --- a/internal/services/browser/api.go +++ b/internal/services/browser/api.go @@ -814,7 +814,7 @@ func (m Model) fetchBlockStorageData() dataLoadedMsg { } } -// fetchPrivateNetworksData fetches private networks +// fetchPrivateNetworksData fetches private networks across all regions func (m Model) fetchPrivateNetworksData() dataLoadedMsg { if m.cloudProject == "" { return dataLoadedMsg{ @@ -822,17 +822,41 @@ func (m Model) fetchPrivateNetworksData() dataLoadedMsg { } } + // Fetch all regions + var regionNames []string + regionEndpoint := fmt.Sprintf("/v1/cloud/project/%s/region", m.cloudProject) + if err := httpLib.Client.Get(regionEndpoint, ®ionNames); err != nil { + return dataLoadedMsg{err: err} + } + + regions := make([]any, len(regionNames)) + for i, r := range regionNames { + regions[i] = r + } + + // Fetch networks in all regions + allRegionNetworks, err := httpLib.FetchObjectsParallel[[]map[string]any](regionEndpoint+"/%s/network", regions, true) + if err != nil { + return dataLoadedMsg{err: err} + } + + // Flatten and filter private networks var networks []map[string]interface{} - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private", m.cloudProject) - err := httpLib.Client.Get(endpoint, &networks) + for _, regionNetworks := range allRegionNetworks { + for _, network := range regionNetworks { + if v, ok := network["visibility"]; ok && v == "private" { + networks = append(networks, network) + } + } + } return dataLoadedMsg{ data: networks, - err: err, + err: nil, } } -// fetchPublicNetworksData fetches public networks +// fetchPublicNetworksData fetches public networks across all regions func (m Model) fetchPublicNetworksData() dataLoadedMsg { if m.cloudProject == "" { return dataLoadedMsg{ @@ -840,13 +864,37 @@ func (m Model) fetchPublicNetworksData() dataLoadedMsg { } } + // Fetch all regions + var regionNames []string + regionEndpoint := fmt.Sprintf("/v1/cloud/project/%s/region", m.cloudProject) + if err := httpLib.Client.Get(regionEndpoint, ®ionNames); err != nil { + return dataLoadedMsg{err: err} + } + + regions := make([]any, len(regionNames)) + for i, r := range regionNames { + regions[i] = r + } + + // Fetch networks in all regions + allRegionNetworks, err := httpLib.FetchObjectsParallel[[]map[string]any](regionEndpoint+"/%s/network", regions, true) + if err != nil { + return dataLoadedMsg{err: err} + } + + // Flatten and filter public networks var networks []map[string]interface{} - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/public", m.cloudProject) - err := httpLib.Client.Get(endpoint, &networks) + for _, regionNetworks := range allRegionNetworks { + for _, network := range regionNetworks { + if v, ok := network["visibility"]; ok && v == "public" { + networks = append(networks, network) + } + } + } return dataLoadedMsg{ data: networks, - err: err, + err: nil, } } @@ -1765,7 +1813,7 @@ func (m Model) fetchPrivateNetworks() tea.Cmd { } var networks []map[string]interface{} - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private", m.cloudProject) + endpoint := fmt.Sprintf("/v1/cloud/project/%s/region/%s/network", m.cloudProject, m.wizard.selectedRegion) err := httpLib.Client.Get(endpoint, &networks) if err != nil { @@ -1775,10 +1823,16 @@ func (m Model) fetchPrivateNetworks() tea.Cmd { } } - // Subnets are not needed for the wizard display - just networks + // Filter to private networks only + var privateNetworks []map[string]interface{} + for _, network := range networks { + if v, ok := network["visibility"]; ok && v == "private" { + privateNetworks = append(privateNetworks, network) + } + } return privateNetworksLoadedMsg{ - networks: networks, + networks: privateNetworks, err: err, } } @@ -1794,22 +1848,8 @@ func (m Model) handlePrivateNetworksLoaded(msg privateNetworksLoadedMsg) (tea.Mo return m, nil } - // Filter networks available in the selected region - var availableNetworks []map[string]interface{} - for _, network := range msg.networks { - // Check if network has regions that include selected region - if regions, ok := network["regions"].([]interface{}); ok { - for _, r := range regions { - if regionMap, ok := r.(map[string]interface{}); ok { - regionName := getString(regionMap, "region") - if regionName == m.wizard.selectedRegion { - availableNetworks = append(availableNetworks, network) - break - } - } - } - } - } + // Networks are already filtered by region (fetched from regional endpoint) + availableNetworks := msg.networks // Build the list: No Network, Create New, then existing networks noNetworkOption := map[string]interface{}{ @@ -2098,8 +2138,8 @@ func (m Model) cleanupCreatedResources() tea.Cmd { // Delete network if created (this will also delete the subnet) if m.wizard.createdNetworkId != "" { - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private/%s", - m.cloudProject, m.wizard.createdNetworkId) + endpoint := fmt.Sprintf("/v1/cloud/project/%s/region/%s/network/%s", + m.cloudProject, m.wizard.selectedRegion, m.wizard.createdNetworkId) if err := httpLib.Client.Delete(endpoint, nil); err != nil { errors = append(errors, fmt.Sprintf("Network: %s", err)) } else { @@ -2662,29 +2702,38 @@ func (m Model) createPrivateNetwork() tea.Cmd { return networkCreatedMsg{err: fmt.Errorf("no cloud project selected")} } - // Step 1: Create the private network + // Step 1: Create the private network using the regional API networkBody := map[string]interface{}{ - "name": m.wizard.newNetworkName, - "vlanId": m.wizard.newNetworkVlanId, - "regions": []string{m.wizard.selectedRegion}, + "name": m.wizard.newNetworkName, + "vlanId": m.wizard.newNetworkVlanId, } - var network map[string]interface{} - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private", m.cloudProject) - err := httpLib.Client.Post(endpoint, networkBody, &network) + var operation map[string]interface{} + endpoint := fmt.Sprintf("/v1/cloud/project/%s/region/%s/network", m.cloudProject, m.wizard.selectedRegion) + err := httpLib.Client.Post(endpoint, networkBody, &operation) if err != nil { return networkCreatedMsg{err: fmt.Errorf("failed to create network: %w", err)} } - networkId := getString(network, "id") - if networkId == "" { + // The regional API returns an operation object; extract the resource ID + resourceId := getString(operation, "resourceId") + if resourceId == "" { + // Try to get the ID directly if the response is a network object + resourceId = getString(operation, "id") + } + if resourceId == "" { return networkCreatedMsg{err: fmt.Errorf("network created but ID not returned")} } + network := map[string]interface{}{ + "id": resourceId, + "name": m.wizard.newNetworkName, + } + // Return step message to continue with subnet creation return networkStepMsg{ step: "network_created", - networkId: networkId, + networkId: resourceId, network: network, } } @@ -2739,19 +2788,23 @@ func (m Model) createSubnet(networkId string, network map[string]interface{}) te dhcpEnd := baseIP + ".254" subnetBody := map[string]interface{}{ - "region": m.wizard.selectedRegion, - "network": cidr, - "noGateway": false, - "dhcp": m.wizard.newNetworkDHCP, + "name": m.wizard.newNetworkName + "-subnet", + "cidr": cidr, + "ipVersion": 4, + "enableDhcp": m.wizard.newNetworkDHCP, + "enableGatewayIp": true, + "gatewayIp": gateway, } - // Only add IP pool if DHCP is enabled + // Only add allocation pools if DHCP is enabled if m.wizard.newNetworkDHCP { - subnetBody["start"] = dhcpStart - subnetBody["end"] = dhcpEnd + subnetBody["allocationPools"] = []map[string]string{ + {"start": dhcpStart, "end": dhcpEnd}, + } } - subnetEndpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private/%s/subnet", m.cloudProject, networkId) + subnetEndpoint := fmt.Sprintf("/v1/cloud/project/%s/region/%s/network/%s/subnet", + m.cloudProject, m.wizard.selectedRegion, networkId) // Retry creating subnet with exponential backoff (network needs to activate) var subnet map[string]interface{} diff --git a/internal/services/cloud/cloud_network.go b/internal/services/cloud/cloud_network.go index db720a58..bc12da9e 100644 --- a/internal/services/cloud/cloud_network.go +++ b/internal/services/cloud/cloud_network.go @@ -11,6 +11,7 @@ import ( "log" "net/url" "strings" + "sync" "time" "github.com/ovh/ovhcloud-cli/internal/assets" @@ -23,8 +24,9 @@ import ( ) var ( - cloudprojectNetworkColumnsToDisplay = []string{"id", "name", "openstackId", "region", "status"} - cloudprojectGatewayColumnsToDisplay = []string{"id", "name", "region", "model", "status"} + cloudprojectPrivateNetworkColumnsToDisplay = []string{"id", "name", "region", "visibility", "vlanId"} + cloudprojectPublicNetworkColumnsToDisplay = []string{"id", "name", "region"} + cloudprojectGatewayColumnsToDisplay = []string{"id", "name", "region", "model", "status"} //go:embed templates/cloud_network_private.tmpl cloudNetworkPrivateTemplate string @@ -35,8 +37,11 @@ var ( //go:embed templates/cloud_network_gateway.tmpl cloudGatewayTemplate string - // CloudNetworkName is used to store the name of the cloud network - CloudNetworkName string + //go:embed templates/cloud_network_private_subnet.tmpl + cloudNetworkPrivateSubnetTemplate string + + // CloudNetworkRegionFilter is used to filter networks by region + CloudNetworkRegionFilter string //go:embed parameter-samples/private-network-create.json PrivateNetworkCreationExample string @@ -74,42 +79,24 @@ var ( } CloudNetworkSpec struct { - Name string `json:"name,omitempty"` - VlanId int `json:"vlanId,omitempty"` - Gateway struct { - Model string `json:"model,omitempty"` - Name string `json:"name,omitempty"` - } `json:"gateway,omitzero"` - Subnet struct { - Name string `json:"name,omitempty"` - Cidr string `json:"cidr,omitempty"` - EnableDhcp bool `json:"enableDhcp"` - EnableGatewayIp bool `json:"enableGatewayIp"` - GatewayIp string `json:"gatewayIp,omitempty"` - DnsNameServers []string `json:"dnsNameServers,omitempty"` - UseDefaultPublicDNSResolver bool `json:"useDefaultPublicDNSResolver,omitempty"` - IPVersion int `json:"ipVersion,omitempty"` - AllocationPools []PrivateNetworkAllocationPool `json:"allocationPools,omitempty"` - HostRoutes []PrivateNetworkHostRoute `json:"hostRoutes,omitempty"` - - CliAllocationPools []string `json:"-"` - CliHostRoutes []string `json:"-"` - } `json:"subnet,omitzero"` - } - - CloudNetworkSubnetEditSpec struct { - Dhcp bool `json:"dhcp"` - DisableGateway bool `json:"disableGateway"` - GatewayIp string `json:"gatewayIp,omitempty"` + Name string `json:"name,omitempty"` + VlanId int `json:"vlanId,omitempty"` } CloudNetworkSubnetSpec struct { - DHCP bool `json:"dhcp"` - NoGateway bool `json:"noGateway"` - Network string `json:"network"` - Start string `json:"start"` - End string `json:"end"` - Region string `json:"region"` + Name string `json:"name,omitempty"` + Cidr string `json:"cidr,omitempty"` + IPVersion int `json:"ipVersion,omitempty"` + EnableDhcp bool `json:"enableDhcp"` + EnableGatewayIp bool `json:"enableGatewayIp"` + GatewayIp string `json:"gatewayIp,omitempty"` + DnsNameServers []string `json:"dnsNameServers,omitempty"` + UseDefaultPublicDNSResolver bool `json:"useDefaultPublicDNSResolver,omitempty"` + AllocationPools []PrivateNetworkAllocationPool `json:"allocationPools,omitempty"` + HostRoutes []PrivateNetworkHostRoute `json:"hostRoutes,omitempty"` + + CliAllocationPools []string `json:"-"` + CliHostRoutes []string `json:"-"` } GatewayInterfaceSpec struct { @@ -127,119 +114,150 @@ type ( Destination string `json:"destination,omitempty"` NextHop string `json:"nextHop,omitempty"` } - - NetworkRegionDetails struct { - OpenstackID string `json:"openstackId"` - Region string `json:"region"` - } - - PrivateNetwork struct { - ID string `json:"id"` - Regions []NetworkRegionDetails `json:"regions"` - } ) func ListPrivateNetworks(_ *cobra.Command, _ []string) { + listNetworksByVisibility("private", cloudprojectPrivateNetworkColumnsToDisplay) +} + +func listNetworksByVisibility(visibility string, columns []string) { projectID, err := getConfiguredCloudProject() if err != nil { display.OutputError(&flags.OutputFormatConfig, "%s", err) return } - body, err := httpLib.FetchExpandedArray(fmt.Sprintf("/v1/cloud/project/%s/network/private", projectID), "id") + var regions []any + + // If a region filter is set, use only that region + if CloudNetworkRegionFilter != "" { + regions = []any{CloudNetworkRegionFilter} + } else { + // Fetch regions with network feature available + regions, err = getCloudRegionsWithFeatureAvailable(projectID, "network") + if err != nil { + display.OutputError(&flags.OutputFormatConfig, "failed to fetch regions with network feature available: %s", err) + return + } + } + + // Fetch networks in targeted regions + baseURL := fmt.Sprintf("/v1/cloud/project/%s/region", projectID) + networks, err := httpLib.FetchObjectsParallel[[]map[string]any](baseURL+"/%s/network", regions, true) if err != nil { - display.OutputError(&flags.OutputFormatConfig, "failed to fetch results: %s", err) + display.OutputError(&flags.OutputFormatConfig, "failed to fetch networks: %s", err) return } - flattenedBody := []map[string]any{} - - for _, line := range body { - regions := line["regions"].([]any) - - for _, region := range regions { - region := region.(map[string]any) - - flattenedBody = append(flattenedBody, map[string]any{ - "id": line["id"], - "name": line["name"], - "vlanId": line["vlanId"], - "openstackId": region["openstackId"], - "region": region["region"], - "status": region["status"], - "type": line["type"], - }) + // Flatten networks in a single array and filter by visibility + var allNetworks []map[string]any + for i, regionNetworks := range networks { + for _, network := range regionNetworks { + if v, ok := network["visibility"]; ok && v == visibility { + // Ensure the region field is set for table display + if _, ok := network["region"]; !ok { + network["region"] = fmt.Sprint(regions[i]) + } + allNetworks = append(allNetworks, network) + } } } - body, err = filtersLib.FilterLines(flattenedBody, flags.GenericFilters) + // Filter results + allNetworks, err = filtersLib.FilterLines(allNetworks, flags.GenericFilters) if err != nil { display.OutputError(&flags.OutputFormatConfig, "failed to filter results: %s", err) return } - display.RenderTable(body, cloudprojectNetworkColumnsToDisplay, &flags.OutputFormatConfig) + display.RenderTable(allNetworks, columns, &flags.OutputFormatConfig) } -func GetPrivateNetwork(_ *cobra.Command, args []string) { +func findNetwork(networkID string) (string, map[string]any, error) { projectID, err := getConfiguredCloudProject() if err != nil { - display.OutputError(&flags.OutputFormatConfig, "%s", err) - return + return "", nil, err } - path := fmt.Sprintf("/v1/cloud/project/%s/network/private/%s", projectID, url.PathEscape(args[0])) - var object map[string]any - if err := httpLib.Client.Get(path, &object); err != nil { - display.OutputError(&flags.OutputFormatConfig, "error fetching %s: %s", path, err) - return + // If a region is provided, go directly to that region + if CloudNetworkRegionFilter != "" { + var ( + network map[string]any + endpoint = fmt.Sprintf("/v1/cloud/project/%s/region/%s/network/%s", + projectID, url.PathEscape(CloudNetworkRegionFilter), url.PathEscape(networkID)) + ) + if err := httpLib.Client.Get(endpoint, &network); err != nil { + return "", nil, fmt.Errorf("network %s not found in region %s: %w", networkID, CloudNetworkRegionFilter, err) + } + return endpoint, network, nil } - for _, region := range object["regions"].([]any) { - region := region.(map[string]any) + // Fetch regions with network feature available + regions, err := getCloudRegionsWithFeatureAvailable(projectID, "network") + if err != nil { + return "", nil, fmt.Errorf("failed to fetch regions with network feature available: %w", err) + } - // Skip regions without openstackId - if openstackId, ok := region["openstackId"]; !ok || openstackId == nil { - continue - } + // Search for the given network in all regions in parallel + type networkResult struct { + endpoint string + network map[string]any + } - // Fetch subnets of region network - path = fmt.Sprintf("/v1/cloud/project/%s/region/%s/network/%s/subnet", - projectID, - url.PathEscape(region["region"].(string)), - url.PathEscape(region["openstackId"].(string)), - ) - var subnets []map[string]any - if err := httpLib.Client.Get(path, &subnets); err != nil { - display.OutputError(&flags.OutputFormatConfig, "error fetching %s: %s", path, err) - return - } + var ( + wg sync.WaitGroup + result = make(chan networkResult, 1) + ) + + for _, region := range regions { + wg.Add(1) + go func(r string) { + defer wg.Done() + var ( + network map[string]any + endpoint = fmt.Sprintf("/v1/cloud/project/%s/region/%s/network/%s", + projectID, url.PathEscape(r), url.PathEscape(networkID)) + ) + if err := httpLib.Client.Get(endpoint, &network); err == nil { + select { + case result <- networkResult{endpoint: endpoint, network: network}: + default: + } + } + }(region.(string)) + } + + // Close the channel once all goroutines are done + go func() { + wg.Wait() + close(result) + }() - region["subnets"] = subnets + if found, ok := <-result; ok { + return found.endpoint, found.network, nil } - display.OutputObject(object, args[0], cloudNetworkPrivateTemplate, &flags.OutputFormatConfig) + return "", nil, errors.New("no network found with given ID") } -func EditPrivateNetwork(cmd *cobra.Command, args []string) { - projectID, err := getConfiguredCloudProject() +func GetPrivateNetwork(_ *cobra.Command, args []string) { + networkID := args[0] + foundURL, object, err := findNetwork(networkID) if err != nil { display.OutputError(&flags.OutputFormatConfig, "%s", err) return } - if err := common.EditResource( - cmd, - "/cloud/project/{serviceName}/network/private/{networkId}", - fmt.Sprintf("/v1/cloud/project/%s/network/private/%s", projectID, url.PathEscape(args[0])), - map[string]any{ - "name": CloudNetworkName, - }, - assets.CloudOpenapiSchema, - ); err != nil { - display.OutputError(&flags.OutputFormatConfig, "%s", err) + // Fetch subnets of the network + var subnets []map[string]any + if err := httpLib.Client.Get(foundURL+"/subnet", &subnets); err != nil { + display.OutputError(&flags.OutputFormatConfig, "error fetching subnets: %s", err) return } + + object["subnets"] = subnets + + display.OutputObject(object, networkID, cloudNetworkPrivateTemplate, &flags.OutputFormatConfig) } func CreatePrivateNetwork(cmd *cobra.Command, args []string) { @@ -249,32 +267,6 @@ func CreatePrivateNetwork(cmd *cobra.Command, args []string) { return } - // Transform CLI flags into the CloudNetworkSpec structure - for _, allocationPool := range CloudNetworkSpec.Subnet.CliAllocationPools { - parts := strings.Split(allocationPool, ":") - if len(parts) != 2 { - display.OutputError(&flags.OutputFormatConfig, "invalid allocation pool format, expected start:end, got %s", allocationPool) - return - } - - CloudNetworkSpec.Subnet.AllocationPools = append(CloudNetworkSpec.Subnet.AllocationPools, PrivateNetworkAllocationPool{ - Start: parts[0], - End: parts[1], - }) - } - for _, hostRoute := range CloudNetworkSpec.Subnet.CliHostRoutes { - parts := strings.Split(hostRoute, ":") - if len(parts) != 2 { - display.OutputError(&flags.OutputFormatConfig, "invalid host route format, expected destination:nextHop, got %s", hostRoute) - return - } - - CloudNetworkSpec.Subnet.HostRoutes = append(CloudNetworkSpec.Subnet.HostRoutes, PrivateNetworkHostRoute{ - Destination: parts[0], - NextHop: parts[1], - }) - } - // Create resource region := args[0] endpoint := fmt.Sprintf("/v1/cloud/project/%s/region/%s/network", projectID, url.PathEscape(region)) @@ -285,7 +277,7 @@ func CreatePrivateNetwork(cmd *cobra.Command, args []string) { PrivateNetworkCreationExample, CloudNetworkSpec, assets.CloudOpenapiSchema, - []string{"name", "subnet"}) + []string{"name"}) if err != nil { display.OutputError(&flags.OutputFormatConfig, "failed to create private network: %s", err) return @@ -304,170 +296,96 @@ You can check the status of the operation with: 'ovhcloud cloud operation get %[ return } - // Fetch all private networks - var networks []PrivateNetwork - if err := httpLib.Client.Get(fmt.Sprintf("/v1/cloud/project/%s/network/private", projectID), &networks); err != nil { - display.OutputError(&flags.OutputFormatConfig, "failed to fetch private networks: %s", err) - return - } - - // Find the created network - var ( - foundNetwork *PrivateNetwork - foundRegionNetwork *NetworkRegionDetails - ) - -eachNetwork: - for _, network := range networks { - for _, regionDetails := range network.Regions { - if regionDetails.OpenstackID == networkID && regionDetails.Region == region { - foundNetwork = &network - foundRegionNetwork = ®ionDetails - break eachNetwork - } - } - } - - if foundNetwork == nil { - display.OutputError(&flags.OutputFormatConfig, "created network not found, this is unexpected") - return - } - - // Fetch subnets of created network - endpoint = fmt.Sprintf("/v1/cloud/project/%s/region/%s/network/%s/subnet", projectID, url.PathEscape(region), url.PathEscape(foundRegionNetwork.OpenstackID)) - var subnets []map[string]any - if err := httpLib.Client.Get(endpoint, &subnets); err != nil { - display.OutputError(&flags.OutputFormatConfig, "failed to fetch subnets of created network: %s", err) - return - } - - // Fetch gateway of created subnets and prepare output - var outputSubnets []map[string]any - for _, subnet := range subnets { - endpoint = fmt.Sprintf("/v1/cloud/project/%s/region/%s/gateway?subnetId=%s", - projectID, - url.PathEscape(region), - url.PathEscape(subnet["id"].(string)), - ) - - var gateways []map[string]any - if err := httpLib.Client.Get(endpoint, &gateways); err != nil { - display.OutputError(&flags.OutputFormatConfig, "failed to fetch gateways of created network: %s", err) - return - } - - var outputGateways []map[string]any - for _, gateway := range gateways { - outputGateway := map[string]any{ - "id": gateway["id"], - "name": gateway["name"], - } - outputGateways = append(outputGateways, outputGateway) - } - - outputSubnets = append(outputSubnets, map[string]any{ - "id": subnet["id"], - "name": subnet["name"], - "gateways": outputGateways, - }) - } - - networkObject := map[string]any{ - "id": foundNetwork.ID, - "openstackId": foundRegionNetwork.OpenstackID, - "region": foundRegionNetwork.Region, - "subnets": outputSubnets, - } - - display.OutputInfo(&flags.OutputFormatConfig, networkObject, "✅ Network %s created successfully (Openstack ID: %s)", foundNetwork.ID, foundRegionNetwork.OpenstackID) + display.OutputInfo(&flags.OutputFormatConfig, nil, "✅ Network %s created successfully in region %s", networkID, region) } func DeletePrivateNetwork(_ *cobra.Command, args []string) { - projectID, err := getConfiguredCloudProject() + networkID := args[0] + foundURL, _, err := findNetwork(networkID) if err != nil { display.OutputError(&flags.OutputFormatConfig, "%s", err) return } - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private/%s", projectID, url.PathEscape(args[0])) - if err := httpLib.Client.Delete(endpoint, nil); err != nil { + if err := httpLib.Client.Delete(foundURL, nil); err != nil { display.OutputError(&flags.OutputFormatConfig, "failed to delete private network: %s", err) return } - display.OutputInfo(&flags.OutputFormatConfig, nil, "✅ Private network %s deleted successfully", args[0]) + display.OutputInfo(&flags.OutputFormatConfig, nil, "✅ Private network %s deleted successfully", networkID) } -func DeletePrivateNetworkRegion(_ *cobra.Command, args []string) { - projectID, err := getConfiguredCloudProject() +func ListPrivateNetworkSubnets(_ *cobra.Command, args []string) { + networkID := args[0] + foundURL, _, err := findNetwork(networkID) if err != nil { display.OutputError(&flags.OutputFormatConfig, "%s", err) return } - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private/%s/region/%s", projectID, url.PathEscape(args[0]), url.PathEscape(args[1])) - if err := httpLib.Client.Delete(endpoint, nil); err != nil { - display.OutputError(&flags.OutputFormatConfig, "failed to delete private network region: %s", err) - return - } - - display.OutputInfo(&flags.OutputFormatConfig, nil, "✅ Private network %s region %s deleted successfully", args[0], args[1]) + common.ManageListRequestNoExpand(foundURL+"/subnet", []string{"id", "name", "cidr", "gatewayIp", "dhcpEnabled", "ipVersion"}, flags.GenericFilters) } -func AddPrivateNetworkRegion(_ *cobra.Command, args []string) { - projectID, err := getConfiguredCloudProject() +func GetPrivateNetworkSubnet(_ *cobra.Command, args []string) { + networkID := args[0] + subnetID := args[1] + foundURL, _, err := findNetwork(networkID) if err != nil { display.OutputError(&flags.OutputFormatConfig, "%s", err) return } - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private/%s/region", projectID, url.PathEscape(args[0])) - if err := httpLib.Client.Post(endpoint, map[string]string{"region": args[1]}, nil); err != nil { - display.OutputError(&flags.OutputFormatConfig, "failed to add private network region: %s", err) + var object map[string]any + if err := httpLib.Client.Get(foundURL+"/subnet/"+url.PathEscape(subnetID), &object); err != nil { + display.OutputError(&flags.OutputFormatConfig, "error fetching subnet %s: %s", subnetID, err) return } - display.OutputInfo(&flags.OutputFormatConfig, nil, "✅ Private network %s region %s added successfully", args[0], args[1]) + display.OutputObject(object, subnetID, cloudNetworkPrivateSubnetTemplate, &flags.OutputFormatConfig) } -func ListPrivateNetworkSubnets(_ *cobra.Command, args []string) { - projectID, err := getConfiguredCloudProject() +func CreatePrivateNetworkSubnet(cmd *cobra.Command, args []string) { + networkID := args[0] + foundURL, _, err := findNetwork(networkID) if err != nil { display.OutputError(&flags.OutputFormatConfig, "%s", err) return } - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private/%s/subnet", projectID, url.PathEscape(args[0])) - - common.ManageListRequestNoExpand(endpoint, []string{"id", "cidr", "gatewayIp", "dhcpEnabled"}, flags.GenericFilters) -} + // Transform CLI flags into the CloudNetworkSubnetSpec structure + for _, allocationPool := range CloudNetworkSubnetSpec.CliAllocationPools { + parts := strings.Split(allocationPool, ":") + if len(parts) != 2 { + display.OutputError(&flags.OutputFormatConfig, "invalid allocation pool format, expected start:end, got %s", allocationPool) + return + } -func GetPrivateNetworkSubnet(_ *cobra.Command, args []string) { - projectID, err := getConfiguredCloudProject() - if err != nil { - display.OutputError(&flags.OutputFormatConfig, "%s", err) - return + CloudNetworkSubnetSpec.AllocationPools = append(CloudNetworkSubnetSpec.AllocationPools, PrivateNetworkAllocationPool{ + Start: parts[0], + End: parts[1], + }) } + for _, hostRoute := range CloudNetworkSubnetSpec.CliHostRoutes { + parts := strings.Split(hostRoute, ":") + if len(parts) != 2 { + display.OutputError(&flags.OutputFormatConfig, "invalid host route format, expected destination:nextHop, got %s", hostRoute) + return + } - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private/%s/subnet", projectID, url.PathEscape(args[0])) - common.ManageObjectRequest(endpoint, args[1], "") -} - -func CreatePrivateNetworkSubnet(cmd *cobra.Command, args []string) { - projectID, err := getConfiguredCloudProject() - if err != nil { - display.OutputError(&flags.OutputFormatConfig, "%s", err) - return + CloudNetworkSubnetSpec.HostRoutes = append(CloudNetworkSubnetSpec.HostRoutes, PrivateNetworkHostRoute{ + Destination: parts[0], + NextHop: parts[1], + }) } subnet, err := common.CreateResource( cmd, - "/cloud/project/{serviceName}/network/private/{networkId}/subnet", - fmt.Sprintf("/v1/cloud/project/%s/network/private/%s/subnet", projectID, url.PathEscape(args[0])), + "/cloud/project/{serviceName}/region/{regionName}/network/{networkId}/subnet", + foundURL+"/subnet", PrivateNetworkSubnetCreationExample, CloudNetworkSubnetSpec, assets.CloudOpenapiSchema, - []string{"dhcp", "network", "start", "end", "region", "noGateway"}) + []string{"name", "cidr", "ipVersion"}) if err != nil { display.OutputError(&flags.OutputFormatConfig, "failed to create subnet: %s", err) return @@ -476,133 +394,46 @@ func CreatePrivateNetworkSubnet(cmd *cobra.Command, args []string) { display.OutputInfo(&flags.OutputFormatConfig, subnet, "✅ Subnet %s created successfully", subnet["id"]) } -func EditPrivateNetworkSubnet(cmd *cobra.Command, args []string) { - projectID, err := getConfiguredCloudProject() - if err != nil { - display.OutputError(&flags.OutputFormatConfig, "%s", err) - return - } - - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private/%s/subnet/%s", projectID, url.PathEscape(args[0]), url.PathEscape(args[1])) - - if err := common.EditResource( - cmd, - "/cloud/project/{serviceName}/network/private/{networkId}/subnet/{subnetId}", - endpoint, - CloudNetworkSubnetEditSpec, - assets.CloudOpenapiSchema, - ); err != nil { - display.OutputError(&flags.OutputFormatConfig, "%s", err) - return - } -} - func DeletePrivateNetworkSubnet(_ *cobra.Command, args []string) { - projectID, err := getConfiguredCloudProject() + networkID := args[0] + subnetID := args[1] + foundURL, _, err := findNetwork(networkID) if err != nil { display.OutputError(&flags.OutputFormatConfig, "%s", err) return } - endpoint := fmt.Sprintf("/v1/cloud/project/%s/network/private/%s/subnet/%s", projectID, url.PathEscape(args[0]), url.PathEscape(args[1])) + endpoint := foundURL + "/subnet/" + url.PathEscape(subnetID) if err := httpLib.Client.Delete(endpoint, nil); err != nil { display.OutputError(&flags.OutputFormatConfig, "failed to delete private network subnet: %s", err) return } - display.OutputInfo(&flags.OutputFormatConfig, nil, "✅ Private network %s subnet %s deleted successfully", args[0], args[1]) + display.OutputInfo(&flags.OutputFormatConfig, nil, "✅ Subnet %s deleted successfully from network %s", subnetID, networkID) } func ListPublicNetworks(_ *cobra.Command, _ []string) { - projectID, err := getConfiguredCloudProject() - if err != nil { - display.OutputError(&flags.OutputFormatConfig, "%s", err) - return - } - - var body []map[string]any - err = httpLib.Client.Get(fmt.Sprintf("/v1/cloud/project/%s/network/public", projectID), &body) - if err != nil { - display.OutputError(&flags.OutputFormatConfig, "failed to fetch results: %s", err) - return - } - - flattenedBody := []map[string]any{} - - for _, line := range body { - regions := line["regions"].([]any) - - for _, region := range regions { - region := region.(map[string]any) - - flattenedBody = append(flattenedBody, map[string]any{ - "id": line["id"], - "name": line["name"], - "vlanId": line["vlanId"], - "openstackId": region["openstackId"], - "region": region["region"], - "status": region["status"], - "type": line["type"], - }) - } - } - - body, err = filtersLib.FilterLines(flattenedBody, flags.GenericFilters) - if err != nil { - display.OutputError(&flags.OutputFormatConfig, "failed to filter results: %s", err) - return - } - - display.RenderTable(body, cloudprojectNetworkColumnsToDisplay, &flags.OutputFormatConfig) + listNetworksByVisibility("public", cloudprojectPublicNetworkColumnsToDisplay) } func GetPublicNetwork(_ *cobra.Command, args []string) { - projectID, err := getConfiguredCloudProject() + networkID := args[0] + foundURL, object, err := findNetwork(networkID) if err != nil { display.OutputError(&flags.OutputFormatConfig, "%s", err) return } - var allNetworks []map[string]any - err = httpLib.Client.Get(fmt.Sprintf("/v1/cloud/project/%s/network/public", projectID), &allNetworks) - if err != nil { - display.OutputError(&flags.OutputFormatConfig, "failed to fetch public networks: %s", err) - return - } - - var object map[string]any - for _, network := range allNetworks { - networkID := network["id"].(string) - if networkID == args[0] { - object = network - break - } - } - - if object == nil { - display.OutputError(&flags.OutputFormatConfig, "no public network found with ID %q", args[0]) + // Fetch subnets of the network + var subnets []map[string]any + if err := httpLib.Client.Get(foundURL+"/subnet", &subnets); err != nil { + display.OutputError(&flags.OutputFormatConfig, "error fetching subnets: %s", err) return } - for _, region := range object["regions"].([]any) { - region := region.(map[string]any) - - // Fetch subnets of region network - path := fmt.Sprintf("/v1/cloud/project/%s/region/%s/network/%s/subnet", - projectID, - url.PathEscape(region["region"].(string)), - url.PathEscape(region["openstackId"].(string)), - ) - var subnets []map[string]any - if err := httpLib.Client.Get(path, &subnets); err != nil { - display.OutputError(&flags.OutputFormatConfig, "error fetching %s: %s", path, err) - return - } - - region["subnets"] = subnets - } + object["subnets"] = subnets - display.OutputObject(object, args[0], cloudNetworkPublicTemplate, &flags.OutputFormatConfig) + display.OutputObject(object, networkID, cloudNetworkPublicTemplate, &flags.OutputFormatConfig) } func ListGateways(_ *cobra.Command, _ []string) { diff --git a/internal/services/cloud/parameter-samples/private-network-create.json b/internal/services/cloud/parameter-samples/private-network-create.json index 33543c48..30f1f8f2 100644 --- a/internal/services/cloud/parameter-samples/private-network-create.json +++ b/internal/services/cloud/parameter-samples/private-network-create.json @@ -1,31 +1,4 @@ { - "gateway": { - "model": "s", - "name": "mygateway" - }, "name": "mynetwork", - "subnet": { - "allocationPools": [ - { - "end": "10.0.2.0", - "start": "10.0.2.0" - } - ], - "cidr": "10.0.2.0/24", - "dnsNameServers": [ - "10.0.2.0" - ], - "enableDhcp": false, - "enableGatewayIp": true, - "gatewayIp": "10.0.2.0", - "hostRoutes": [ - { - "destination": "10.0.2.0/24", - "nextHop": "10.0.2.0" - } - ], - "ipVersion": 4, - "name": "mysubnet", - "useDefaultPublicDNSResolver": false - } -} \ No newline at end of file + "vlanId": 0 +} diff --git a/internal/services/cloud/parameter-samples/private-network-subnet-create.json b/internal/services/cloud/parameter-samples/private-network-subnet-create.json index 9b02269b..5b4ff20d 100644 --- a/internal/services/cloud/parameter-samples/private-network-subnet-create.json +++ b/internal/services/cloud/parameter-samples/private-network-subnet-create.json @@ -1,8 +1,15 @@ { - "dhcp": false, - "end": "192.168.1.24", - "network": "192.168.1.0/24", - "noGateway": false, - "region": "GRA9", - "start": "192.168.1.0" + "name": "my-subnet", + "cidr": "192.168.1.0/24", + "ipVersion": 4, + "enableDhcp": true, + "enableGatewayIp": true, + "gatewayIp": "192.168.1.1", + "useDefaultPublicDNSResolver": false, + "allocationPools": [ + { + "start": "192.168.1.2", + "end": "192.168.1.254" + } + ] } \ No newline at end of file diff --git a/internal/services/cloud/templates/cloud_network_private.tmpl b/internal/services/cloud/templates/cloud_network_private.tmpl index 7f4cc589..1f270551 100644 --- a/internal/services/cloud/templates/cloud_network_private.tmpl +++ b/internal/services/cloud/templates/cloud_network_private.tmpl @@ -5,23 +5,15 @@ _{{index .Result "name"}}_ ## General information -**Status**: {{index .Result "status"}} -**Type**: {{index .Result "type"}} -**vlan ID**: {{index .Result "vlanId"}} +**Status**: {{index .Result "status"}} +**Visibility**: {{index .Result "visibility"}} +**vlan ID**: {{index .Result "vlanId"}} -## Regional networks information +{{if index .Result "subnets"}}## Subnets -{{range index .Result "regions"}} -### Network {{.openstackId}} - -**Region**: {{.region}} -**Status**: {{.status}} - -{{if .subnets}}**Subnets** -| Name | CIDR | Gateway IP | -| --- | --- | --- | -{{ range .subnets }}| {{.name}} | {{.cidr}} | {{.gatewayIp}} | -{{end}} +| ID | Name | CIDR | Gateway IP | DHCP | IP Version | +| --- | --- | --- | --- | --- | --- | +{{range index .Result "subnets"}}| {{.id}} | {{.name}} | {{.cidr}} | {{.gatewayIp}} | {{.dhcpEnabled}} | {{.ipVersion}} | {{end}} {{end}} diff --git a/internal/services/cloud/templates/cloud_network_private_subnet.tmpl b/internal/services/cloud/templates/cloud_network_private_subnet.tmpl new file mode 100644 index 00000000..76c4060e --- /dev/null +++ b/internal/services/cloud/templates/cloud_network_private_subnet.tmpl @@ -0,0 +1,33 @@ +🚀 Subnet {{.ServiceName}} +======= + +_{{index .Result "name"}}_ + +## General information + +**CIDR**: {{index .Result "cidr"}} +**IP Version**: {{index .Result "ipVersion"}} +**Gateway IP**: {{index .Result "gatewayIp"}} +**DHCP Enabled**: {{index .Result "dhcpEnabled"}} + +## DNS Name Servers + +{{ range index .Result "dnsNameServers" }}- {{.}} +{{end}} + +## Allocation Pools + +| Start | End | +| --- | --- | +{{ range index .Result "allocationPools" }}| {{.start}} | {{.end}} | +{{end}} + +{{if index .Result "hostRoutes"}}## Host Routes + +| Destination | Next Hop | +| --- | --- | +{{ range index .Result "hostRoutes" }}| {{.destination}} | {{.nextHop}} | +{{end}} +{{end}} + +💡 Use option --json or --yaml to get the raw output with all information