feat: Add subnet+gateway information in output when creating a private network

Signed-off-by: Arthur Amstutz <arthur.amstutz@corp.ovh.com>
This commit is contained in:
Arthur Amstutz 2025-09-26 19:52:27 +00:00
parent 9588435616
commit d5aaecb075
No known key found for this signature in database
GPG key ID: F3C6FC59C43A6EF6
3 changed files with 232 additions and 13 deletions

View file

@ -49,7 +49,7 @@ You can also run the CLI using Docker:
docker run -it --rm -v ovhcloud-cli-config-files:/config ovhcom/ovhcloud-cli login
```
## Install using HomeBrew
## Install using Homebrew
```sh
brew install ovh/tap/ovhcloud-cli

View file

@ -0,0 +1,157 @@
// SPDX-FileCopyrightText: 2025 OVH SAS <opensource@ovh.net>
//
// SPDX-License-Identifier: Apache-2.0
package cmd_test
import (
"net/http"
"github.com/jarcoal/httpmock"
"github.com/maxatome/go-testdeep/td"
"github.com/maxatome/tdhttpmock"
"github.com/ovh/ovhcloud-cli/internal/cmd"
)
func (ms *MockSuite) TestCloudPrivateNetworkCreateCmd(assert, require *td.T) {
httpmock.RegisterMatcherResponder(http.MethodPost,
"https://eu.api.ovh.com/1.0/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
}
}`),
),
httpmock.NewStringResponder(200, `{"id": "operation-12345"}`),
)
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/1.0/cloud/project/fakeProjectID/operation/operation-12345",
httpmock.NewStringResponder(200, `
{
"id": "6610ec10-9b09-11f0-a8ac-0050568ce122",
"action": "network#create",
"createdAt": "2025-09-26T20:43:14.376907+02:00",
"startedAt": "2025-09-26T20:43:14.376907+02:00",
"completedAt": "2025-09-26T20:43:36.631086+02:00",
"progress": 0,
"regions": [
"BHS5"
],
"resourceId": "80c1de3e-9b09-11f0-993b-0050568ce122",
"status": "completed",
"subOperations": [
{
"id": "8c0806ba-9b09-11f0-9a54-0050568ce122",
"action": "gateway#create",
"startedAt": "2025-09-26T20:43:14.376907+02:00",
"completedAt": "2025-09-26T20:43:36.631086+02:00",
"progress": 0,
"regions": [
"BHS5"
],
"resourceId": "97a2703c-9b09-11f0-9b6c-0050568ce122",
"status": "completed"
}
]
}`),
)
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/1.0/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/1.0/cloud/project/fakeProjectID/region/BHS5/network/80c1de3e-9b09-11f0-993b-0050568ce122/subnet",
httpmock.NewStringResponder(200, `[
{
"id": "c59a3fdc-9b0f-11f0-ac97-0050568ce122",
"name": "TestFromTheCLI",
"cidr": "10.0.0.0/24",
"ipVersion": 4,
"dhcpEnabled": false,
"gatewayIp": "10.0.0.1",
"allocationPools": [
{
"start": "10.0.0.2",
"end": "10.0.0.254"
}
]
}
]`),
)
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/1.0/cloud/project/fakeProjectID/region/BHS5/gateway?subnetId=c59a3fdc-9b0f-11f0-ac97-0050568ce122",
httpmock.NewStringResponder(200, `[
{
"id": "e7045f34-8f2b-41a4-a734-97b7b0e323de",
"status": "active",
"name": "TestFromTheCLI",
"interfaces": [
{
"id": "56d17852-9b11-11f0-8d13-0050568ce122",
"ip": "10.0.0.1",
"subnetId": "56d17852-9b11-11f0-8d13-0050568ce122",
"networkId": "c59a3fdc-9b0f-11f0-ac97-0050568ce122"
},
{
"id": "56d17852-9b11-11f0-8d13-0050568ce122",
"ip": "10.0.0.218",
"subnetId": "56d17852-9b11-11f0-8d13-0050568ce122",
"networkId": "c59a3fdc-9b0f-11f0-ac97-0050568ce122"
}
],
"externalInformation": {
"ips": [
{
"ip": "1.2.3.4",
"subnetId": "981c226c-57da-4766-966b-3b45db0cfc84"
}
],
"networkId": "c59a3fdc-9b0f-11f0-ac97-0050568ce122"
},
"region": "BHS5",
"model": "s"
}
]`),
)
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", "--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)'
`)
}

View file

@ -80,8 +80,8 @@ var (
Subnet struct {
Name string `json:"name,omitempty"`
Cidr string `json:"cidr,omitempty"`
EnableDhcp bool `json:"enableDhcp,omitempty"`
EnableGatewayIp bool `json:"enableGatewayIp,omitempty"`
EnableDhcp bool `json:"enableDhcp"`
EnableGatewayIp bool `json:"enableGatewayIp"`
GatewayIp string `json:"gatewayIp,omitempty"`
DnsNameServers []string `json:"dnsNameServers,omitempty"`
UseDefaultPublicDNSResolver bool `json:"useDefaultPublicDNSResolver,omitempty"`
@ -115,6 +115,16 @@ 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) {
@ -283,29 +293,81 @@ You can check the status of the operation with: 'ovhcloud cloud operation get %[
}
// Fetch all private networks
var networks []struct {
ID string `json:"id"`
Regions []struct {
OpenstackID string `json:"openstackId"`
Region string `json:"region"`
} `json:"regions"`
}
var networks []PrivateNetwork
if err := httpLib.Client.Get(fmt.Sprintf("/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 {
display.OutputInfo(&flags.OutputFormatConfig, regionDetails, "✅ Network %s created successfully (Openstack ID: %s)", network.ID, regionDetails.OpenstackID)
return
foundNetwork = &network
foundRegionNetwork = &regionDetails
break eachNetwork
}
}
}
display.OutputError(&flags.OutputFormatConfig, "created network not found, this is unexpected")
if foundNetwork == nil {
display.OutputError(&flags.OutputFormatConfig, "created network not found, this is unexpected")
return
}
// Fetch subnets of created network
endpoint = fmt.Sprintf("/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("/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)
}
func DeletePrivateNetwork(_ *cobra.Command, args []string) {