diff --git a/hcloud/network.go b/hcloud/network.go index 5a196816..cf85a554 100644 --- a/hcloud/network.go +++ b/hcloud/network.go @@ -45,6 +45,9 @@ type Network struct { Servers []*Server Protection NetworkProtection Labels map[string]string + + // ExposeRoutesToVSwitch indicates if the routes from this network should be exposed to the vSwitch connection. + ExposeRoutesToVSwitch bool } // NetworkSubnet represents a subnet of a network in the Hetzner Cloud. @@ -190,6 +193,9 @@ func (c *NetworkClient) Delete(ctx context.Context, network *Network) (*Response type NetworkUpdateOpts struct { Name string Labels map[string]string + // ExposeRoutesToVSwitch indicates if the routes from this network should be exposed to the vSwitch connection. + // The exposing only takes effect if a vSwitch connection is active. + ExposeRoutesToVSwitch *bool } // Update updates a network. @@ -200,6 +206,10 @@ func (c *NetworkClient) Update(ctx context.Context, network *Network, opts Netwo if opts.Labels != nil { reqBody.Labels = &opts.Labels } + if opts.ExposeRoutesToVSwitch != nil { + reqBody.ExposeRoutesToVSwitch = opts.ExposeRoutesToVSwitch + } + reqBodyData, err := json.Marshal(reqBody) if err != nil { return nil, nil, err @@ -226,6 +236,9 @@ type NetworkCreateOpts struct { Subnets []NetworkSubnet Routes []NetworkRoute Labels map[string]string + // ExposeRoutesToVSwitch indicates if the routes from this network should be exposed to the vSwitch connection. + // The exposing only takes effect if a vSwitch connection is active. + ExposeRoutesToVSwitch bool } // Validate checks if options are valid. @@ -245,8 +258,9 @@ func (c *NetworkClient) Create(ctx context.Context, opts NetworkCreateOpts) (*Ne return nil, nil, err } reqBody := schema.NetworkCreateRequest{ - Name: opts.Name, - IPRange: opts.IPRange.String(), + Name: opts.Name, + IPRange: opts.IPRange.String(), + ExposeRoutesToVSwitch: opts.ExposeRoutesToVSwitch, } for _, subnet := range opts.Subnets { s := schema.NetworkSubnet{ diff --git a/hcloud/network_test.go b/hcloud/network_test.go index d5f5d005..b06f3346 100644 --- a/hcloud/network_test.go +++ b/hcloud/network_test.go @@ -213,6 +213,36 @@ func TestNetworkCreate(t *testing.T) { t.Fatal(err) } }) + + t.Run("optional fields", func(t *testing.T) { + env := newTestEnv() + defer env.Teardown() + + env.Mux.HandleFunc("/networks", func(w http.ResponseWriter, r *http.Request) { + var reqBody schema.NetworkCreateRequest + if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { + t.Fatal(err) + } + if reqBody.ExposeRoutesToVSwitch != true { + t.Errorf("unexpected ExposeRoutesToVSwitch: %v", reqBody.ExposeRoutesToVSwitch) + } + + json.NewEncoder(w).Encode(schema.NetworkCreateResponse{ + Network: schema.Network{ + ID: 1, + }, + }) + }) + opts := NetworkCreateOpts{ + Name: "my-network", + IPRange: ipRange, + ExposeRoutesToVSwitch: true, + } + _, _, err := env.Client.Network.Create(ctx, opts) + if err != nil { + t.Fatal(err) + } + }) } func TestNetworkDelete(t *testing.T) { @@ -245,7 +275,7 @@ func TestNetworkClientUpdate(t *testing.T) { if r.Method != "PUT" { t.Error("expected PUT") } - var reqBody schema.ServerUpdateRequest + var reqBody schema.NetworkUpdateRequest if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { t.Fatal(err) } @@ -280,7 +310,7 @@ func TestNetworkClientUpdate(t *testing.T) { if r.Method != "PUT" { t.Error("expected PUT") } - var reqBody schema.ServerUpdateRequest + var reqBody schema.NetworkUpdateRequest if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { t.Fatal(err) } @@ -307,6 +337,41 @@ func TestNetworkClientUpdate(t *testing.T) { } }) + t.Run("update expose routes to vswitch", func(t *testing.T) { + env := newTestEnv() + defer env.Teardown() + + env.Mux.HandleFunc("/networks/1", func(w http.ResponseWriter, r *http.Request) { + if r.Method != "PUT" { + t.Error("expected PUT") + } + var reqBody schema.NetworkUpdateRequest + if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { + t.Fatal(err) + } + if reqBody.ExposeRoutesToVSwitch == nil || *reqBody.ExposeRoutesToVSwitch != true { + t.Errorf("unexpected field in request: %v", reqBody.ExposeRoutesToVSwitch) + } + json.NewEncoder(w).Encode(schema.NetworkUpdateResponse{ + Network: schema.Network{ + ID: 1, + }, + }) + }) + + opts := NetworkUpdateOpts{ + ExposeRoutesToVSwitch: Ptr(true), + } + updatedNetwork, _, err := env.Client.Network.Update(ctx, network, opts) + if err != nil { + t.Fatal(err) + } + + if updatedNetwork.ID != 1 { + t.Errorf("unexpected network ID: %v", updatedNetwork.ID) + } + }) + t.Run("no updates", func(t *testing.T) { env := newTestEnv() defer env.Teardown() @@ -315,7 +380,7 @@ func TestNetworkClientUpdate(t *testing.T) { if r.Method != "PUT" { t.Error("expected PUT") } - var reqBody schema.ServerUpdateRequest + var reqBody schema.NetworkUpdateRequest if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { t.Fatal(err) } diff --git a/hcloud/schema.go b/hcloud/schema.go index c76d7de3..f4a93667 100644 --- a/hcloud/schema.go +++ b/hcloud/schema.go @@ -403,7 +403,8 @@ func NetworkFromSchema(s schema.Network) *Network { Protection: NetworkProtection{ Delete: s.Protection.Delete, }, - Labels: map[string]string{}, + Labels: map[string]string{}, + ExposeRoutesToVSwitch: s.ExposeRoutesToVSwitch, } _, n.IPRange, _ = net.ParseCIDR(s.IPRange) diff --git a/hcloud/schema/network.go b/hcloud/schema/network.go index 87e184d9..56bcbea9 100644 --- a/hcloud/schema/network.go +++ b/hcloud/schema/network.go @@ -4,15 +4,16 @@ import "time" // Network defines the schema of a network. type Network struct { - ID int `json:"id"` - Name string `json:"name"` - Created time.Time `json:"created"` - IPRange string `json:"ip_range"` - Subnets []NetworkSubnet `json:"subnets"` - Routes []NetworkRoute `json:"routes"` - Servers []int `json:"servers"` - Protection NetworkProtection `json:"protection"` - Labels map[string]string `json:"labels"` + ID int `json:"id"` + Name string `json:"name"` + Created time.Time `json:"created"` + IPRange string `json:"ip_range"` + Subnets []NetworkSubnet `json:"subnets"` + Routes []NetworkRoute `json:"routes"` + Servers []int `json:"servers"` + Protection NetworkProtection `json:"protection"` + Labels map[string]string `json:"labels"` + ExposeRoutesToVSwitch bool `json:"expose_routes_to_vswitch"` } // NetworkSubnet represents a subnet of a network. @@ -37,8 +38,9 @@ type NetworkProtection struct { // NetworkUpdateRequest defines the schema of the request to update a network. type NetworkUpdateRequest struct { - Name string `json:"name,omitempty"` - Labels *map[string]string `json:"labels,omitempty"` + Name string `json:"name,omitempty"` + Labels *map[string]string `json:"labels,omitempty"` + ExposeRoutesToVSwitch *bool `json:"expose_routes_to_vswitch,omitempty"` } // NetworkUpdateResponse defines the schema of the response when updating a network. @@ -60,11 +62,12 @@ type NetworkGetResponse struct { // NetworkCreateRequest defines the schema of the request to create a network. type NetworkCreateRequest struct { - Name string `json:"name"` - IPRange string `json:"ip_range"` - Subnets []NetworkSubnet `json:"subnets,omitempty"` - Routes []NetworkRoute `json:"routes,omitempty"` - Labels *map[string]string `json:"labels,omitempty"` + Name string `json:"name"` + IPRange string `json:"ip_range"` + Subnets []NetworkSubnet `json:"subnets,omitempty"` + Routes []NetworkRoute `json:"routes,omitempty"` + Labels *map[string]string `json:"labels,omitempty"` + ExposeRoutesToVSwitch bool `json:"expose_routes_to_vswitch"` } // NetworkCreateResponse defines the schema of the response when