From f93459d62b88877199331b8a9b1758cf17b95f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20T=C3=B6lle?= Date: Mon, 6 Mar 2023 11:42:23 +0100 Subject: [PATCH 1/2] feat: add ServerClient.RebuildWithResult to return root password The existing method Serverclient.Rebuild did not return the root password and there was no way to add it without breaking API compatibility. Closes #244 --- hcloud/schema/server.go | 3 +- hcloud/server.go | 31 ++++++++++++++--- hcloud/server_test.go | 77 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/hcloud/schema/server.go b/hcloud/schema/server.go index 654ccfc6..4786b1f9 100644 --- a/hcloud/schema/server.go +++ b/hcloud/schema/server.go @@ -262,7 +262,8 @@ type ServerActionRebuildRequest struct { // ServerActionRebuildResponse defines the schema of the response when // creating a rebuild server action. type ServerActionRebuildResponse struct { - Action Action `json:"action"` + Action Action `json:"action"` + RootPassword *string `json:"root_password"` } // ServerActionAttachISORequest defines the schema for the request to diff --git a/hcloud/server.go b/hcloud/server.go index da708a2d..ae044356 100644 --- a/hcloud/server.go +++ b/hcloud/server.go @@ -756,8 +756,23 @@ type ServerRebuildOpts struct { Image *Image } +// ServerRebuildResult is the result of a create server call. +type ServerRebuildResult struct { + Action *Action + RootPassword string +} + // Rebuild rebuilds a server. +// +// Deprecated: Use [ServerClient.RebuildWithResult] instead. func (c *ServerClient) Rebuild(ctx context.Context, server *Server, opts ServerRebuildOpts) (*Action, *Response, error) { + result, resp, err := c.RebuildWithResult(ctx, server, opts) + + return result.Action, resp, err +} + +// RebuildWithResult rebuilds a server. +func (c *ServerClient) RebuildWithResult(ctx context.Context, server *Server, opts ServerRebuildOpts) (ServerRebuildResult, *Response, error) { reqBody := schema.ServerActionRebuildRequest{} if opts.Image.ID != 0 { reqBody.Image = opts.Image.ID @@ -766,21 +781,29 @@ func (c *ServerClient) Rebuild(ctx context.Context, server *Server, opts ServerR } reqBodyData, err := json.Marshal(reqBody) if err != nil { - return nil, nil, err + return ServerRebuildResult{}, nil, err } path := fmt.Sprintf("/servers/%d/actions/rebuild", server.ID) req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) if err != nil { - return nil, nil, err + return ServerRebuildResult{}, nil, err } respBody := schema.ServerActionRebuildResponse{} resp, err := c.client.Do(req, &respBody) if err != nil { - return nil, resp, err + return ServerRebuildResult{}, resp, err } - return ActionFromSchema(respBody.Action), resp, nil + + result := ServerRebuildResult{ + Action: ActionFromSchema(respBody.Action), + } + if respBody.RootPassword != nil { + result.RootPassword = *respBody.RootPassword + } + + return result, resp, nil } // AttachISO attaches an ISO to a server. diff --git a/hcloud/server_test.go b/hcloud/server_test.go index 3b79c308..cb1b821e 100644 --- a/hcloud/server_test.go +++ b/hcloud/server_test.go @@ -1522,6 +1522,83 @@ func TestServerClientRebuild(t *testing.T) { }) } +func TestServerClientRebuildWithResult(t *testing.T) { + var ( + ctx = context.Background() + server = &Server{ID: 1} + ) + + t.Run("with image ID", func(t *testing.T) { + env := newTestEnv() + defer env.Teardown() + + env.Mux.HandleFunc("/servers/1/actions/rebuild", func(w http.ResponseWriter, r *http.Request) { + var reqBody schema.ServerActionRebuildRequest + if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { + t.Fatal(err) + } + if id, ok := reqBody.Image.(float64); !ok || id != 1 { + t.Errorf("unexpected image ID: %v", reqBody.Image) + } + json.NewEncoder(w).Encode(schema.ServerActionRebuildResponse{ + Action: schema.Action{ + ID: 1, + }, + RootPassword: Ptr("hetzner"), + }) + }) + + opts := ServerRebuildOpts{ + Image: &Image{ID: 1}, + } + result, _, err := env.Client.Server.RebuildWithResult(ctx, server, opts) + if err != nil { + t.Fatal(err) + } + if result.Action.ID != 1 { + t.Errorf("unexpected action ID: %d", result.Action.ID) + } + if result.RootPassword != "hetzner" { + t.Errorf("unexpected root password: %s", result.RootPassword) + } + }) + + t.Run("with image name", func(t *testing.T) { + env := newTestEnv() + defer env.Teardown() + + env.Mux.HandleFunc("/servers/1/actions/rebuild", func(w http.ResponseWriter, r *http.Request) { + var reqBody schema.ServerActionRebuildRequest + if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { + t.Fatal(err) + } + if name, ok := reqBody.Image.(string); !ok || name != "debian-9" { + t.Errorf("unexpected image name: %v", reqBody.Image) + } + json.NewEncoder(w).Encode(schema.ServerActionRebuildResponse{ + Action: schema.Action{ + ID: 1, + }, + RootPassword: nil, + }) + }) + + opts := ServerRebuildOpts{ + Image: &Image{Name: "debian-9"}, + } + result, _, err := env.Client.Server.RebuildWithResult(ctx, server, opts) + if err != nil { + t.Fatal(err) + } + if result.Action.ID != 1 { + t.Errorf("unexpected action ID: %d", result.Action.ID) + } + if result.RootPassword != "" { + t.Errorf("unexpected root password: %s", result.RootPassword) + } + }) +} + func TestServerClientAttachISO(t *testing.T) { var ( ctx = context.Background() From f1845c099261b21be4dd31461f673918d93fb8ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20T=C3=B6lle?= Date: Mon, 6 Mar 2023 11:44:35 +0100 Subject: [PATCH 2/2] chore: cleanup comments for ServerClient.Delete(WithResult) - Comment should start with the method name - Deprecated methods should contain "^Deprecated: xx" in the comment - References to other symbols should use square brackets --- hcloud/server.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hcloud/server.go b/hcloud/server.go index ae044356..52ea7fa5 100644 --- a/hcloud/server.go +++ b/hcloud/server.go @@ -464,13 +464,14 @@ type ServerDeleteResult struct { } // Delete deletes a server. -// This method is deprecated, use ServerClient.DeleteWithResult instead. +// +// Deprecated: Use [ServerClient.DeleteWithResult] instead. func (c *ServerClient) Delete(ctx context.Context, server *Server) (*Response, error) { _, resp, err := c.DeleteWithResult(ctx, server) return resp, err } -// Delete deletes a server and returns the parsed response containing the action. +// DeleteWithResult deletes a server and returns the parsed response containing the action. func (c *ServerClient) DeleteWithResult(ctx context.Context, server *Server) (*ServerDeleteResult, *Response, error) { req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/servers/%d", server.ID), nil) if err != nil {