Skip to content

Commit

Permalink
Placement groups (#179)
Browse files Browse the repository at this point in the history
  • Loading branch information
Adi146 committed Aug 16, 2021
1 parent e951e99 commit ab0ebb2
Show file tree
Hide file tree
Showing 9 changed files with 846 additions and 1 deletion.
2 changes: 2 additions & 0 deletions hcloud/client.go
Expand Up @@ -74,6 +74,7 @@ type Client struct {
ServerType ServerTypeClient
SSHKey SSHKeyClient
Volume VolumeClient
PlacementGroup PlacementGroupClient
}

// A ClientOption is used to configure a Client.
Expand Down Expand Up @@ -164,6 +165,7 @@ func NewClient(options ...ClientOption) *Client {
client.LoadBalancerType = LoadBalancerTypeClient{client: client}
client.Certificate = CertificateClient{client: client}
client.Firewall = FirewallClient{client: client}
client.PlacementGroup = PlacementGroupClient{client: client}

return client
}
Expand Down
243 changes: 243 additions & 0 deletions hcloud/placement_group.go
@@ -0,0 +1,243 @@
package hcloud

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
"time"

"github.com/hetznercloud/hcloud-go/hcloud/schema"
)

// PlacementGroup represents a Placement Group in the Hetzner Cloud.
type PlacementGroup struct {
ID int
Name string
Labels map[string]string
Created time.Time
Servers []int
Type PlacementGroupType
}

// PlacementGroupType specifies the type of a Placement Group
type PlacementGroupType string

const (
// PlacementGroupTypeSpread spreads all servers in the group on different vhosts
PlacementGroupTypeSpread PlacementGroupType = "spread"
)

// PlacementGroupClient is a client for the Placement Groups API.
type PlacementGroupClient struct {
client *Client
}

// GetByID retrieves a PlacementGroup by its ID. If the PlacementGroup does not exist, nil is returned.
func (c *PlacementGroupClient) GetByID(ctx context.Context, id int) (*PlacementGroup, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/placement_groups/%d", id), nil)
if err != nil {
return nil, nil, err
}

var body schema.PlacementGroupGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, nil, err
}
return PlacementGroupFromSchema(body.PlacementGroup), resp, nil
}

// GetByName retrieves a PlacementGroup by its name. If the PlacementGroup does not exist, nil is returned.
func (c *PlacementGroupClient) GetByName(ctx context.Context, name string) (*PlacementGroup, *Response, error) {
if name == "" {
return nil, nil, nil
}
placementGroups, response, err := c.List(ctx, PlacementGroupListOpts{Name: name})
if len(placementGroups) == 0 {
return nil, response, err
}
return placementGroups[0], response, err
}

// Get retrieves a PlacementGroup by its ID if the input can be parsed as an integer, otherwise it
// retrieves a PlacementGroup by its name. If the PlacementGroup does not exist, nil is returned.
func (c *PlacementGroupClient) Get(ctx context.Context, idOrName string) (*PlacementGroup, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))
}
return c.GetByName(ctx, idOrName)
}

// PlacementGroupListOpts specifies options for listing PlacementGroup.
type PlacementGroupListOpts struct {
ListOpts
Name string
Type PlacementGroupType
}

func (l PlacementGroupListOpts) values() url.Values {
vals := l.ListOpts.values()
if l.Name != "" {
vals.Add("name", l.Name)
}
if l.Type != "" {
vals.Add("type", string(l.Type))
}
return vals
}

// List returns a list of PlacementGroups for a specific page.
//
// Please note that filters specified in opts are not taken into account
// when their value corresponds to their zero value or when they are empty.
func (c *PlacementGroupClient) List(ctx context.Context, opts PlacementGroupListOpts) ([]*PlacementGroup, *Response, error) {
path := "/placement_groups?" + opts.values().Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}

var body schema.PlacementGroupListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
placementGroups := make([]*PlacementGroup, 0, len(body.PlacementGroups))
for _, g := range body.PlacementGroups {
placementGroups = append(placementGroups, PlacementGroupFromSchema(g))
}
return placementGroups, resp, nil
}

// All returns all PlacementGroups.
func (c *PlacementGroupClient) All(ctx context.Context) ([]*PlacementGroup, error) {
opts := PlacementGroupListOpts{
ListOpts: ListOpts{
PerPage: 50,
},
}

return c.AllWithOpts(ctx, opts)
}

// AllWithOpts returns all PlacementGroups for the given options.
func (c *PlacementGroupClient) AllWithOpts(ctx context.Context, opts PlacementGroupListOpts) ([]*PlacementGroup, error) {
var allPlacementGroups []*PlacementGroup

err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
placementGroups, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allPlacementGroups = append(allPlacementGroups, placementGroups...)
return resp, nil
})
if err != nil {
return nil, err
}

return allPlacementGroups, nil
}

// PlacementGroupCreateOpts specifies options for creating a new PlacementGroup.
type PlacementGroupCreateOpts struct {
Name string
Labels map[string]string
Type PlacementGroupType
}

// Validate checks if options are valid
func (o PlacementGroupCreateOpts) Validate() error {
if o.Name == "" {
return errors.New("missing name")
}
return nil
}

// PlacementGroupCreateResult is the result of a create PlacementGroup call.
type PlacementGroupCreateResult struct {
PlacementGroup *PlacementGroup
Action *Action
}

// Create creates a new PlacementGroup
func (c *PlacementGroupClient) Create(ctx context.Context, opts PlacementGroupCreateOpts) (PlacementGroupCreateResult, *Response, error) {
if err := opts.Validate(); err != nil {
return PlacementGroupCreateResult{}, nil, err
}
reqBody := placementGroupCreateOptsToSchema(opts)
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return PlacementGroupCreateResult{}, nil, err
}
req, err := c.client.NewRequest(ctx, "POST", "/placement_groups", bytes.NewReader(reqBodyData))
if err != nil {
return PlacementGroupCreateResult{}, nil, err
}

respBody := schema.PlacementGroupCreateResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return PlacementGroupCreateResult{}, nil, err
}
result := PlacementGroupCreateResult{
PlacementGroup: PlacementGroupFromSchema(respBody.PlacementGroup),
}
if respBody.Action != nil {
result.Action = ActionFromSchema(*respBody.Action)
}

return result, resp, nil
}

// PlacementGroupUpdateOpts specifies options for updating a PlacementGroup.
type PlacementGroupUpdateOpts struct {
Name string
Labels map[string]string
}

// Update updates a PlacementGroup.
func (c *PlacementGroupClient) Update(ctx context.Context, placementGroup *PlacementGroup, opts PlacementGroupUpdateOpts) (*PlacementGroup, *Response, error) {
reqBody := schema.PlacementGroupUpdateRequest{}
if opts.Name != "" {
reqBody.Name = &opts.Name
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}

path := fmt.Sprintf("/placement_groups/%d", placementGroup.ID)
req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}

respBody := schema.PlacementGroupUpdateResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}

return PlacementGroupFromSchema(respBody.PlacementGroup), resp, nil
}

// Delete deletes a PlacementGroup.
func (c *PlacementGroupClient) Delete(ctx context.Context, placementGroup *PlacementGroup) (*Response, error) {
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/placement_groups/%d", placementGroup.ID), nil)
if err != nil {
return nil, err
}
return c.client.Do(req, nil)
}

0 comments on commit ab0ebb2

Please sign in to comment.