Repository: hetznercloud/hcloud-go
Pull request #105 (2018-12-06 09:38:46)

Add automount Flag to VolumeCreate Request


Patch:
diff --git a/CHANGES.md b/CHANGES.md
index f25ed50..573c554 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -3,6 +3,8 @@
 ## master
 
 * Add missing constants for all [documented error codes](https://docs.hetzner.cloud/#overview-errors)
+* Add support for automounting volumes
+* Add support for attaching volumes when creating a server
 
 ## v1.11.0
 
diff --git a/hcloud/schema/server.go b/hcloud/schema/server.go
index edaf54c..e680ae7 100644
--- a/hcloud/schema/server.go
+++ b/hcloud/schema/server.go
@@ -85,6 +85,8 @@ type ServerCreateRequest struct {
 	UserData         string             `json:"user_data,omitempty"`
 	StartAfterCreate *bool              `json:"start_after_create,omitempty"`
 	Labels           *map[string]string `json:"labels,omitempty"`
+	Automount        *bool              `json:"automount,omitempty"`
+	Volumes          []int              `json:"volumes,omitempty"`
 }
 
 // ServerCreateResponse defines the schema of the response when
diff --git a/hcloud/schema/volume.go b/hcloud/schema/volume.go
index a8aa793..1ca18f5 100644
--- a/hcloud/schema/volume.go
+++ b/hcloud/schema/volume.go
@@ -18,11 +18,13 @@ type Volume struct {
 // VolumeCreateRequest defines the schema of the request
 // to create a volume.
 type VolumeCreateRequest struct {
-	Name     string             `json:"name"`
-	Size     int                `json:"size"`
-	Server   *int               `json:"server,omitempty"`
-	Location interface{}        `json:"location,omitempty"` // int, string, or nil
-	Labels   *map[string]string `json:"labels,omitempty"`
+	Name      string             `json:"name"`
+	Size      int                `json:"size"`
+	Server    *int               `json:"server,omitempty"`
+	Location  interface{}        `json:"location,omitempty"` // int, string, or nil
+	Labels    *map[string]string `json:"labels,omitempty"`
+	Automount *bool              `json:"automount,omitempty"`
+	Format    *string            `json:"format,omitempty"`
 }
 
 // VolumeCreateResponse defines the schema of the response
@@ -76,7 +78,8 @@ type VolumeActionChangeProtectionResponse struct {
 // VolumeActionAttachVolumeRequest defines the schema of the request to
 // attach a volume to a server.
 type VolumeActionAttachVolumeRequest struct {
-	Server int `json:"server"`
+	Server    int   `json:"server"`
+	Automount *bool `json:"automount"`
 }
 
 // VolumeActionAttachVolumeResponse defines the schema of the response when
diff --git a/hcloud/server.go b/hcloud/server.go
index ad0b16e..c301d5b 100644
--- a/hcloud/server.go
+++ b/hcloud/server.go
@@ -205,6 +205,8 @@ type ServerCreateOpts struct {
 	UserData         string
 	StartAfterCreate *bool
 	Labels           map[string]string
+	Automount        *bool
+	Volumes          []*Volume
 }
 
 // Validate checks if options are valid.
@@ -241,6 +243,7 @@ func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (Serve
 	var reqBody schema.ServerCreateRequest
 	reqBody.UserData = opts.UserData
 	reqBody.Name = opts.Name
+	reqBody.Automount = opts.Automount
 	reqBody.StartAfterCreate = opts.StartAfterCreate
 	if opts.ServerType.ID != 0 {
 		reqBody.ServerType = opts.ServerType.ID
@@ -258,6 +261,10 @@ func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (Serve
 	for _, sshKey := range opts.SSHKeys {
 		reqBody.SSHKeys = append(reqBody.SSHKeys, sshKey.ID)
 	}
+	for _, volume := range opts.Volumes {
+		reqBody.Volumes = append(reqBody.Volumes, volume.ID)
+	}
+
 	if opts.Location != nil {
 		if opts.Location.ID != 0 {
 			reqBody.Location = strconv.Itoa(opts.Location.ID)
diff --git a/hcloud/volume.go b/hcloud/volume.go
index 735f391..a9d2ea3 100644
--- a/hcloud/volume.go
+++ b/hcloud/volume.go
@@ -135,11 +135,13 @@ func (c *VolumeClient) AllWithOpts(ctx context.Context, opts VolumeListOpts) ([]
 
 // VolumeCreateOpts specifies parameters for creating a volume.
 type VolumeCreateOpts struct {
-	Name     string
-	Size     int
-	Server   *Server
-	Location *Location
-	Labels   map[string]string
+	Name      string
+	Size      int
+	Server    *Server
+	Location  *Location
+	Labels    map[string]string
+	Automount *bool
+	Format    *string
 }
 
 // Validate checks if options are valid.
@@ -156,6 +158,9 @@ func (o VolumeCreateOpts) Validate() error {
 	if o.Server != nil && o.Location != nil {
 		return errors.New("only one of server or location must be provided")
 	}
+	if o.Server == nil && (o.Automount != nil && *o.Automount) {
+		return errors.New("server must be provided when automount is true")
+	}
 	return nil
 }
 
@@ -172,8 +177,10 @@ func (c *VolumeClient) Create(ctx context.Context, opts VolumeCreateOpts) (Volum
 		return VolumeCreateResult{}, nil, err
 	}
 	reqBody := schema.VolumeCreateRequest{
-		Name: opts.Name,
-		Size: opts.Size,
+		Name:      opts.Name,
+		Size:      opts.Size,
+		Automount: opts.Automount,
+		Format:    opts.Format,
 	}
 	if opts.Labels != nil {
 		reqBody.Labels = &opts.Labels
@@ -188,6 +195,7 @@ func (c *VolumeClient) Create(ctx context.Context, opts VolumeCreateOpts) (Volum
 			reqBody.Location = opts.Location.Name
 		}
 	}
+
 	reqBodyData, err := json.Marshal(reqBody)
 	if err != nil {
 		return VolumeCreateResult{}, nil, err
@@ -258,11 +266,19 @@ func (c *VolumeClient) Update(ctx context.Context, volume *Volume, opts VolumeUp
 	return VolumeFromSchema(respBody.Volume), resp, nil
 }
 
-// Attach attaches a volume to a server.
-func (c *VolumeClient) Attach(ctx context.Context, volume *Volume, server *Server) (*Action, *Response, error) {
+// VolumeAttachOpts specifies options for attaching a volume.
+type VolumeAttachOpts struct {
+	Server    *Server
+	Automount *bool
+}
+
+// AttachWithOpts attaches a volume to a server.
+func (c *VolumeClient) AttachWithOpts(ctx context.Context, volume *Volume, opts VolumeAttachOpts) (*Action, *Response, error) {
 	reqBody := schema.VolumeActionAttachVolumeRequest{
-		Server: server.ID,
+		Server:    opts.Server.ID,
+		Automount: opts.Automount,
 	}
+
 	reqBodyData, err := json.Marshal(reqBody)
 	if err != nil {
 		return nil, nil, err
@@ -282,6 +298,11 @@ func (c *VolumeClient) Attach(ctx context.Context, volume *Volume, server *Serve
 	return ActionFromSchema(respBody.Action), resp, nil
 }
 
+// Attach attaches a volume to a server.
+func (c *VolumeClient) Attach(ctx context.Context, volume *Volume, server *Server) (*Action, *Response, error) {
+	return c.AttachWithOpts(ctx, volume, VolumeAttachOpts{Server: server})
+}
+
 // Detach detaches a volume from a server.
 func (c *VolumeClient) Detach(ctx context.Context, volume *Volume) (*Action, *Response, error) {
 	var reqBody schema.VolumeActionDetachVolumeRequest


Test patch:
diff --git a/hcloud/server_test.go b/hcloud/server_test.go
index 283c87b..1bda5b4 100644
--- a/hcloud/server_test.go
+++ b/hcloud/server_test.go
@@ -342,6 +342,56 @@ func TestServersCreateWithoutSSHKeys(t *testing.T) {
 	}
 }
 
+func TestServersCreateWithVolumes(t *testing.T) {
+	env := newTestEnv()
+	defer env.Teardown()
+
+	env.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) {
+		var reqBody schema.ServerCreateRequest
+		if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
+			t.Fatal(err)
+		}
+		if len(reqBody.Volumes) != 2 || reqBody.Volumes[0] != 1 || reqBody.Volumes[1] != 2 {
+			t.Errorf("unexpected Volumes: %v", reqBody.Volumes)
+		}
+		if reqBody.Automount == nil || !*reqBody.Automount {
+			t.Errorf("unexpected Automount: %v", reqBody.Automount)
+		}
+		json.NewEncoder(w).Encode(schema.ServerCreateResponse{
+			Server: schema.Server{
+				ID: 1,
+			},
+			NextActions: []schema.Action{
+				{ID: 2},
+			},
+		})
+	})
+
+	ctx := context.Background()
+	result, _, err := env.Client.Server.Create(ctx, ServerCreateOpts{
+		Name:       "test",
+		ServerType: &ServerType{ID: 1},
+		Image:      &Image{ID: 2},
+		Volumes: []*Volume{
+			{ID: 1},
+			{ID: 2},
+		},
+		Automount: Bool(true),
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if result.Server == nil {
+		t.Fatal("no server")
+	}
+	if result.Server.ID != 1 {
+		t.Errorf("unexpected server ID: %v", result.Server.ID)
+	}
+	if len(result.NextActions) != 1 || result.NextActions[0].ID != 2 {
+		t.Errorf("unexpected next actions: %v", result.NextActions)
+	}
+}
+
 func TestServersCreateWithDatacenterID(t *testing.T) {
 	env := newTestEnv()
 	defer env.Teardown()
diff --git a/hcloud/volume_test.go b/hcloud/volume_test.go
index d77af54..0283c4e 100644
--- a/hcloud/volume_test.go
+++ b/hcloud/volume_test.go
@@ -311,6 +311,12 @@ func TestVolumeClientCreateWithLocation(t *testing.T) {
 		if reqBody.Labels == nil || (*reqBody.Labels)["key"] != "value" {
 			t.Errorf("unexpected labels in request: %v", reqBody.Labels)
 		}
+		if reqBody.Automount != nil {
+			t.Errorf("unexpected automount in request: %v", reqBody.Automount)
+		}
+		if reqBody.Format != nil {
+			t.Errorf("unexpected format in request: %v", reqBody.Automount)
+		}
 		fmt.Fprint(w, `{
 			"volume": {
 				"id": 1,
@@ -353,7 +359,77 @@ func TestVolumeClientCreateWithLocation(t *testing.T) {
 		t.Errorf("unexpected volume ID: %v", result.Volume.ID)
 	}
 }
+func TestVolumeClientCreateWithAutomount(t *testing.T) {
+	env := newTestEnv()
+	defer env.Teardown()
+
+	env.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) {
+		var reqBody schema.VolumeCreateRequest
+		if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
+			t.Fatal(err)
+		}
+		if reqBody.Name != "my-volume" {
+			t.Errorf("unexpected volume name in request: %v", reqBody.Name)
+		}
+		if reqBody.Size != 42 {
+			t.Errorf("unexpected volume size in request: %v", reqBody.Size)
+		}
+		if reqBody.Server == nil || *reqBody.Server != 1 {
+			t.Errorf("unexpected server in request: %v", reqBody.Server)
+		}
+		if reqBody.Labels == nil || (*reqBody.Labels)["key"] != "value" {
+			t.Errorf("unexpected labels in request: %v", reqBody.Labels)
+		}
+		if *reqBody.Automount != true {
+			t.Errorf("unexpected automount in request: %v", reqBody.Automount)
+		}
+		if *reqBody.Format != "xfs" {
+			t.Errorf("unexpected format in request: %v", reqBody.Automount)
+		}
+		fmt.Fprint(w, `{
+			"volume": {
+				"id": 1,
+				"created": "2016-01-30T23:50:11+00:00",
+				"name": "my-volume",
+				"server": 1,
+				"location": {
+					"id": 1,
+					"name": "fsn1",
+					"description": "Falkenstein DC Park 1",
+					"country": "DE",
+					"city": "Falkenstein",
+					"latitude": 50.47612,
+					"longitude": 12.370071
+				},
+				"size": 42,
+				"linux_device":"/dev/disk/by-id/scsi-0HC_volume_1",
+				"protection": {
+					"delete": true
+				},
+				"labels": {
+					"key": "value"
+				}
+			}
+		}`)
+	})
 
+	ctx := context.Background()
+	opts := VolumeCreateOpts{
+		Name:      "my-volume",
+		Size:      42,
+		Server:    &Server{ID: 1},
+		Labels:    map[string]string{"key": "value"},
+		Automount: Bool(true),
+		Format:    String("xfs"),
+	}
+	result, _, err := env.Client.Volume.Create(ctx, opts)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if result.Volume.ID != 1 {
+		t.Errorf("unexpected volume ID: %v", result.Volume.ID)
+	}
+}
 func TestVolumeClientUpdate(t *testing.T) {
 	var (
 		ctx    = context.Background()