Repository: hetznercloud/hcloud-go (go)

Issue:
Implement updating a server
https://docs.hetzner.cloud/#resources-servers-put

Pull request description:
Implement updating servers
Closes #28.

Patch:
diff --git a/hcloud/schema/server.go b/hcloud/schema/server.go
index 3a5f695..91d0615 100644
--- a/hcloud/schema/server.go
+++ b/hcloud/schema/server.go
@@ -82,6 +82,16 @@ type ServerCreateResponse struct {
 	RootPassword *string `json:"root_password"`
 }
 
+// ServerUpdateRequest defines the schema of the request to update a server.
+type ServerUpdateRequest struct {
+	Name string `json:"name,omitempty"`
+}
+
+// ServerUpdateResponse defines the schema of the response when updating a server.
+type ServerUpdateResponse struct {
+	Server Server `json:"server"`
+}
+
 // ServerActionPoweronRequest defines the schema for the request to
 // create a poweron server action.
 type ServerActionPoweronRequest struct{}
diff --git a/hcloud/server.go b/hcloud/server.go
index d3733ed..8b6bbc7 100644
--- a/hcloud/server.go
+++ b/hcloud/server.go
@@ -285,6 +285,35 @@ func (c *ServerClient) Delete(ctx context.Context, server *Server) (*Response, e
 	return c.client.Do(req, nil)
 }
 
+// ServerUpdateOpts specifies options for updating a server.
+type ServerUpdateOpts struct {
+	Name string
+}
+
+// Update updates a server.
+func (c *ServerClient) Update(ctx context.Context, server *Server, opts ServerUpdateOpts) (*Server, *Response, error) {
+	reqBody := schema.ServerUpdateRequest{
+		Name: opts.Name,
+	}
+	reqBodyData, err := json.Marshal(reqBody)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	path := fmt.Sprintf("/servers/%d", server.ID)
+	req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
+	if err != nil {
+		return nil, nil, err
+	}
+
+	respBody := schema.ServerUpdateResponse{}
+	resp, err := c.client.Do(req, &respBody)
+	if err != nil {
+		return nil, resp, err
+	}
+	return ServerFromSchema(respBody.Server), resp, nil
+}
+
 // Poweron starts a server.
 func (c *ServerClient) Poweron(ctx context.Context, server *Server) (*Action, *Response, error) {
 	path := fmt.Sprintf("/servers/%d/actions/poweron", server.ID)


Test patch:
diff --git a/hcloud/server_test.go b/hcloud/server_test.go
index 31a5bd8..f963a5a 100644
--- a/hcloud/server_test.go
+++ b/hcloud/server_test.go
@@ -447,6 +447,81 @@ func TestServersDelete(t *testing.T) {
 	}
 }
 
+func TestServerClientUpdate(t *testing.T) {
+	var (
+		ctx    = context.Background()
+		server = &Server{ID: 1}
+	)
+
+	t.Run("update name", func(t *testing.T) {
+		env := newTestEnv()
+		defer env.Teardown()
+
+		env.Mux.HandleFunc("/servers/1", func(w http.ResponseWriter, r *http.Request) {
+			if r.Method != "PUT" {
+				t.Error("expected PUT")
+			}
+			var reqBody schema.ServerUpdateRequest
+			if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
+				t.Fatal(err)
+			}
+			if reqBody.Name != "test" {
+				t.Errorf("unexpected name: %v", reqBody.Name)
+			}
+			json.NewEncoder(w).Encode(schema.ServerUpdateResponse{
+				Server: schema.Server{
+					ID: 1,
+				},
+			})
+		})
+
+		opts := ServerUpdateOpts{
+			Name: "test",
+		}
+		updatedServer, _, err := env.Client.Server.Update(ctx, server, opts)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if updatedServer.ID != 1 {
+			t.Errorf("unexpected server ID: %v", updatedServer.ID)
+		}
+	})
+
+	t.Run("no updates", func(t *testing.T) {
+		env := newTestEnv()
+		defer env.Teardown()
+
+		env.Mux.HandleFunc("/servers/1", func(w http.ResponseWriter, r *http.Request) {
+			if r.Method != "PUT" {
+				t.Error("expected PUT")
+			}
+			var reqBody schema.ServerUpdateRequest
+			if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
+				t.Fatal(err)
+			}
+			if reqBody.Name != "" {
+				t.Errorf("unexpected no name, but got: %v", reqBody.Name)
+			}
+			json.NewEncoder(w).Encode(schema.ServerUpdateResponse{
+				Server: schema.Server{
+					ID: 1,
+				},
+			})
+		})
+
+		opts := ServerUpdateOpts{}
+		updatedServer, _, err := env.Client.Server.Update(ctx, server, opts)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if updatedServer.ID != 1 {
+			t.Errorf("unexpected server ID: %v", updatedServer.ID)
+		}
+	})
+}
+
 func TestServerClientPoweron(t *testing.T) {
 	env := newTestEnv()
 	defer env.Teardown()