Merge pull request #6871 from LKaemmerling/master

Add hcloud builder
This commit is contained in:
Megan Marsh 2018-10-18 11:13:16 -07:00 committed by GitHub
commit c5b6eff691
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 5184 additions and 0 deletions

View File

@ -0,0 +1,47 @@
package hcloud
import (
"context"
"fmt"
"log"
"strconv"
"github.com/hetznercloud/hcloud-go/hcloud"
)
type Artifact struct {
// The name of the snapshot
snapshotName string
// The ID of the image
snapshotId int
// The hcloudClient for making API calls
hcloudClient *hcloud.Client
}
func (*Artifact) BuilderId() string {
return BuilderId
}
func (*Artifact) Files() []string {
return nil
}
func (a *Artifact) Id() string {
return strconv.Itoa(a.snapshotId)
}
func (a *Artifact) String() string {
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v)", a.snapshotName, a.snapshotId)
}
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
_, err := a.hcloudClient.Image.Delete(context.TODO(), &hcloud.Image{ID: a.snapshotId})
return err
}

View File

@ -0,0 +1,29 @@
package hcloud
import (
"testing"
"github.com/hashicorp/packer/packer"
)
func TestArtifact_Impl(t *testing.T) {
var _ packer.Artifact = (*Artifact)(nil)
}
func TestArtifactId(t *testing.T) {
a := &Artifact{"packer-foobar", 42, nil}
expected := "42"
if a.Id() != expected {
t.Fatalf("artifact ID should match: %v", expected)
}
}
func TestArtifactString(t *testing.T) {
a := &Artifact{"packer-foobar", 42, nil}
expected := "A snapshot was created: 'packer-foobar' (ID: 42)"
if a.String() != expected {
t.Fatalf("artifact string should match: %v", expected)
}
}

94
builder/hcloud/builder.go Normal file
View File

@ -0,0 +1,94 @@
package hcloud
import (
"fmt"
"log"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hetznercloud/hcloud-go/hcloud"
)
// The unique id for the builder
const BuilderId = "hcloud.builder"
type Builder struct {
config Config
runner multistep.Runner
hcloudClient *hcloud.Client
}
var pluginVersion = "1.0.0"
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
config, warnings, errs := NewConfig(raws...)
if errs != nil {
return warnings, errs
}
b.config = *config
return nil, nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
opts := []hcloud.ClientOption{
hcloud.WithToken(b.config.HCloudToken),
hcloud.WithEndpoint(b.config.Endpoint),
hcloud.WithPollInterval(b.config.PollInterval),
hcloud.WithApplication("hcloud-packer", pluginVersion),
}
b.hcloudClient = hcloud.NewClient(opts...)
// Set up the state
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("hcloudClient", b.hcloudClient)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
&stepCreateSSHKey{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("ssh_key_%s.pem", b.config.PackerBuildName),
},
&stepCreateServer{},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: getServerIP,
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.Comm,
},
&stepShutdownServer{},
&stepCreateSnapshot{},
}
// Run the steps
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
if _, ok := state.GetOk("snapshot_name"); !ok {
return nil, nil
}
artifact := &Artifact{
snapshotName: state.Get("snapshot_name").(string),
snapshotId: state.Get("snapshot_id").(int),
hcloudClient: b.hcloudClient,
}
return artifact, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}

View File

@ -0,0 +1,35 @@
package hcloud
import (
"os"
"testing"
builderT "github.com/hashicorp/packer/helper/builder/testing"
)
func TestBuilderAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccBasic,
})
}
func testAccPreCheck(t *testing.T) {
if v := os.Getenv("HCLOUD_TOKEN"); v == "" {
t.Fatal("HCLOUD_TOKEN must be set for acceptance tests")
}
}
const testBuilderAccBasic = `
{
"builders": [{
"type": "test",
"location": "nbg1",
"server_type": "cx11",
"image": "ubuntu-18.04",
"user_data": "",
"user_data_file": ""
}]
}
`

132
builder/hcloud/config.go Normal file
View File

@ -0,0 +1,132 @@
package hcloud
import (
"errors"
"fmt"
"os"
"time"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/mitchellh/mapstructure"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
HCloudToken string `mapstructure:"token"`
Endpoint string `mapstructure:"endpoint"`
PollInterval time.Duration `mapstructure:"poll_interval"`
ServerName string `mapstructure:"server_name"`
Location string `mapstructure:"location"`
ServerType string `mapstructure:"server_type"`
Image string `mapstructure:"image"`
SnapshotName string `mapstructure:"snapshot_name"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
ctx interpolate.Context
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
var md mapstructure.Metadata
err := config.Decode(c, &config.DecodeOpts{
Metadata: &md,
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"run_command",
},
},
}, raws...)
if err != nil {
return nil, nil, err
}
// Defaults
if c.HCloudToken == "" {
c.HCloudToken = os.Getenv("HCLOUD_TOKEN")
}
if c.Endpoint == "" {
if os.Getenv("HCLOUD_ENDPOINT") != "" {
c.Endpoint = os.Getenv("HCLOUD_ENDPOINT")
} else {
c.Endpoint = hcloud.Endpoint
}
}
if c.PollInterval == 0 {
c.PollInterval = 500 * time.Millisecond
}
if c.SnapshotName == "" {
def, err := interpolate.Render("packer-{{timestamp}}", nil)
if err != nil {
panic(err)
}
// Default to packer-{{ unix timestamp (utc) }}
c.SnapshotName = def
}
if c.ServerName == "" {
// Default to packer-[time-ordered-uuid]
c.ServerName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
}
var errs *packer.MultiError
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
if c.HCloudToken == "" {
// Required configurations that will display errors if not set
errs = packer.MultiErrorAppend(
errs, errors.New("token for auth must be specified"))
}
if c.Location == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("location is required"))
}
if c.ServerType == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("server type is required"))
}
if c.Image == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("image is required"))
}
if c.UserData != "" && c.UserDataFile != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("only one of user_data or user_data_file can be specified"))
} else if c.UserDataFile != "" {
if _, err := os.Stat(c.UserDataFile); err != nil {
errs = packer.MultiErrorAppend(
errs, errors.New(fmt.Sprintf("user_data_file not found: %s", c.UserDataFile)))
}
}
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
packer.LogSecretFilter.Set(c.HCloudToken)
return c, nil, nil
}
func getServerIP(state multistep.StateBag) (string, error) {
return state.Get("server_ip").(string), nil
}

View File

@ -0,0 +1,92 @@
package hcloud
import (
"context"
"fmt"
"io/ioutil"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hetznercloud/hcloud-go/hcloud"
)
type stepCreateServer struct {
serverId int
}
func (s *stepCreateServer) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("hcloudClient").(*hcloud.Client)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
sshKeyId := state.Get("ssh_key_id").(int)
// Create the server based on configuration
ui.Say("Creating server...")
userData := c.UserData
if c.UserDataFile != "" {
contents, err := ioutil.ReadFile(c.UserDataFile)
if err != nil {
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
return multistep.ActionHalt
}
userData = string(contents)
}
serverCreateResult, _, err := client.Server.Create(context.TODO(), hcloud.ServerCreateOpts{
Name: c.ServerName,
ServerType: &hcloud.ServerType{Name: c.ServerType},
Image: &hcloud.Image{Name: c.Image},
SSHKeys: []*hcloud.SSHKey{{ID: sshKeyId}},
Location: &hcloud.Location{Name: c.Location},
UserData: userData,
})
if err != nil {
err := fmt.Errorf("Error creating server: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("server_ip", serverCreateResult.Server.PublicNet.IPv4.IP.String())
// We use this in cleanup
s.serverId = serverCreateResult.Server.ID
// Store the server id for later
state.Put("server_id", serverCreateResult.Server.ID)
_, errCh := client.Action.WatchProgress(context.TODO(), serverCreateResult.Action)
for {
select {
case err1 := <-errCh:
if err1 == nil {
return multistep.ActionContinue
} else {
err := fmt.Errorf("Error creating server: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}
}
func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
// If the serverID isn't there, we probably never created it
if s.serverId == 0 {
return
}
client := state.Get("hcloudClient").(*hcloud.Client)
ui := state.Get("ui").(packer.Ui)
// Destroy the server we just created
ui.Say("Destroying server...")
_, err := client.Server.Delete(context.TODO(), &hcloud.Server{ID: s.serverId})
if err != nil {
ui.Error(fmt.Sprintf(
"Error destroying server. Please destroy it manually: %s", err))
}
}

View File

@ -0,0 +1,53 @@
package hcloud
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hetznercloud/hcloud-go/hcloud"
)
type stepCreateSnapshot struct{}
func (s *stepCreateSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("hcloudClient").(*hcloud.Client)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
serverID := state.Get("server_id").(int)
ui.Say("Creating snapshot ...")
ui.Say("This can take some time")
result, _, err := client.Server.CreateImage(context.TODO(), &hcloud.Server{ID: serverID}, &hcloud.ServerCreateImageOpts{
Type: hcloud.ImageTypeSnapshot,
Description: hcloud.String(c.SnapshotName),
})
if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("snapshot_id", result.Image.ID)
state.Put("snapshot_name", c.SnapshotName)
_, errCh := client.Action.WatchProgress(context.TODO(), result.Action)
for {
select {
case err1 := <-errCh:
if err1 == nil {
return multistep.ActionContinue
} else {
err := fmt.Errorf("Error creating snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}
}
func (s *stepCreateSnapshot) Cleanup(state multistep.StateBag) {
// no cleanup
}

View File

@ -0,0 +1,118 @@
package hcloud
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"log"
"os"
"runtime"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hetznercloud/hcloud-go/hcloud"
"golang.org/x/crypto/ssh"
)
type stepCreateSSHKey struct {
Debug bool
DebugKeyPath string
keyId int
}
func (s *stepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("hcloudClient").(*hcloud.Client)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
ui.Say("Creating temporary ssh key for server...")
priv, err := rsa.GenerateKey(rand.Reader, 2014)
// ASN.1 DER encoded form
privDER := x509.MarshalPKCS1PrivateKey(priv)
privBLK := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: privDER,
}
// Set the private key in the config for later
c.Comm.SSHPrivateKey = pem.EncodeToMemory(&privBLK)
// Marshal the public key into SSH compatible format
// TODO properly handle the public key error
pub, _ := ssh.NewPublicKey(&priv.PublicKey)
pubSSHFormat := string(ssh.MarshalAuthorizedKey(pub))
// The name of the public key on the Hetzner Cloud
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
// Create the key!
key, _, err := client.SSHKey.Create(context.TODO(), hcloud.SSHKeyCreateOpts{
Name: name,
PublicKey: pubSSHFormat,
})
if err != nil {
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// We use this to check cleanup
s.keyId = key.ID
log.Printf("temporary ssh key name: %s", name)
// Remember some state for the future
state.Put("ssh_key_id", key.ID)
// If we're in debug mode, output the private key to the working directory.
if s.Debug {
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
f, err := os.Create(s.DebugKeyPath)
if err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
defer f.Close()
// Write the key out
if _, err := f.Write(pem.EncodeToMemory(&privBLK)); err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
// Chmod it so that it is SSH ready
if runtime.GOOS != "windows" {
if err := f.Chmod(0600); err != nil {
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
// If no key id is set, then we never created it, so just return
if s.keyId == 0 {
return
}
client := state.Get("hcloudClient").(*hcloud.Client)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting temporary ssh key...")
_, err := client.SSHKey.Delete(context.TODO(), &hcloud.SSHKey{ID: s.keyId})
if err != nil {
log.Printf("Error cleaning up ssh key: %s", err)
ui.Error(fmt.Sprintf(
"Error cleaning up ssh key. Please delete the key manually: %s", err))
}
}

View File

@ -0,0 +1,49 @@
package hcloud
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hetznercloud/hcloud-go/hcloud"
)
type stepShutdownServer struct{}
func (s *stepShutdownServer) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("hcloudClient").(*hcloud.Client)
ui := state.Get("ui").(packer.Ui)
serverID := state.Get("server_id").(int)
ui.Say("Shutting down server...")
action, _, err := client.Server.Shutdown(context.TODO(), &hcloud.Server{ID: serverID})
if err != nil {
err := fmt.Errorf("Error stopping server: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
_, errCh := client.Action.WatchProgress(context.TODO(), action)
for {
select {
case err1 := <-errCh:
if err1 == nil {
return multistep.ActionContinue
} else {
err := fmt.Errorf("Error stopping server: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}
}
func (s *stepShutdownServer) Cleanup(state multistep.StateBag) {
// no cleanup
}

View File

@ -25,6 +25,7 @@ import (
dockerbuilder "github.com/hashicorp/packer/builder/docker"
filebuilder "github.com/hashicorp/packer/builder/file"
googlecomputebuilder "github.com/hashicorp/packer/builder/googlecompute"
hcloudbuilder "github.com/hashicorp/packer/builder/hcloud"
hypervisobuilder "github.com/hashicorp/packer/builder/hyperv/iso"
hypervvmcxbuilder "github.com/hashicorp/packer/builder/hyperv/vmcx"
lxcbuilder "github.com/hashicorp/packer/builder/lxc"
@ -95,6 +96,7 @@ var Builders = map[string]packer.Builder{
"docker": new(dockerbuilder.Builder),
"file": new(filebuilder.Builder),
"googlecompute": new(googlecomputebuilder.Builder),
"hcloud": new(hcloudbuilder.Builder),
"hyperv-iso": new(hypervisobuilder.Builder),
"hyperv-vmcx": new(hypervvmcxbuilder.Builder),
"lxc": new(lxcbuilder.Builder),

21
vendor/github.com/hetznercloud/hcloud-go/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Hetzner Cloud GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,193 @@
package hcloud
import (
"context"
"fmt"
"time"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// Action represents an action in the Hetzner Cloud.
type Action struct {
ID int
Status ActionStatus
Command string
Progress int
Started time.Time
Finished time.Time
ErrorCode string
ErrorMessage string
Resources []*ActionResource
}
// ActionStatus represents an action's status.
type ActionStatus string
// List of action statuses.
const (
ActionStatusRunning ActionStatus = "running"
ActionStatusSuccess ActionStatus = "success"
ActionStatusError ActionStatus = "error"
)
// ActionResource references other resources from an action.
type ActionResource struct {
ID int
Type ActionResourceType
}
// ActionResourceType represents an action's resource reference type.
type ActionResourceType string
// List of action resource reference types.
const (
ActionResourceTypeServer ActionResourceType = "server"
ActionResourceTypeImage ActionResourceType = "image"
ActionResourceTypeISO ActionResourceType = "iso"
ActionResourceTypeFloatingIP ActionResourceType = "floating_ip"
ActionResourceTypeVolume ActionResourceType = "volume"
)
// ActionError is the error of an action.
type ActionError struct {
Code string
Message string
}
func (e ActionError) Error() string {
return fmt.Sprintf("%s (%s)", e.Message, e.Code)
}
func (a *Action) Error() error {
if a.ErrorCode != "" && a.ErrorMessage != "" {
return ActionError{
Code: a.ErrorCode,
Message: a.ErrorMessage,
}
}
return nil
}
// ActionClient is a client for the actions API.
type ActionClient struct {
client *Client
}
// GetByID retrieves an action by its ID.
func (c *ActionClient) GetByID(ctx context.Context, id int) (*Action, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/actions/%d", id), nil)
if err != nil {
return nil, nil, err
}
var body schema.ActionGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, nil, err
}
return ActionFromSchema(body.Action), resp, nil
}
// ActionListOpts specifies options for listing actions.
type ActionListOpts struct {
ListOpts
}
// List returns a list of actions for a specific page.
func (c *ActionClient) List(ctx context.Context, opts ActionListOpts) ([]*Action, *Response, error) {
path := "/actions?" + valuesForListOpts(opts.ListOpts).Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.ActionListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
actions := make([]*Action, 0, len(body.Actions))
for _, i := range body.Actions {
actions = append(actions, ActionFromSchema(i))
}
return actions, resp, nil
}
// All returns all actions.
func (c *ActionClient) All(ctx context.Context) ([]*Action, error) {
allActions := []*Action{}
opts := ActionListOpts{}
opts.PerPage = 50
_, err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
actions, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allActions = append(allActions, actions...)
return resp, nil
})
if err != nil {
return nil, err
}
return allActions, nil
}
// WatchProgress watches the action's progress until it completes with success or error.
func (c *ActionClient) WatchProgress(ctx context.Context, action *Action) (<-chan int, <-chan error) {
errCh := make(chan error, 1)
progressCh := make(chan int)
go func() {
defer close(errCh)
defer close(progressCh)
ticker := time.NewTicker(c.client.pollInterval)
sendProgress := func(p int) {
select {
case progressCh <- p:
break
default:
break
}
}
for {
select {
case <-ctx.Done():
errCh <- ctx.Err()
return
case <-ticker.C:
break
}
a, _, err := c.GetByID(ctx, action.ID)
if err != nil {
errCh <- ctx.Err()
return
}
switch a.Status {
case ActionStatusRunning:
sendProgress(a.Progress)
break
case ActionStatusSuccess:
sendProgress(100)
errCh <- nil
return
case ActionStatusError:
errCh <- a.Error()
return
}
}
}()
return progressCh, errCh
}

View File

@ -0,0 +1,329 @@
package hcloud
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// Endpoint is the base URL of the API.
const Endpoint = "https://api.hetzner.cloud/v1"
// UserAgent is the value for the library part of the User-Agent header
// that is sent with each request.
const UserAgent = "hcloud-go/" + Version
// A BackoffFunc returns the duration to wait before performing the
// next retry. The retries argument specifies how many retries have
// already been performed. When called for the first time, retries is 0.
type BackoffFunc func(retries int) time.Duration
// ConstantBackoff returns a BackoffFunc which backs off for
// constant duration d.
func ConstantBackoff(d time.Duration) BackoffFunc {
return func(_ int) time.Duration {
return d
}
}
// ExponentialBackoff returns a BackoffFunc which implements an exponential
// backoff using the formula: b^retries * d
func ExponentialBackoff(b float64, d time.Duration) BackoffFunc {
return func(retries int) time.Duration {
return time.Duration(math.Pow(b, float64(retries))) * d
}
}
// Client is a client for the Hetzner Cloud API.
type Client struct {
endpoint string
token string
pollInterval time.Duration
backoffFunc BackoffFunc
httpClient *http.Client
applicationName string
applicationVersion string
userAgent string
Action ActionClient
Datacenter DatacenterClient
FloatingIP FloatingIPClient
Image ImageClient
ISO ISOClient
Location LocationClient
Pricing PricingClient
Server ServerClient
ServerType ServerTypeClient
SSHKey SSHKeyClient
Volume VolumeClient
}
// A ClientOption is used to configure a Client.
type ClientOption func(*Client)
// WithEndpoint configures a Client to use the specified API endpoint.
func WithEndpoint(endpoint string) ClientOption {
return func(client *Client) {
client.endpoint = strings.TrimRight(endpoint, "/")
}
}
// WithToken configures a Client to use the specified token for authentication.
func WithToken(token string) ClientOption {
return func(client *Client) {
client.token = token
}
}
// WithPollInterval configures a Client to use the specified interval when polling
// from the API.
func WithPollInterval(pollInterval time.Duration) ClientOption {
return func(client *Client) {
client.pollInterval = pollInterval
}
}
// WithBackoffFunc configures a Client to use the specified backoff function.
func WithBackoffFunc(f BackoffFunc) ClientOption {
return func(client *Client) {
client.backoffFunc = f
}
}
// WithApplication configures a Client with the given application name and
// application version. The version may be blank. Programs are encouraged
// to at least set an application name.
func WithApplication(name, version string) ClientOption {
return func(client *Client) {
client.applicationName = name
client.applicationVersion = version
}
}
// NewClient creates a new client.
func NewClient(options ...ClientOption) *Client {
client := &Client{
endpoint: Endpoint,
httpClient: &http.Client{},
backoffFunc: ExponentialBackoff(2, 500*time.Millisecond),
pollInterval: 500 * time.Millisecond,
}
for _, option := range options {
option(client)
}
client.buildUserAgent()
client.Action = ActionClient{client: client}
client.Datacenter = DatacenterClient{client: client}
client.FloatingIP = FloatingIPClient{client: client}
client.Image = ImageClient{client: client}
client.ISO = ISOClient{client: client}
client.Location = LocationClient{client: client}
client.Pricing = PricingClient{client: client}
client.Server = ServerClient{client: client}
client.ServerType = ServerTypeClient{client: client}
client.SSHKey = SSHKeyClient{client: client}
client.Volume = VolumeClient{client: client}
return client
}
// NewRequest creates an HTTP request against the API. The returned request
// is assigned with ctx and has all necessary headers set (auth, user agent, etc.).
func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) {
url := c.endpoint + path
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", c.userAgent)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
req = req.WithContext(ctx)
return req, nil
}
// Do performs an HTTP request against the API.
func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
var retries int
for {
resp, err := c.httpClient.Do(r)
if err != nil {
return nil, err
}
response := &Response{Response: resp}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
resp.Body.Close()
return response, err
}
resp.Body.Close()
resp.Body = ioutil.NopCloser(bytes.NewReader(body))
if err = response.readMeta(body); err != nil {
return response, fmt.Errorf("hcloud: error reading response meta data: %s", err)
}
if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
err = errorFromResponse(resp, body)
if err == nil {
err = fmt.Errorf("hcloud: server responded with status code %d", resp.StatusCode)
} else {
if err, ok := err.(Error); ok && err.Code == ErrorCodeRateLimitExceeded {
c.backoff(retries)
retries++
continue
}
}
return response, err
}
if v != nil {
if w, ok := v.(io.Writer); ok {
_, err = io.Copy(w, bytes.NewReader(body))
} else {
err = json.Unmarshal(body, v)
}
}
return response, err
}
}
func (c *Client) backoff(retries int) {
time.Sleep(c.backoffFunc(retries))
}
func (c *Client) all(f func(int) (*Response, error)) (*Response, error) {
var (
page = 1
)
for {
resp, err := f(page)
if err != nil {
return nil, err
}
if resp.Meta.Pagination == nil || resp.Meta.Pagination.NextPage == 0 {
return resp, nil
}
page = resp.Meta.Pagination.NextPage
}
}
func (c *Client) buildUserAgent() {
switch {
case c.applicationName != "" && c.applicationVersion != "":
c.userAgent = c.applicationName + "/" + c.applicationVersion + " " + UserAgent
case c.applicationName != "" && c.applicationVersion == "":
c.userAgent = c.applicationName + " " + UserAgent
default:
c.userAgent = UserAgent
}
}
func errorFromResponse(resp *http.Response, body []byte) error {
if !strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
return nil
}
var respBody schema.ErrorResponse
if err := json.Unmarshal(body, &respBody); err != nil {
return nil
}
if respBody.Error.Code == "" && respBody.Error.Message == "" {
return nil
}
return ErrorFromSchema(respBody.Error)
}
// Response represents a response from the API. It embeds http.Response.
type Response struct {
*http.Response
Meta Meta
}
func (r *Response) readMeta(body []byte) error {
if h := r.Header.Get("RateLimit-Limit"); h != "" {
r.Meta.Ratelimit.Limit, _ = strconv.Atoi(h)
}
if h := r.Header.Get("RateLimit-Remaining"); h != "" {
r.Meta.Ratelimit.Remaining, _ = strconv.Atoi(h)
}
if h := r.Header.Get("RateLimit-Reset"); h != "" {
if ts, err := strconv.ParseInt(h, 10, 64); err == nil {
r.Meta.Ratelimit.Reset = time.Unix(ts, 0)
}
}
if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") {
var s schema.MetaResponse
if err := json.Unmarshal(body, &s); err != nil {
return err
}
if s.Meta.Pagination != nil {
p := PaginationFromSchema(*s.Meta.Pagination)
r.Meta.Pagination = &p
}
}
return nil
}
// Meta represents meta information included in an API response.
type Meta struct {
Pagination *Pagination
Ratelimit Ratelimit
}
// Pagination represents pagination meta information.
type Pagination struct {
Page int
PerPage int
PreviousPage int
NextPage int
LastPage int
TotalEntries int
}
// Ratelimit represents ratelimit information.
type Ratelimit struct {
Limit int
Remaining int
Reset time.Time
}
// ListOpts specifies options for listing resources.
type ListOpts struct {
Page int // Page (starting at 1)
PerPage int // Items per page (0 means default)
LabelSelector string // Label selector for filtering by labels
}
func valuesForListOpts(opts ListOpts) url.Values {
vals := url.Values{}
if opts.Page > 0 {
vals.Add("page", strconv.Itoa(opts.Page))
}
if opts.PerPage > 0 {
vals.Add("per_page", strconv.Itoa(opts.PerPage))
}
if len(opts.LabelSelector) > 0 {
vals.Add("label_selector", opts.LabelSelector)
}
return vals
}

View File

@ -0,0 +1,124 @@
package hcloud
import (
"context"
"fmt"
"net/url"
"strconv"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// Datacenter represents a datacenter in the Hetzner Cloud.
type Datacenter struct {
ID int
Name string
Description string
Location *Location
ServerTypes DatacenterServerTypes
}
// DatacenterServerTypes represents the server types available and supported in a datacenter.
type DatacenterServerTypes struct {
Supported []*ServerType
Available []*ServerType
}
// DatacenterClient is a client for the datacenter API.
type DatacenterClient struct {
client *Client
}
// GetByID retrieves a datacenter by its ID.
func (c *DatacenterClient) GetByID(ctx context.Context, id int) (*Datacenter, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/datacenters/%d", id), nil)
if err != nil {
return nil, nil, err
}
var body schema.DatacenterGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, resp, err
}
return DatacenterFromSchema(body.Datacenter), resp, nil
}
// GetByName retrieves an datacenter by its name.
func (c *DatacenterClient) GetByName(ctx context.Context, name string) (*Datacenter, *Response, error) {
path := "/datacenters?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.DatacenterListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
if len(body.Datacenters) == 0 {
return nil, resp, nil
}
return DatacenterFromSchema(body.Datacenters[0]), resp, nil
}
// Get retrieves a datacenter by its ID if the input can be parsed as an integer, otherwise it retrieves a datacenter by its name.
func (c *DatacenterClient) Get(ctx context.Context, idOrName string) (*Datacenter, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))
}
return c.GetByName(ctx, idOrName)
}
// DatacenterListOpts specifies options for listing datacenters.
type DatacenterListOpts struct {
ListOpts
}
// List returns a list of datacenters for a specific page.
func (c *DatacenterClient) List(ctx context.Context, opts DatacenterListOpts) ([]*Datacenter, *Response, error) {
path := "/datacenters?" + valuesForListOpts(opts.ListOpts).Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.DatacenterListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
datacenters := make([]*Datacenter, 0, len(body.Datacenters))
for _, i := range body.Datacenters {
datacenters = append(datacenters, DatacenterFromSchema(i))
}
return datacenters, resp, nil
}
// All returns all datacenters.
func (c *DatacenterClient) All(ctx context.Context) ([]*Datacenter, error) {
allDatacenters := []*Datacenter{}
opts := DatacenterListOpts{}
opts.PerPage = 50
_, err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
datacenters, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allDatacenters = append(allDatacenters, datacenters...)
return resp, nil
})
if err != nil {
return nil, err
}
return allDatacenters, nil
}

View File

@ -0,0 +1,52 @@
package hcloud
import "fmt"
// ErrorCode represents an error code returned from the API.
type ErrorCode string
// Error codes returned from the API.
const (
ErrorCodeServiceError ErrorCode = "service_error" // Generic server error
ErrorCodeRateLimitExceeded ErrorCode = "rate_limit_exceeded" // Rate limit exceeded
ErrorCodeUnknownError ErrorCode = "unknown_error" // Unknown error
ErrorCodeNotFound ErrorCode = "not_found" // Resource not found
ErrorCodeInvalidInput ErrorCode = "invalid_input" // Validation error
// Deprecated error codes
// The actual value of this error code is limit_reached. The new error code
// rate_limit_exceeded for ratelimiting was introduced before Hetzner Cloud
// launched into the public. To make clients using the old error code still
// work as expected, we set the value of the old error code to that of the
// new error code.
ErrorCodeLimitReached = ErrorCodeRateLimitExceeded
)
// Error is an error returned from the API.
type Error struct {
Code ErrorCode
Message string
Details interface{}
}
func (e Error) Error() string {
return fmt.Sprintf("%s (%s)", e.Message, e.Code)
}
// ErrorDetailsInvalidInput contains the details of an 'invalid_input' error.
type ErrorDetailsInvalidInput struct {
Fields []ErrorDetailsInvalidInputField
}
// ErrorDetailsInvalidInputField contains the validation errors reported on a field.
type ErrorDetailsInvalidInputField struct {
Name string
Messages []string
}
// IsError returns whether err is an API error with the given error code.
func IsError(err error, code ErrorCode) bool {
apiErr, ok := err.(Error)
return ok && apiErr.Code == code
}

View File

@ -0,0 +1,335 @@
package hcloud
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// FloatingIP represents a Floating IP in the Hetzner Cloud.
type FloatingIP struct {
ID int
Description string
IP net.IP
Network *net.IPNet
Type FloatingIPType
Server *Server
DNSPtr map[string]string
HomeLocation *Location
Blocked bool
Protection FloatingIPProtection
Labels map[string]string
}
// DNSPtrForIP returns the reverse DNS pointer of the IP address.
func (f *FloatingIP) DNSPtrForIP(ip net.IP) string {
return f.DNSPtr[ip.String()]
}
// FloatingIPProtection represents the protection level of a Floating IP.
type FloatingIPProtection struct {
Delete bool
}
// FloatingIPType represents the type of a Floating IP.
type FloatingIPType string
// Floating IP types.
const (
FloatingIPTypeIPv4 FloatingIPType = "ipv4"
FloatingIPTypeIPv6 FloatingIPType = "ipv6"
)
// FloatingIPClient is a client for the Floating IP API.
type FloatingIPClient struct {
client *Client
}
// GetByID retrieves a Floating IP by its ID.
func (c *FloatingIPClient) GetByID(ctx context.Context, id int) (*FloatingIP, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/floating_ips/%d", id), nil)
if err != nil {
return nil, nil, err
}
var body schema.FloatingIPGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, resp, err
}
return FloatingIPFromSchema(body.FloatingIP), resp, nil
}
// FloatingIPListOpts specifies options for listing Floating IPs.
type FloatingIPListOpts struct {
ListOpts
}
// List returns a list of Floating IPs for a specific page.
func (c *FloatingIPClient) List(ctx context.Context, opts FloatingIPListOpts) ([]*FloatingIP, *Response, error) {
path := "/floating_ips?" + valuesForListOpts(opts.ListOpts).Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.FloatingIPListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
floatingIPs := make([]*FloatingIP, 0, len(body.FloatingIPs))
for _, s := range body.FloatingIPs {
floatingIPs = append(floatingIPs, FloatingIPFromSchema(s))
}
return floatingIPs, resp, nil
}
// All returns all Floating IPs.
func (c *FloatingIPClient) All(ctx context.Context) ([]*FloatingIP, error) {
return c.AllWithOpts(ctx, FloatingIPListOpts{ListOpts{PerPage: 50}})
}
// AllWithOpts returns all Floating IPs for the given options.
func (c *FloatingIPClient) AllWithOpts(ctx context.Context, opts FloatingIPListOpts) ([]*FloatingIP, error) {
allFloatingIPs := []*FloatingIP{}
_, err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
floatingIPs, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allFloatingIPs = append(allFloatingIPs, floatingIPs...)
return resp, nil
})
if err != nil {
return nil, err
}
return allFloatingIPs, nil
}
// FloatingIPCreateOpts specifies options for creating a Floating IP.
type FloatingIPCreateOpts struct {
Type FloatingIPType
HomeLocation *Location
Server *Server
Description *string
Labels map[string]string
}
// Validate checks if options are valid.
func (o FloatingIPCreateOpts) Validate() error {
switch o.Type {
case FloatingIPTypeIPv4, FloatingIPTypeIPv6:
break
default:
return errors.New("missing or invalid type")
}
if o.HomeLocation == nil && o.Server == nil {
return errors.New("one of home location or server is required")
}
return nil
}
// FloatingIPCreateResult is the result of creating a Floating IP.
type FloatingIPCreateResult struct {
FloatingIP *FloatingIP
Action *Action
}
// Create creates a Floating IP.
func (c *FloatingIPClient) Create(ctx context.Context, opts FloatingIPCreateOpts) (FloatingIPCreateResult, *Response, error) {
if err := opts.Validate(); err != nil {
return FloatingIPCreateResult{}, nil, err
}
reqBody := schema.FloatingIPCreateRequest{
Type: string(opts.Type),
Description: opts.Description,
}
if opts.HomeLocation != nil {
reqBody.HomeLocation = String(opts.HomeLocation.Name)
}
if opts.Server != nil {
reqBody.Server = Int(opts.Server.ID)
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return FloatingIPCreateResult{}, nil, err
}
req, err := c.client.NewRequest(ctx, "POST", "/floating_ips", bytes.NewReader(reqBodyData))
if err != nil {
return FloatingIPCreateResult{}, nil, err
}
var respBody schema.FloatingIPCreateResponse
resp, err := c.client.Do(req, &respBody)
if err != nil {
return FloatingIPCreateResult{}, resp, err
}
var action *Action
if respBody.Action != nil {
action = ActionFromSchema(*respBody.Action)
}
return FloatingIPCreateResult{
FloatingIP: FloatingIPFromSchema(respBody.FloatingIP),
Action: action,
}, resp, nil
}
// Delete deletes a Floating IP.
func (c *FloatingIPClient) Delete(ctx context.Context, floatingIP *FloatingIP) (*Response, error) {
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/floating_ips/%d", floatingIP.ID), nil)
if err != nil {
return nil, err
}
return c.client.Do(req, nil)
}
// FloatingIPUpdateOpts specifies options for updating a Floating IP.
type FloatingIPUpdateOpts struct {
Description string
Labels map[string]string
}
// Update updates a Floating IP.
func (c *FloatingIPClient) Update(ctx context.Context, floatingIP *FloatingIP, opts FloatingIPUpdateOpts) (*FloatingIP, *Response, error) {
reqBody := schema.FloatingIPUpdateRequest{
Description: opts.Description,
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/floating_ips/%d", floatingIP.ID)
req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.FloatingIPUpdateResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return FloatingIPFromSchema(respBody.FloatingIP), resp, nil
}
// Assign assigns a Floating IP to a server.
func (c *FloatingIPClient) Assign(ctx context.Context, floatingIP *FloatingIP, server *Server) (*Action, *Response, error) {
reqBody := schema.FloatingIPActionAssignRequest{
Server: server.ID,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/floating_ips/%d/actions/assign", floatingIP.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
var respBody schema.FloatingIPActionAssignResponse
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// Unassign unassigns a Floating IP from the currently assigned server.
func (c *FloatingIPClient) Unassign(ctx context.Context, floatingIP *FloatingIP) (*Action, *Response, error) {
var reqBody schema.FloatingIPActionUnassignRequest
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/floating_ips/%d/actions/unassign", floatingIP.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
var respBody schema.FloatingIPActionUnassignResponse
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// ChangeDNSPtr changes or resets the reverse DNS pointer for a Floating IP address.
// Pass a nil ptr to reset the reverse DNS pointer to its default value.
func (c *FloatingIPClient) ChangeDNSPtr(ctx context.Context, floatingIP *FloatingIP, ip string, ptr *string) (*Action, *Response, error) {
reqBody := schema.FloatingIPActionChangeDNSPtrRequest{
IP: ip,
DNSPtr: ptr,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/floating_ips/%d/actions/change_dns_ptr", floatingIP.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.FloatingIPActionChangeDNSPtrResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// FloatingIPChangeProtectionOpts specifies options for changing the resource protection level of a Floating IP.
type FloatingIPChangeProtectionOpts struct {
Delete *bool
}
// ChangeProtection changes the resource protection level of a Floating IP.
func (c *FloatingIPClient) ChangeProtection(ctx context.Context, floatingIP *FloatingIP, opts FloatingIPChangeProtectionOpts) (*Action, *Response, error) {
reqBody := schema.FloatingIPActionChangeProtectionRequest{
Delete: opts.Delete,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/floating_ips/%d/actions/change_protection", floatingIP.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.FloatingIPActionChangeProtectionResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, err
}

View File

@ -0,0 +1,5 @@
// Package hcloud is a library for the Hetzner Cloud API.
package hcloud
// Version is the library's version following Semantic Versioning.
const Version = "1.9.0"

View File

@ -0,0 +1,10 @@
package hcloud
// String returns a pointer to the passed string s.
func String(s string) *string { return &s }
// Int returns a pointer to the passed integer i.
func Int(i int) *int { return &i }
// Bool returns a pointer to the passed bool b.
func Bool(b bool) *bool { return &b }

View File

@ -0,0 +1,243 @@
package hcloud
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
"time"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// Image represents an Image in the Hetzner Cloud.
type Image struct {
ID int
Name string
Type ImageType
Status ImageStatus
Description string
ImageSize float32
DiskSize float32
Created time.Time
CreatedFrom *Server
BoundTo *Server
RapidDeploy bool
OSFlavor string
OSVersion string
Protection ImageProtection
Deprecated time.Time // The zero value denotes the image is not deprecated.
Labels map[string]string
}
// IsDeprecated returns whether the image is deprecated.
func (image *Image) IsDeprecated() bool {
return !image.Deprecated.IsZero()
}
// ImageProtection represents the protection level of an image.
type ImageProtection struct {
Delete bool
}
// ImageType specifies the type of an image.
type ImageType string
const (
// ImageTypeSnapshot represents a snapshot image.
ImageTypeSnapshot ImageType = "snapshot"
// ImageTypeBackup represents a backup image.
ImageTypeBackup ImageType = "backup"
// ImageTypeSystem represents a system image.
ImageTypeSystem ImageType = "system"
)
// ImageStatus specifies the status of an image.
type ImageStatus string
const (
// ImageStatusCreating is the status when an image is being created.
ImageStatusCreating ImageStatus = "creating"
// ImageStatusAvailable is the status when an image is available.
ImageStatusAvailable ImageStatus = "available"
)
// ImageClient is a client for the image API.
type ImageClient struct {
client *Client
}
// GetByID retrieves an image by its ID.
func (c *ImageClient) GetByID(ctx context.Context, id int) (*Image, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/images/%d", id), nil)
if err != nil {
return nil, nil, err
}
var body schema.ImageGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, nil, err
}
return ImageFromSchema(body.Image), resp, nil
}
// GetByName retrieves an image by its name.
func (c *ImageClient) GetByName(ctx context.Context, name string) (*Image, *Response, error) {
path := "/images?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.ImageListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
if len(body.Images) == 0 {
return nil, resp, nil
}
return ImageFromSchema(body.Images[0]), resp, nil
}
// Get retrieves an image by its ID if the input can be parsed as an integer, otherwise it retrieves an image by its name.
func (c *ImageClient) Get(ctx context.Context, idOrName string) (*Image, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))
}
return c.GetByName(ctx, idOrName)
}
// ImageListOpts specifies options for listing images.
type ImageListOpts struct {
ListOpts
}
// List returns a list of images for a specific page.
func (c *ImageClient) List(ctx context.Context, opts ImageListOpts) ([]*Image, *Response, error) {
path := "/images?" + valuesForListOpts(opts.ListOpts).Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.ImageListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
images := make([]*Image, 0, len(body.Images))
for _, i := range body.Images {
images = append(images, ImageFromSchema(i))
}
return images, resp, nil
}
// All returns all images.
func (c *ImageClient) All(ctx context.Context) ([]*Image, error) {
return c.AllWithOpts(ctx, ImageListOpts{ListOpts{PerPage: 50}})
}
// AllWithOpts returns all images for the given options.
func (c *ImageClient) AllWithOpts(ctx context.Context, opts ImageListOpts) ([]*Image, error) {
allImages := []*Image{}
_, err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
images, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allImages = append(allImages, images...)
return resp, nil
})
if err != nil {
return nil, err
}
return allImages, nil
}
// Delete deletes an image.
func (c *ImageClient) Delete(ctx context.Context, image *Image) (*Response, error) {
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/images/%d", image.ID), nil)
if err != nil {
return nil, err
}
return c.client.Do(req, nil)
}
// ImageUpdateOpts specifies options for updating an image.
type ImageUpdateOpts struct {
Description *string
Type ImageType
Labels map[string]string
}
// Update updates an image.
func (c *ImageClient) Update(ctx context.Context, image *Image, opts ImageUpdateOpts) (*Image, *Response, error) {
reqBody := schema.ImageUpdateRequest{
Description: opts.Description,
}
if opts.Type != "" {
reqBody.Type = String(string(opts.Type))
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/images/%d", image.ID)
req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.ImageUpdateResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ImageFromSchema(respBody.Image), resp, nil
}
// ImageChangeProtectionOpts specifies options for changing the resource protection level of an image.
type ImageChangeProtectionOpts struct {
Delete *bool
}
// ChangeProtection changes the resource protection level of an image.
func (c *ImageClient) ChangeProtection(ctx context.Context, image *Image, opts ImageChangeProtectionOpts) (*Action, *Response, error) {
reqBody := schema.ImageActionChangeProtectionRequest{
Delete: opts.Delete,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/images/%d/actions/change_protection", image.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.ImageActionChangeProtectionResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, err
}

135
vendor/github.com/hetznercloud/hcloud-go/hcloud/iso.go generated vendored Normal file
View File

@ -0,0 +1,135 @@
package hcloud
import (
"context"
"fmt"
"net/url"
"strconv"
"time"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// ISO represents an ISO image in the Hetzner Cloud.
type ISO struct {
ID int
Name string
Description string
Type ISOType
Deprecated time.Time
}
// IsDeprecated returns true if the ISO is deprecated
func (iso *ISO) IsDeprecated() bool {
return !iso.Deprecated.IsZero()
}
// ISOType specifies the type of an ISO image.
type ISOType string
const (
// ISOTypePublic is the type of a public ISO image.
ISOTypePublic ISOType = "public"
// ISOTypePrivate is the type of a private ISO image.
ISOTypePrivate ISOType = "private"
)
// ISOClient is a client for the ISO API.
type ISOClient struct {
client *Client
}
// GetByID retrieves an ISO by its ID.
func (c *ISOClient) GetByID(ctx context.Context, id int) (*ISO, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/isos/%d", id), nil)
if err != nil {
return nil, nil, err
}
var body schema.ISOGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, resp, err
}
return ISOFromSchema(body.ISO), resp, nil
}
// GetByName retrieves an ISO by its name.
func (c *ISOClient) GetByName(ctx context.Context, name string) (*ISO, *Response, error) {
path := "/isos?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.ISOListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
if len(body.ISOs) == 0 {
return nil, resp, nil
}
return ISOFromSchema(body.ISOs[0]), resp, nil
}
// Get retrieves an ISO by its ID if the input can be parsed as an integer, otherwise it retrieves an ISO by its name.
func (c *ISOClient) Get(ctx context.Context, idOrName string) (*ISO, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))
}
return c.GetByName(ctx, idOrName)
}
// ISOListOpts specifies options for listing isos.
type ISOListOpts struct {
ListOpts
}
// List returns a list of ISOs for a specific page.
func (c *ISOClient) List(ctx context.Context, opts ISOListOpts) ([]*ISO, *Response, error) {
path := "/isos?" + valuesForListOpts(opts.ListOpts).Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.ISOListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
isos := make([]*ISO, 0, len(body.ISOs))
for _, i := range body.ISOs {
isos = append(isos, ISOFromSchema(i))
}
return isos, resp, nil
}
// All returns all ISOs.
func (c *ISOClient) All(ctx context.Context) ([]*ISO, error) {
allISOs := []*ISO{}
opts := ISOListOpts{}
opts.PerPage = 50
_, err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
isos, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allISOs = append(allISOs, isos...)
return resp, nil
})
if err != nil {
return nil, err
}
return allISOs, nil
}

View File

@ -0,0 +1,120 @@
package hcloud
import (
"context"
"fmt"
"net/url"
"strconv"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// Location represents a location in the Hetzner Cloud.
type Location struct {
ID int
Name string
Description string
Country string
City string
Latitude float64
Longitude float64
}
// LocationClient is a client for the location API.
type LocationClient struct {
client *Client
}
// GetByID retrieves a location by its ID.
func (c *LocationClient) GetByID(ctx context.Context, id int) (*Location, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/locations/%d", id), nil)
if err != nil {
return nil, nil, err
}
var body schema.LocationGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, resp, err
}
return LocationFromSchema(body.Location), resp, nil
}
// GetByName retrieves an location by its name.
func (c *LocationClient) GetByName(ctx context.Context, name string) (*Location, *Response, error) {
path := "/locations?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.LocationListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
if len(body.Locations) == 0 {
return nil, resp, nil
}
return LocationFromSchema(body.Locations[0]), resp, nil
}
// Get retrieves a location by its ID if the input can be parsed as an integer, otherwise it retrieves a location by its name.
func (c *LocationClient) Get(ctx context.Context, idOrName string) (*Location, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))
}
return c.GetByName(ctx, idOrName)
}
// LocationListOpts specifies options for listing location.
type LocationListOpts struct {
ListOpts
}
// List returns a list of locations for a specific page.
func (c *LocationClient) List(ctx context.Context, opts LocationListOpts) ([]*Location, *Response, error) {
path := "/locations?" + valuesForListOpts(opts.ListOpts).Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.LocationListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
locations := make([]*Location, 0, len(body.Locations))
for _, i := range body.Locations {
locations = append(locations, LocationFromSchema(i))
}
return locations, resp, nil
}
// All returns all locations.
func (c *LocationClient) All(ctx context.Context) ([]*Location, error) {
allLocations := []*Location{}
opts := LocationListOpts{}
opts.PerPage = 50
_, err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
locations, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allLocations = append(allLocations, locations...)
return resp, nil
})
if err != nil {
return nil, err
}
return allLocations, nil
}

View File

@ -0,0 +1,80 @@
package hcloud
import (
"context"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// Pricing specifies pricing information for various resources.
type Pricing struct {
Image ImagePricing
FloatingIP FloatingIPPricing
Traffic TrafficPricing
ServerBackup ServerBackupPricing
ServerTypes []ServerTypePricing
}
// Price represents a price. Net amount, gross amount, as well as VAT rate are
// specified as strings and it is the user's responsibility to convert them to
// appropriate types for calculations.
type Price struct {
Currency string
VATRate string
Net string
Gross string
}
// ImagePricing provides pricing information for imaegs.
type ImagePricing struct {
PerGBMonth Price
}
// FloatingIPPricing provides pricing information for Floating IPs.
type FloatingIPPricing struct {
Monthly Price
}
// TrafficPricing provides pricing information for traffic.
type TrafficPricing struct {
PerTB Price
}
// ServerBackupPricing provides pricing information for server backups.
type ServerBackupPricing struct {
Percentage string
}
// ServerTypePricing provides pricing information for a server type.
type ServerTypePricing struct {
ServerType *ServerType
Pricings []ServerTypeLocationPricing
}
// ServerTypeLocationPricing provides pricing information for a server type
// at a location.
type ServerTypeLocationPricing struct {
Location *Location
Hourly Price
Monthly Price
}
// PricingClient is a client for the pricing API.
type PricingClient struct {
client *Client
}
// Get retrieves pricing information.
func (c *PricingClient) Get(ctx context.Context) (Pricing, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", "/pricing", nil)
if err != nil {
return Pricing{}, nil, err
}
var body schema.PricingGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return Pricing{}, nil, err
}
return PricingFromSchema(body.Pricing), resp, nil
}

View File

@ -0,0 +1,399 @@
package hcloud
import (
"net"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// This file provides converter functions to convert models in the
// schema package to models in the hcloud package.
// ActionFromSchema converts a schema.Action to an Action.
func ActionFromSchema(s schema.Action) *Action {
action := &Action{
ID: s.ID,
Status: ActionStatus(s.Status),
Command: s.Command,
Progress: s.Progress,
Started: s.Started,
Resources: []*ActionResource{},
}
if s.Finished != nil {
action.Finished = *s.Finished
}
if s.Error != nil {
action.ErrorCode = s.Error.Code
action.ErrorMessage = s.Error.Message
}
for _, r := range s.Resources {
action.Resources = append(action.Resources, &ActionResource{
ID: r.ID,
Type: ActionResourceType(r.Type),
})
}
return action
}
// FloatingIPFromSchema converts a schema.FloatingIP to a FloatingIP.
func FloatingIPFromSchema(s schema.FloatingIP) *FloatingIP {
f := &FloatingIP{
ID: s.ID,
Type: FloatingIPType(s.Type),
HomeLocation: LocationFromSchema(s.HomeLocation),
Blocked: s.Blocked,
Protection: FloatingIPProtection{
Delete: s.Protection.Delete,
},
}
if s.Description != nil {
f.Description = *s.Description
}
if s.Server != nil {
f.Server = &Server{ID: *s.Server}
}
if f.Type == FloatingIPTypeIPv4 {
f.IP = net.ParseIP(s.IP)
} else {
f.IP, f.Network, _ = net.ParseCIDR(s.IP)
}
f.DNSPtr = map[string]string{}
for _, entry := range s.DNSPtr {
f.DNSPtr[entry.IP] = entry.DNSPtr
}
f.Labels = map[string]string{}
for key, value := range s.Labels {
f.Labels[key] = value
}
return f
}
// ISOFromSchema converts a schema.ISO to an ISO.
func ISOFromSchema(s schema.ISO) *ISO {
return &ISO{
ID: s.ID,
Name: s.Name,
Description: s.Description,
Type: ISOType(s.Type),
Deprecated: s.Deprecated,
}
}
// LocationFromSchema converts a schema.Location to a Location.
func LocationFromSchema(s schema.Location) *Location {
return &Location{
ID: s.ID,
Name: s.Name,
Description: s.Description,
Country: s.Country,
City: s.City,
Latitude: s.Latitude,
Longitude: s.Longitude,
}
}
// DatacenterFromSchema converts a schema.Datacenter to a Datacenter.
func DatacenterFromSchema(s schema.Datacenter) *Datacenter {
d := &Datacenter{
ID: s.ID,
Name: s.Name,
Description: s.Description,
Location: LocationFromSchema(s.Location),
ServerTypes: DatacenterServerTypes{
Available: []*ServerType{},
Supported: []*ServerType{},
},
}
for _, t := range s.ServerTypes.Available {
d.ServerTypes.Available = append(d.ServerTypes.Available, &ServerType{ID: t})
}
for _, t := range s.ServerTypes.Supported {
d.ServerTypes.Supported = append(d.ServerTypes.Supported, &ServerType{ID: t})
}
return d
}
// ServerFromSchema converts a schema.Server to a Server.
func ServerFromSchema(s schema.Server) *Server {
server := &Server{
ID: s.ID,
Name: s.Name,
Status: ServerStatus(s.Status),
Created: s.Created,
PublicNet: ServerPublicNetFromSchema(s.PublicNet),
ServerType: ServerTypeFromSchema(s.ServerType),
IncludedTraffic: s.IncludedTraffic,
RescueEnabled: s.RescueEnabled,
Datacenter: DatacenterFromSchema(s.Datacenter),
Locked: s.Locked,
Protection: ServerProtection{
Delete: s.Protection.Delete,
Rebuild: s.Protection.Rebuild,
},
}
if s.Image != nil {
server.Image = ImageFromSchema(*s.Image)
}
if s.BackupWindow != nil {
server.BackupWindow = *s.BackupWindow
}
if s.OutgoingTraffic != nil {
server.OutgoingTraffic = *s.OutgoingTraffic
}
if s.IngoingTraffic != nil {
server.IngoingTraffic = *s.IngoingTraffic
}
if s.ISO != nil {
server.ISO = ISOFromSchema(*s.ISO)
}
server.Labels = map[string]string{}
for key, value := range s.Labels {
server.Labels[key] = value
}
for _, id := range s.Volumes {
server.Volumes = append(server.Volumes, &Volume{ID: id})
}
return server
}
// ServerPublicNetFromSchema converts a schema.ServerPublicNet to a ServerPublicNet.
func ServerPublicNetFromSchema(s schema.ServerPublicNet) ServerPublicNet {
publicNet := ServerPublicNet{
IPv4: ServerPublicNetIPv4FromSchema(s.IPv4),
IPv6: ServerPublicNetIPv6FromSchema(s.IPv6),
}
for _, id := range s.FloatingIPs {
publicNet.FloatingIPs = append(publicNet.FloatingIPs, &FloatingIP{ID: id})
}
return publicNet
}
// ServerPublicNetIPv4FromSchema converts a schema.ServerPublicNetIPv4 to
// a ServerPublicNetIPv4.
func ServerPublicNetIPv4FromSchema(s schema.ServerPublicNetIPv4) ServerPublicNetIPv4 {
return ServerPublicNetIPv4{
IP: net.ParseIP(s.IP),
Blocked: s.Blocked,
DNSPtr: s.DNSPtr,
}
}
// ServerPublicNetIPv6FromSchema converts a schema.ServerPublicNetIPv6 to
// a ServerPublicNetIPv6.
func ServerPublicNetIPv6FromSchema(s schema.ServerPublicNetIPv6) ServerPublicNetIPv6 {
ipv6 := ServerPublicNetIPv6{
Blocked: s.Blocked,
DNSPtr: map[string]string{},
}
ipv6.IP, ipv6.Network, _ = net.ParseCIDR(s.IP)
for _, dnsPtr := range s.DNSPtr {
ipv6.DNSPtr[dnsPtr.IP] = dnsPtr.DNSPtr
}
return ipv6
}
// ServerTypeFromSchema converts a schema.ServerType to a ServerType.
func ServerTypeFromSchema(s schema.ServerType) *ServerType {
st := &ServerType{
ID: s.ID,
Name: s.Name,
Description: s.Description,
Cores: s.Cores,
Memory: s.Memory,
Disk: s.Disk,
StorageType: StorageType(s.StorageType),
CPUType: CPUType(s.CPUType),
}
for _, price := range s.Prices {
st.Pricings = append(st.Pricings, ServerTypeLocationPricing{
Location: &Location{Name: price.Location},
Hourly: Price{
Net: price.PriceHourly.Net,
Gross: price.PriceHourly.Gross,
},
Monthly: Price{
Net: price.PriceMonthly.Net,
Gross: price.PriceMonthly.Gross,
},
})
}
return st
}
// SSHKeyFromSchema converts a schema.SSHKey to a SSHKey.
func SSHKeyFromSchema(s schema.SSHKey) *SSHKey {
sshKey := &SSHKey{
ID: s.ID,
Name: s.Name,
Fingerprint: s.Fingerprint,
PublicKey: s.PublicKey,
}
sshKey.Labels = map[string]string{}
for key, value := range s.Labels {
sshKey.Labels[key] = value
}
return sshKey
}
// ImageFromSchema converts a schema.Image to an Image.
func ImageFromSchema(s schema.Image) *Image {
i := &Image{
ID: s.ID,
Type: ImageType(s.Type),
Status: ImageStatus(s.Status),
Description: s.Description,
DiskSize: s.DiskSize,
Created: s.Created,
RapidDeploy: s.RapidDeploy,
OSFlavor: s.OSFlavor,
Protection: ImageProtection{
Delete: s.Protection.Delete,
},
Deprecated: s.Deprecated,
}
if s.Name != nil {
i.Name = *s.Name
}
if s.ImageSize != nil {
i.ImageSize = *s.ImageSize
}
if s.OSVersion != nil {
i.OSVersion = *s.OSVersion
}
if s.CreatedFrom != nil {
i.CreatedFrom = &Server{
ID: s.CreatedFrom.ID,
Name: s.CreatedFrom.Name,
}
}
if s.BoundTo != nil {
i.BoundTo = &Server{
ID: *s.BoundTo,
}
}
i.Labels = map[string]string{}
for key, value := range s.Labels {
i.Labels[key] = value
}
return i
}
// VolumeFromSchema converts a schema.Volume to a Volume.
func VolumeFromSchema(s schema.Volume) *Volume {
v := &Volume{
ID: s.ID,
Name: s.Name,
Location: LocationFromSchema(s.Location),
Size: s.Size,
LinuxDevice: s.LinuxDevice,
Protection: VolumeProtection{
Delete: s.Protection.Delete,
},
Created: s.Created,
}
if s.Server != nil {
v.Server = &Server{ID: *s.Server}
}
v.Labels = map[string]string{}
for key, value := range s.Labels {
v.Labels[key] = value
}
return v
}
// PaginationFromSchema converts a schema.MetaPagination to a Pagination.
func PaginationFromSchema(s schema.MetaPagination) Pagination {
return Pagination{
Page: s.Page,
PerPage: s.PerPage,
PreviousPage: s.PreviousPage,
NextPage: s.NextPage,
LastPage: s.LastPage,
TotalEntries: s.TotalEntries,
}
}
// ErrorFromSchema converts a schema.Error to an Error.
func ErrorFromSchema(s schema.Error) Error {
e := Error{
Code: ErrorCode(s.Code),
Message: s.Message,
}
switch d := s.Details.(type) {
case schema.ErrorDetailsInvalidInput:
details := ErrorDetailsInvalidInput{
Fields: []ErrorDetailsInvalidInputField{},
}
for _, field := range d.Fields {
details.Fields = append(details.Fields, ErrorDetailsInvalidInputField{
Name: field.Name,
Messages: field.Messages,
})
}
e.Details = details
}
return e
}
// PricingFromSchema converts a schema.Pricing to a Pricing.
func PricingFromSchema(s schema.Pricing) Pricing {
p := Pricing{
Image: ImagePricing{
PerGBMonth: Price{
Currency: s.Currency,
VATRate: s.VATRate,
Net: s.Image.PricePerGBMonth.Net,
Gross: s.Image.PricePerGBMonth.Gross,
},
},
FloatingIP: FloatingIPPricing{
Monthly: Price{
Currency: s.Currency,
VATRate: s.VATRate,
Net: s.FloatingIP.PriceMonthly.Net,
Gross: s.FloatingIP.PriceMonthly.Gross,
},
},
Traffic: TrafficPricing{
PerTB: Price{
Currency: s.Currency,
VATRate: s.VATRate,
Net: s.Traffic.PricePerTB.Net,
Gross: s.Traffic.PricePerTB.Gross,
},
},
ServerBackup: ServerBackupPricing{
Percentage: s.ServerBackup.Percentage,
},
}
for _, serverType := range s.ServerTypes {
var pricings []ServerTypeLocationPricing
for _, price := range serverType.Prices {
pricings = append(pricings, ServerTypeLocationPricing{
Location: &Location{Name: price.Location},
Hourly: Price{
Currency: s.Currency,
VATRate: s.VATRate,
Net: price.PriceHourly.Net,
Gross: price.PriceHourly.Gross,
},
Monthly: Price{
Currency: s.Currency,
VATRate: s.VATRate,
Net: price.PriceMonthly.Net,
Gross: price.PriceMonthly.Gross,
},
})
}
p.ServerTypes = append(p.ServerTypes, ServerTypePricing{
ServerType: &ServerType{
ID: serverType.ID,
Name: serverType.Name,
},
Pricings: pricings,
})
}
return p
}

View File

@ -0,0 +1,39 @@
package schema
import "time"
// Action defines the schema of an action.
type Action struct {
ID int `json:"id"`
Status string `json:"status"`
Command string `json:"command"`
Progress int `json:"progress"`
Started time.Time `json:"started"`
Finished *time.Time `json:"finished"`
Error *ActionError `json:"error"`
Resources []ActionResourceReference `json:"resources"`
}
// ActionResourceReference defines the schema of an action resource reference.
type ActionResourceReference struct {
ID int `json:"id"`
Type string `json:"type"`
}
// ActionError defines the schema of an error embedded
// in an action.
type ActionError struct {
Code string `json:"code"`
Message string `json:"message"`
}
// ActionGetResponse is the schema of the response when
// retrieving a single action.
type ActionGetResponse struct {
Action Action `json:"action"`
}
// ActionListResponse defines the schema of the response when listing actions.
type ActionListResponse struct {
Actions []Action `json:"actions"`
}

View File

@ -0,0 +1,23 @@
package schema
// Datacenter defines the schema of a datacenter.
type Datacenter struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Location Location `json:"location"`
ServerTypes struct {
Supported []int `json:"supported"`
Available []int `json:"available"`
} `json:"server_types"`
}
// DatacenterGetResponse defines the schema of the response when retrieving a single datacenter.
type DatacenterGetResponse struct {
Datacenter Datacenter `json:"datacenter"`
}
// DatacenterListResponse defines the schema of the response when listing datacenters.
type DatacenterListResponse struct {
Datacenters []Datacenter `json:"datacenters"`
}

View File

@ -0,0 +1,43 @@
package schema
import "encoding/json"
// Error represents the schema of an error response.
type Error struct {
Code string `json:"code"`
Message string `json:"message"`
DetailsRaw json.RawMessage `json:"details"`
Details interface{}
}
// UnmarshalJSON overrides default json unmarshalling.
func (e *Error) UnmarshalJSON(data []byte) (err error) {
type Alias Error
alias := (*Alias)(e)
if err = json.Unmarshal(data, alias); err != nil {
return
}
switch e.Code {
case "invalid_input":
details := ErrorDetailsInvalidInput{}
if err = json.Unmarshal(e.DetailsRaw, &details); err != nil {
return
}
alias.Details = details
}
return
}
// ErrorResponse defines the schema of a response containing an error.
type ErrorResponse struct {
Error Error `json:"error"`
}
// ErrorDetailsInvalidInput defines the schema of the Details field
// of an error with code 'invalid_input'.
type ErrorDetailsInvalidInput struct {
Fields []struct {
Name string `json:"name"`
Messages []string `json:"messages"`
} `json:"fields"`
}

View File

@ -0,0 +1,112 @@
package schema
// FloatingIP defines the schema of a Floating IP.
type FloatingIP struct {
ID int `json:"id"`
Description *string `json:"description"`
IP string `json:"ip"`
Type string `json:"type"`
Server *int `json:"server"`
DNSPtr []FloatingIPDNSPtr `json:"dns_ptr"`
HomeLocation Location `json:"home_location"`
Blocked bool `json:"blocked"`
Protection FloatingIPProtection `json:"protection"`
Labels map[string]string `json:"labels"`
}
// FloatingIPProtection represents the protection level of a Floating IP.
type FloatingIPProtection struct {
Delete bool `json:"delete"`
}
// FloatingIPDNSPtr contains reverse DNS information for a
// IPv4 or IPv6 Floating IP.
type FloatingIPDNSPtr struct {
IP string `json:"ip"`
DNSPtr string `json:"dns_ptr"`
}
// FloatingIPGetResponse defines the schema of the response when
// retrieving a single Floating IP.
type FloatingIPGetResponse struct {
FloatingIP FloatingIP `json:"floating_ip"`
}
// FloatingIPUpdateRequest defines the schema of the request to update a Floating IP.
type FloatingIPUpdateRequest struct {
Description string `json:"description,omitempty"`
Labels *map[string]string `json:"labels,omitempty"`
}
// FloatingIPUpdateResponse defines the schema of the response when updating a Floating IP.
type FloatingIPUpdateResponse struct {
FloatingIP FloatingIP `json:"floating_ip"`
}
// FloatingIPListResponse defines the schema of the response when
// listing Floating IPs.
type FloatingIPListResponse struct {
FloatingIPs []FloatingIP `json:"floating_ips"`
}
// FloatingIPCreateRequest defines the schema of the request to
// create a Floating IP.
type FloatingIPCreateRequest struct {
Type string `json:"type"`
HomeLocation *string `json:"home_location,omitempty"`
Server *int `json:"server,omitempty"`
Description *string `json:"description,omitempty"`
Labels *map[string]string `json:"labels,omitempty"`
}
// FloatingIPCreateResponse defines the schema of the response
// when creating a Floating IP.
type FloatingIPCreateResponse struct {
FloatingIP FloatingIP `json:"floating_ip"`
Action *Action `json:"action"`
}
// FloatingIPActionAssignRequest defines the schema of the request to
// create an assign Floating IP action.
type FloatingIPActionAssignRequest struct {
Server int `json:"server"`
}
// FloatingIPActionAssignResponse defines the schema of the response when
// creating an assign action.
type FloatingIPActionAssignResponse struct {
Action Action `json:"action"`
}
// FloatingIPActionUnassignRequest defines the schema of the request to
// create an unassign Floating IP action.
type FloatingIPActionUnassignRequest struct{}
// FloatingIPActionUnassignResponse defines the schema of the response when
// creating an unassign action.
type FloatingIPActionUnassignResponse struct {
Action Action `json:"action"`
}
// FloatingIPActionChangeDNSPtrRequest defines the schema for the request to
// change a Floating IP's reverse DNS pointer.
type FloatingIPActionChangeDNSPtrRequest struct {
IP string `json:"ip"`
DNSPtr *string `json:"dns_ptr"`
}
// FloatingIPActionChangeDNSPtrResponse defines the schema of the response when
// creating a change_dns_ptr Floating IP action.
type FloatingIPActionChangeDNSPtrResponse struct {
Action Action `json:"action"`
}
// FloatingIPActionChangeProtectionRequest defines the schema of the request to change the resource protection of a Floating IP.
type FloatingIPActionChangeProtectionRequest struct {
Delete *bool `json:"delete,omitempty"`
}
// FloatingIPActionChangeProtectionResponse defines the schema of the response when changing the resource protection of a Floating IP.
type FloatingIPActionChangeProtectionResponse struct {
Action Action `json:"action"`
}

View File

@ -0,0 +1,68 @@
package schema
import "time"
// Image defines the schema of an image.
type Image struct {
ID int `json:"id"`
Status string `json:"status"`
Type string `json:"type"`
Name *string `json:"name"`
Description string `json:"description"`
ImageSize *float32 `json:"image_size"`
DiskSize float32 `json:"disk_size"`
Created time.Time `json:"created"`
CreatedFrom *ImageCreatedFrom `json:"created_from"`
BoundTo *int `json:"bound_to"`
OSFlavor string `json:"os_flavor"`
OSVersion *string `json:"os_version"`
RapidDeploy bool `json:"rapid_deploy"`
Protection ImageProtection `json:"protection"`
Deprecated time.Time `json:"deprecated"`
Labels map[string]string `json:"labels"`
}
// ImageProtection represents the protection level of a image.
type ImageProtection struct {
Delete bool `json:"delete"`
}
// ImageCreatedFrom defines the schema of the images created from reference.
type ImageCreatedFrom struct {
ID int `json:"id"`
Name string `json:"name"`
}
// ImageGetResponse defines the schema of the response when
// retrieving a single image.
type ImageGetResponse struct {
Image Image `json:"image"`
}
// ImageListResponse defines the schema of the response when
// listing images.
type ImageListResponse struct {
Images []Image `json:"images"`
}
// ImageUpdateRequest defines the schema of the request to update an image.
type ImageUpdateRequest struct {
Description *string `json:"description,omitempty"`
Type *string `json:"type,omitempty"`
Labels *map[string]string `json:"labels,omitempty"`
}
// ImageUpdateResponse defines the schema of the response when updating an image.
type ImageUpdateResponse struct {
Image Image `json:"image"`
}
// ImageActionChangeProtectionRequest defines the schema of the request to change the resource protection of an image.
type ImageActionChangeProtectionRequest struct {
Delete *bool `json:"delete,omitempty"`
}
// ImageActionChangeProtectionResponse defines the schema of the response when changing the resource protection of an image.
type ImageActionChangeProtectionResponse struct {
Action Action `json:"action"`
}

View File

@ -0,0 +1,22 @@
package schema
import "time"
// ISO defines the schema of an ISO image.
type ISO struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"`
Deprecated time.Time `json:"deprecated"`
}
// ISOGetResponse defines the schema of the response when retrieving a single ISO.
type ISOGetResponse struct {
ISO ISO `json:"iso"`
}
// ISOListResponse defines the schema of the response when listing ISOs.
type ISOListResponse struct {
ISOs []ISO `json:"isos"`
}

View File

@ -0,0 +1,22 @@
package schema
// Location defines the schema of a location.
type Location struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Country string `json:"country"`
City string `json:"city"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
// LocationGetResponse defines the schema of the response when retrieving a single location.
type LocationGetResponse struct {
Location Location `json:"location"`
}
// LocationListResponse defines the schema of the response when listing locations.
type LocationListResponse struct {
Locations []Location `json:"locations"`
}

View File

@ -0,0 +1,23 @@
package schema
// Meta defines the schema of meta information which may be included
// in responses.
type Meta struct {
Pagination *MetaPagination `json:"pagination"`
}
// MetaPagination defines the schema of pagination information.
type MetaPagination struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
PreviousPage int `json:"previous_page"`
NextPage int `json:"next_page"`
LastPage int `json:"last_page"`
TotalEntries int `json:"total_entries"`
}
// MetaResponse defines the schema of a response containing
// meta information.
type MetaResponse struct {
Meta Meta `json:"meta"`
}

View File

@ -0,0 +1,58 @@
package schema
// Pricing defines the schema for pricing information.
type Pricing struct {
Currency string `json:"currency"`
VATRate string `json:"vat_rate"`
Image PricingImage `json:"image"`
FloatingIP PricingFloatingIP `json:"floating_ip"`
Traffic PricingTraffic `json:"traffic"`
ServerBackup PricingServerBackup `json:"server_backup"`
ServerTypes []PricingServerType `json:"server_types"`
}
// Price defines the schema of a single price with net and gross amount.
type Price struct {
Net string `json:"net"`
Gross string `json:"gross"`
}
// PricingImage defines the schema of pricing information for an image.
type PricingImage struct {
PricePerGBMonth Price `json:"price_per_gb_month"`
}
// PricingFloatingIP defines the schema of pricing information for a Floating IP.
type PricingFloatingIP struct {
PriceMonthly Price `json:"price_monthly"`
}
// PricingTraffic defines the schema of pricing information for traffic.
type PricingTraffic struct {
PricePerTB Price `json:"price_per_tb"`
}
// PricingServerBackup defines the schema of pricing information for server backups.
type PricingServerBackup struct {
Percentage string `json:"percentage"`
}
// PricingServerType defines the schema of pricing information for a server type.
type PricingServerType struct {
ID int `json:"id"`
Name string `json:"name"`
Prices []PricingServerTypePrice `json:"prices"`
}
// PricingServerTypePrice defines the schema of pricing information for a server
// type at a location.
type PricingServerTypePrice struct {
Location string `json:"location"`
PriceHourly Price `json:"price_hourly"`
PriceMonthly Price `json:"price_monthly"`
}
// PricingGetResponse defines the schema of the response when retrieving pricing information.
type PricingGetResponse struct {
Pricing Pricing `json:"pricing"`
}

View File

@ -0,0 +1,300 @@
package schema
import "time"
// Server defines the schema of a server.
type Server struct {
ID int `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Created time.Time `json:"created"`
PublicNet ServerPublicNet `json:"public_net"`
ServerType ServerType `json:"server_type"`
IncludedTraffic uint64 `json:"included_traffic"`
OutgoingTraffic *uint64 `json:"outgoing_traffic"`
IngoingTraffic *uint64 `json:"ingoing_traffic"`
BackupWindow *string `json:"backup_window"`
RescueEnabled bool `json:"rescue_enabled"`
ISO *ISO `json:"iso"`
Locked bool `json:"locked"`
Datacenter Datacenter `json:"datacenter"`
Image *Image `json:"image"`
Protection ServerProtection `json:"protection"`
Labels map[string]string `json:"labels"`
Volumes []int `json:"volumes"`
}
// ServerProtection defines the schema of a server's resource protection.
type ServerProtection struct {
Delete bool `json:"delete"`
Rebuild bool `json:"rebuild"`
}
// ServerPublicNet defines the schema of a server's
// public network information.
type ServerPublicNet struct {
IPv4 ServerPublicNetIPv4 `json:"ipv4"`
IPv6 ServerPublicNetIPv6 `json:"ipv6"`
FloatingIPs []int `json:"floating_ips"`
}
// ServerPublicNetIPv4 defines the schema of a server's public
// network information for an IPv4.
type ServerPublicNetIPv4 struct {
IP string `json:"ip"`
Blocked bool `json:"blocked"`
DNSPtr string `json:"dns_ptr"`
}
// ServerPublicNetIPv6 defines the schema of a server's public
// network information for an IPv6.
type ServerPublicNetIPv6 struct {
IP string `json:"ip"`
Blocked bool `json:"blocked"`
DNSPtr []ServerPublicNetIPv6DNSPtr `json:"dns_ptr"`
}
// ServerPublicNetIPv6DNSPtr defines the schema of a server's
// public network information for an IPv6 reverse DNS.
type ServerPublicNetIPv6DNSPtr struct {
IP string `json:"ip"`
DNSPtr string `json:"dns_ptr"`
}
// ServerGetResponse defines the schema of the response when
// retrieving a single server.
type ServerGetResponse struct {
Server Server `json:"server"`
}
// ServerListResponse defines the schema of the response when
// listing servers.
type ServerListResponse struct {
Servers []Server `json:"servers"`
}
// ServerCreateRequest defines the schema for the request to
// create a server.
type ServerCreateRequest struct {
Name string `json:"name"`
ServerType interface{} `json:"server_type"` // int or string
Image interface{} `json:"image"` // int or string
SSHKeys []int `json:"ssh_keys,omitempty"`
Location string `json:"location,omitempty"`
Datacenter string `json:"datacenter,omitempty"`
UserData string `json:"user_data,omitempty"`
StartAfterCreate *bool `json:"start_after_create,omitempty"`
Labels *map[string]string `json:"labels,omitempty"`
}
// ServerCreateResponse defines the schema of the response when
// creating a server.
type ServerCreateResponse struct {
Server Server `json:"server"`
Action Action `json:"action"`
RootPassword *string `json:"root_password"`
}
// ServerUpdateRequest defines the schema of the request to update a server.
type ServerUpdateRequest struct {
Name string `json:"name,omitempty"`
Labels *map[string]string `json:"labels,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{}
// ServerActionPoweronResponse defines the schema of the response when
// creating a poweron server action.
type ServerActionPoweronResponse struct {
Action Action `json:"action"`
}
// ServerActionPoweroffRequest defines the schema for the request to
// create a poweroff server action.
type ServerActionPoweroffRequest struct{}
// ServerActionPoweroffResponse defines the schema of the response when
// creating a poweroff server action.
type ServerActionPoweroffResponse struct {
Action Action `json:"action"`
}
// ServerActionRebootRequest defines the schema for the request to
// create a reboot server action.
type ServerActionRebootRequest struct{}
// ServerActionRebootResponse defines the schema of the response when
// creating a reboot server action.
type ServerActionRebootResponse struct {
Action Action `json:"action"`
}
// ServerActionResetRequest defines the schema for the request to
// create a reset server action.
type ServerActionResetRequest struct{}
// ServerActionResetResponse defines the schema of the response when
// creating a reset server action.
type ServerActionResetResponse struct {
Action Action `json:"action"`
}
// ServerActionShutdownRequest defines the schema for the request to
// create a shutdown server action.
type ServerActionShutdownRequest struct{}
// ServerActionShutdownResponse defines the schema of the response when
// creating a shutdown server action.
type ServerActionShutdownResponse struct {
Action Action `json:"action"`
}
// ServerActionResetPasswordRequest defines the schema for the request to
// create a reset_password server action.
type ServerActionResetPasswordRequest struct{}
// ServerActionResetPasswordResponse defines the schema of the response when
// creating a reset_password server action.
type ServerActionResetPasswordResponse struct {
Action Action `json:"action"`
RootPassword string `json:"root_password"`
}
// ServerActionCreateImageRequest defines the schema for the request to
// create a create_image server action.
type ServerActionCreateImageRequest struct {
Type *string `json:"type"`
Description *string `json:"description"`
Labels *map[string]string `json:"labels,omitempty"`
}
// ServerActionCreateImageResponse defines the schema of the response when
// creating a create_image server action.
type ServerActionCreateImageResponse struct {
Action Action `json:"action"`
Image Image `json:"image"`
}
// ServerActionEnableRescueRequest defines the schema for the request to
// create a enable_rescue server action.
type ServerActionEnableRescueRequest struct {
Type *string `json:"type,omitempty"`
SSHKeys []int `json:"ssh_keys,omitempty"`
}
// ServerActionEnableRescueResponse defines the schema of the response when
// creating a enable_rescue server action.
type ServerActionEnableRescueResponse struct {
Action Action `json:"action"`
RootPassword string `json:"root_password"`
}
// ServerActionDisableRescueRequest defines the schema for the request to
// create a disable_rescue server action.
type ServerActionDisableRescueRequest struct{}
// ServerActionDisableRescueResponse defines the schema of the response when
// creating a disable_rescue server action.
type ServerActionDisableRescueResponse struct {
Action Action `json:"action"`
}
// ServerActionRebuildRequest defines the schema for the request to
// rebuild a server.
type ServerActionRebuildRequest struct {
Image interface{} `json:"image"` // int or string
}
// ServerActionRebuildResponse defines the schema of the response when
// creating a rebuild server action.
type ServerActionRebuildResponse struct {
Action Action `json:"action"`
}
// ServerActionAttachISORequest defines the schema for the request to
// attach an ISO to a server.
type ServerActionAttachISORequest struct {
ISO interface{} `json:"iso"` // int or string
}
// ServerActionAttachISOResponse defines the schema of the response when
// creating a attach_iso server action.
type ServerActionAttachISOResponse struct {
Action Action `json:"action"`
}
// ServerActionDetachISORequest defines the schema for the request to
// detach an ISO from a server.
type ServerActionDetachISORequest struct{}
// ServerActionDetachISOResponse defines the schema of the response when
// creating a detach_iso server action.
type ServerActionDetachISOResponse struct {
Action Action `json:"action"`
}
// ServerActionEnableBackupRequest defines the schema for the request to
// enable backup for a server.
type ServerActionEnableBackupRequest struct {
BackupWindow *string `json:"backup_window,omitempty"`
}
// ServerActionEnableBackupResponse defines the schema of the response when
// creating a enable_backup server action.
type ServerActionEnableBackupResponse struct {
Action Action `json:"action"`
}
// ServerActionDisableBackupRequest defines the schema for the request to
// disable backup for a server.
type ServerActionDisableBackupRequest struct{}
// ServerActionDisableBackupResponse defines the schema of the response when
// creating a disable_backup server action.
type ServerActionDisableBackupResponse struct {
Action Action `json:"action"`
}
// ServerActionChangeTypeRequest defines the schema for the request to
// change a server's type.
type ServerActionChangeTypeRequest struct {
ServerType interface{} `json:"server_type"` // int or string
UpgradeDisk bool `json:"upgrade_disk"`
}
// ServerActionChangeTypeResponse defines the schema of the response when
// creating a change_type server action.
type ServerActionChangeTypeResponse struct {
Action Action `json:"action"`
}
// ServerActionChangeDNSPtrRequest defines the schema for the request to
// change a server's reverse DNS pointer.
type ServerActionChangeDNSPtrRequest struct {
IP string `json:"ip"`
DNSPtr *string `json:"dns_ptr"`
}
// ServerActionChangeDNSPtrResponse defines the schema of the response when
// creating a change_dns_ptr server action.
type ServerActionChangeDNSPtrResponse struct {
Action Action `json:"action"`
}
// ServerActionChangeProtectionRequest defines the schema of the request to change the resource protection of a server.
type ServerActionChangeProtectionRequest struct {
Rebuild *bool `json:"rebuild,omitempty"`
Delete *bool `json:"delete,omitempty"`
}
// ServerActionChangeProtectionResponse defines the schema of the response when changing the resource protection of a server.
type ServerActionChangeProtectionResponse struct {
Action Action `json:"action"`
}

View File

@ -0,0 +1,26 @@
package schema
// ServerType defines the schema of a server type.
type ServerType struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Cores int `json:"cores"`
Memory float32 `json:"memory"`
Disk int `json:"disk"`
StorageType string `json:"storage_type"`
CPUType string `json:"cpu_type"`
Prices []PricingServerTypePrice `json:"prices"`
}
// ServerTypeListResponse defines the schema of the response when
// listing server types.
type ServerTypeListResponse struct {
ServerTypes []ServerType `json:"server_types"`
}
// ServerTypeGetResponse defines the schema of the response when
// retrieving a single server type.
type ServerTypeGetResponse struct {
ServerType ServerType `json:"server_type"`
}

View File

@ -0,0 +1,47 @@
package schema
// SSHKey defines the schema of a SSH key.
type SSHKey struct {
ID int `json:"id"`
Name string `json:"name"`
Fingerprint string `json:"fingerprint"`
PublicKey string `json:"public_key"`
Labels map[string]string `json:"labels"`
}
// SSHKeyCreateRequest defines the schema of the request
// to create a SSH key.
type SSHKeyCreateRequest struct {
Name string `json:"name"`
PublicKey string `json:"public_key"`
Labels *map[string]string `json:"labels,omitempty"`
}
// SSHKeyCreateResponse defines the schema of the response
// when creating a SSH key.
type SSHKeyCreateResponse struct {
SSHKey SSHKey `json:"ssh_key"`
}
// SSHKeyListResponse defines the schema of the response
// when listing SSH keys.
type SSHKeyListResponse struct {
SSHKeys []SSHKey `json:"ssh_keys"`
}
// SSHKeyGetResponse defines the schema of the response
// when retrieving a single SSH key.
type SSHKeyGetResponse struct {
SSHKey SSHKey `json:"ssh_key"`
}
// SSHKeyUpdateRequest defines the schema of the request to update a SSH key.
type SSHKeyUpdateRequest struct {
Name string `json:"name,omitempty"`
Labels *map[string]string `json:"labels,omitempty"`
}
// SSHKeyUpdateResponse defines the schema of the response when updating a SSH key.
type SSHKeyUpdateResponse struct {
SSHKey SSHKey `json:"ssh_key"`
}

View File

@ -0,0 +1,105 @@
package schema
import "time"
// Volume defines the schema of a volume.
type Volume struct {
ID int `json:"id"`
Name string `json:"name"`
Server *int `json:"server"`
Location Location `json:"location"`
Size int `json:"size"`
Protection VolumeProtection `json:"protection"`
Labels map[string]string `json:"labels"`
LinuxDevice string `json:"linux_device"`
Created time.Time `json:"created"`
}
// 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"`
}
// VolumeCreateResponse defines the schema of the response
// when creating a volume.
type VolumeCreateResponse struct {
Volume Volume `json:"volume"`
Action *Action `json:"action"`
}
// VolumeListResponse defines the schema of the response
// when listing volumes.
type VolumeListResponse struct {
Volumes []Volume `json:"volumes"`
}
// VolumeGetResponse defines the schema of the response
// when retrieving a single volume.
type VolumeGetResponse struct {
Volume Volume `json:"volume"`
}
// VolumeUpdateRequest defines the schema of the request to update a volume.
type VolumeUpdateRequest struct {
Name string `json:"name,omitempty"`
Labels *map[string]string `json:"labels,omitempty"`
}
// VolumeUpdateResponse defines the schema of the response when updating a volume.
type VolumeUpdateResponse struct {
Volume Volume `json:"volume"`
}
// VolumeProtection defines the schema of a volume's resource protection.
type VolumeProtection struct {
Delete bool `json:"delete"`
}
// VolumeActionChangeProtectionRequest defines the schema of the request to
// change the resource protection of a volume.
type VolumeActionChangeProtectionRequest struct {
Delete *bool `json:"delete,omitempty"`
}
// VolumeActionChangeProtectionResponse defines the schema of the response when
// changing the resource protection of a volume.
type VolumeActionChangeProtectionResponse struct {
Action Action `json:"action"`
}
// VolumeActionAttachVolumeRequest defines the schema of the request to
// attach a volume to a server.
type VolumeActionAttachVolumeRequest struct {
Server int `json:"server"`
}
// VolumeActionAttachVolumeResponse defines the schema of the response when
// attaching a volume to a server.
type VolumeActionAttachVolumeResponse struct {
Action Action `json:"action"`
}
// VolumeActionDetachVolumeRequest defines the schema of the request to
// create an detach volume action.
type VolumeActionDetachVolumeRequest struct{}
// VolumeActionDetachVolumeResponse defines the schema of the response when
// creating an detach volume action.
type VolumeActionDetachVolumeResponse struct {
Action Action `json:"action"`
}
// VolumeActionResizeVolumeRequest defines the schema of the request to resize a volume.
type VolumeActionResizeVolumeRequest struct {
Size int `json:"size"`
}
// VolumeActionResizeVolumeResponse defines the schema of the response when resizing a volume.
type VolumeActionResizeVolumeResponse struct {
Action Action `json:"action"`
}

View File

@ -0,0 +1,778 @@
package hcloud
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"strconv"
"time"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// Server represents a server in the Hetzner Cloud.
type Server struct {
ID int
Name string
Status ServerStatus
Created time.Time
PublicNet ServerPublicNet
ServerType *ServerType
Datacenter *Datacenter
IncludedTraffic uint64
OutgoingTraffic uint64
IngoingTraffic uint64
BackupWindow string
RescueEnabled bool
Locked bool
ISO *ISO
Image *Image
Protection ServerProtection
Labels map[string]string
Volumes []*Volume
}
// ServerProtection represents the protection level of a server.
type ServerProtection struct {
Delete, Rebuild bool
}
// ServerStatus specifies a server's status.
type ServerStatus string
const (
// ServerStatusInitializing is the status when a server is initializing.
ServerStatusInitializing ServerStatus = "initializing"
// ServerStatusOff is the status when a server is off.
ServerStatusOff ServerStatus = "off"
// ServerStatusRunning is the status when a server is running.
ServerStatusRunning ServerStatus = "running"
)
// ServerPublicNet represents a server's public network.
type ServerPublicNet struct {
IPv4 ServerPublicNetIPv4
IPv6 ServerPublicNetIPv6
FloatingIPs []*FloatingIP
}
// ServerPublicNetIPv4 represents a server's public IPv4 address.
type ServerPublicNetIPv4 struct {
IP net.IP
Blocked bool
DNSPtr string
}
// ServerPublicNetIPv6 represents a server's public IPv6 network and address.
type ServerPublicNetIPv6 struct {
IP net.IP
Network *net.IPNet
Blocked bool
DNSPtr map[string]string
}
// DNSPtrForIP returns the reverse dns pointer of the ip address.
func (s *ServerPublicNetIPv6) DNSPtrForIP(ip net.IP) string {
return s.DNSPtr[ip.String()]
}
// ServerRescueType represents rescue types.
type ServerRescueType string
// List of rescue types.
const (
ServerRescueTypeLinux32 ServerRescueType = "linux32"
ServerRescueTypeLinux64 ServerRescueType = "linux64"
ServerRescueTypeFreeBSD64 ServerRescueType = "freebsd64"
)
// ServerClient is a client for the servers API.
type ServerClient struct {
client *Client
}
// GetByID retrieves a server by its ID.
func (c *ServerClient) GetByID(ctx context.Context, id int) (*Server, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/servers/%d", id), nil)
if err != nil {
return nil, nil, err
}
var body schema.ServerGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, nil, err
}
return ServerFromSchema(body.Server), resp, nil
}
// GetByName retreives a server by its name.
func (c *ServerClient) GetByName(ctx context.Context, name string) (*Server, *Response, error) {
path := "/servers?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.ServerListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
if len(body.Servers) == 0 {
return nil, resp, nil
}
return ServerFromSchema(body.Servers[0]), resp, nil
}
// Get retrieves a server by its ID if the input can be parsed as an integer, otherwise it retrieves a server by its name.
func (c *ServerClient) Get(ctx context.Context, idOrName string) (*Server, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))
}
return c.GetByName(ctx, idOrName)
}
// ServerListOpts specifies options for listing servers.
type ServerListOpts struct {
ListOpts
}
// List returns a list of servers for a specific page.
func (c *ServerClient) List(ctx context.Context, opts ServerListOpts) ([]*Server, *Response, error) {
path := "/servers?" + valuesForListOpts(opts.ListOpts).Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.ServerListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
servers := make([]*Server, 0, len(body.Servers))
for _, s := range body.Servers {
servers = append(servers, ServerFromSchema(s))
}
return servers, resp, nil
}
// All returns all servers.
func (c *ServerClient) All(ctx context.Context) ([]*Server, error) {
return c.AllWithOpts(ctx, ServerListOpts{ListOpts{PerPage: 50}})
}
// AllWithOpts returns all servers for the given options.
func (c *ServerClient) AllWithOpts(ctx context.Context, opts ServerListOpts) ([]*Server, error) {
allServers := []*Server{}
_, err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
servers, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allServers = append(allServers, servers...)
return resp, nil
})
if err != nil {
return nil, err
}
return allServers, nil
}
// ServerCreateOpts specifies options for creating a new server.
type ServerCreateOpts struct {
Name string
ServerType *ServerType
Image *Image
SSHKeys []*SSHKey
Location *Location
Datacenter *Datacenter
UserData string
StartAfterCreate *bool
Labels map[string]string
}
// Validate checks if options are valid.
func (o ServerCreateOpts) Validate() error {
if o.Name == "" {
return errors.New("missing name")
}
if o.ServerType == nil || (o.ServerType.ID == 0 && o.ServerType.Name == "") {
return errors.New("missing server type")
}
if o.Image == nil || (o.Image.ID == 0 && o.Image.Name == "") {
return errors.New("missing image")
}
if o.Location != nil && o.Datacenter != nil {
return errors.New("location and datacenter are mutually exclusive")
}
return nil
}
// ServerCreateResult is the result of a create server call.
type ServerCreateResult struct {
Server *Server
Action *Action
RootPassword string
}
// Create creates a new server.
func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (ServerCreateResult, *Response, error) {
if err := opts.Validate(); err != nil {
return ServerCreateResult{}, nil, err
}
var reqBody schema.ServerCreateRequest
reqBody.UserData = opts.UserData
reqBody.Name = opts.Name
reqBody.StartAfterCreate = opts.StartAfterCreate
if opts.ServerType.ID != 0 {
reqBody.ServerType = opts.ServerType.ID
} else if opts.ServerType.Name != "" {
reqBody.ServerType = opts.ServerType.Name
}
if opts.Image.ID != 0 {
reqBody.Image = opts.Image.ID
} else if opts.Image.Name != "" {
reqBody.Image = opts.Image.Name
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
for _, sshKey := range opts.SSHKeys {
reqBody.SSHKeys = append(reqBody.SSHKeys, sshKey.ID)
}
if opts.Location != nil {
if opts.Location.ID != 0 {
reqBody.Location = strconv.Itoa(opts.Location.ID)
} else {
reqBody.Location = opts.Location.Name
}
}
if opts.Datacenter != nil {
if opts.Datacenter.ID != 0 {
reqBody.Datacenter = strconv.Itoa(opts.Datacenter.ID)
} else {
reqBody.Datacenter = opts.Datacenter.Name
}
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return ServerCreateResult{}, nil, err
}
req, err := c.client.NewRequest(ctx, "POST", "/servers", bytes.NewReader(reqBodyData))
if err != nil {
return ServerCreateResult{}, nil, err
}
var respBody schema.ServerCreateResponse
resp, err := c.client.Do(req, &respBody)
if err != nil {
return ServerCreateResult{}, resp, err
}
result := ServerCreateResult{
Server: ServerFromSchema(respBody.Server),
Action: ActionFromSchema(respBody.Action),
}
if respBody.RootPassword != nil {
result.RootPassword = *respBody.RootPassword
}
return result, resp, nil
}
// Delete deletes a server.
func (c *ServerClient) Delete(ctx context.Context, server *Server) (*Response, error) {
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/servers/%d", server.ID), nil)
if err != nil {
return nil, err
}
return c.client.Do(req, nil)
}
// ServerUpdateOpts specifies options for updating a server.
type ServerUpdateOpts struct {
Name string
Labels map[string]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,
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
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)
req, err := c.client.NewRequest(ctx, "POST", path, nil)
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionPoweronResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// Reboot reboots a server.
func (c *ServerClient) Reboot(ctx context.Context, server *Server) (*Action, *Response, error) {
path := fmt.Sprintf("/servers/%d/actions/reboot", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, nil)
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionRebootResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// Reset resets a server.
func (c *ServerClient) Reset(ctx context.Context, server *Server) (*Action, *Response, error) {
path := fmt.Sprintf("/servers/%d/actions/reset", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, nil)
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionResetResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// Shutdown shuts down a server.
func (c *ServerClient) Shutdown(ctx context.Context, server *Server) (*Action, *Response, error) {
path := fmt.Sprintf("/servers/%d/actions/shutdown", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, nil)
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionShutdownResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// Poweroff stops a server.
func (c *ServerClient) Poweroff(ctx context.Context, server *Server) (*Action, *Response, error) {
path := fmt.Sprintf("/servers/%d/actions/poweroff", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, nil)
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionPoweroffResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// ServerResetPasswordResult is the result of resetting a server's password.
type ServerResetPasswordResult struct {
Action *Action
RootPassword string
}
// ResetPassword resets a server's password.
func (c *ServerClient) ResetPassword(ctx context.Context, server *Server) (ServerResetPasswordResult, *Response, error) {
path := fmt.Sprintf("/servers/%d/actions/reset_password", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, nil)
if err != nil {
return ServerResetPasswordResult{}, nil, err
}
respBody := schema.ServerActionResetPasswordResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return ServerResetPasswordResult{}, resp, err
}
return ServerResetPasswordResult{
Action: ActionFromSchema(respBody.Action),
RootPassword: respBody.RootPassword,
}, resp, nil
}
// ServerCreateImageOpts specifies options for creating an image from a server.
type ServerCreateImageOpts struct {
Type ImageType
Description *string
Labels map[string]string
}
// Validate checks if options are valid.
func (o ServerCreateImageOpts) Validate() error {
switch o.Type {
case ImageTypeSnapshot, ImageTypeBackup:
break
case "":
break
default:
return errors.New("invalid type")
}
return nil
}
// ServerCreateImageResult is the result of creating an image from a server.
type ServerCreateImageResult struct {
Action *Action
Image *Image
}
// CreateImage creates an image from a server.
func (c *ServerClient) CreateImage(ctx context.Context, server *Server, opts *ServerCreateImageOpts) (ServerCreateImageResult, *Response, error) {
var reqBody schema.ServerActionCreateImageRequest
if opts != nil {
if err := opts.Validate(); err != nil {
return ServerCreateImageResult{}, nil, fmt.Errorf("invalid options: %s", err)
}
if opts.Description != nil {
reqBody.Description = opts.Description
}
if opts.Type != "" {
reqBody.Type = String(string(opts.Type))
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return ServerCreateImageResult{}, nil, err
}
path := fmt.Sprintf("/servers/%d/actions/create_image", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return ServerCreateImageResult{}, nil, err
}
respBody := schema.ServerActionCreateImageResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return ServerCreateImageResult{}, resp, err
}
return ServerCreateImageResult{
Action: ActionFromSchema(respBody.Action),
Image: ImageFromSchema(respBody.Image),
}, resp, nil
}
// ServerEnableRescueOpts specifies options for enabling rescue mode for a server.
type ServerEnableRescueOpts struct {
Type ServerRescueType
SSHKeys []*SSHKey
}
// ServerEnableRescueResult is the result of enabling rescue mode for a server.
type ServerEnableRescueResult struct {
Action *Action
RootPassword string
}
// EnableRescue enables rescue mode for a server.
func (c *ServerClient) EnableRescue(ctx context.Context, server *Server, opts ServerEnableRescueOpts) (ServerEnableRescueResult, *Response, error) {
reqBody := schema.ServerActionEnableRescueRequest{
Type: String(string(opts.Type)),
}
for _, sshKey := range opts.SSHKeys {
reqBody.SSHKeys = append(reqBody.SSHKeys, sshKey.ID)
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return ServerEnableRescueResult{}, nil, err
}
path := fmt.Sprintf("/servers/%d/actions/enable_rescue", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return ServerEnableRescueResult{}, nil, err
}
respBody := schema.ServerActionEnableRescueResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return ServerEnableRescueResult{}, resp, err
}
result := ServerEnableRescueResult{
Action: ActionFromSchema(respBody.Action),
RootPassword: respBody.RootPassword,
}
return result, resp, nil
}
// DisableRescue disables rescue mode for a server.
func (c *ServerClient) DisableRescue(ctx context.Context, server *Server) (*Action, *Response, error) {
path := fmt.Sprintf("/servers/%d/actions/disable_rescue", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, nil)
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionDisableRescueResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// ServerRebuildOpts specifies options for rebuilding a server.
type ServerRebuildOpts struct {
Image *Image
}
// Rebuild rebuilds a server.
func (c *ServerClient) Rebuild(ctx context.Context, server *Server, opts ServerRebuildOpts) (*Action, *Response, error) {
reqBody := schema.ServerActionRebuildRequest{}
if opts.Image.ID != 0 {
reqBody.Image = opts.Image.ID
} else {
reqBody.Image = opts.Image.Name
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, 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
}
respBody := schema.ServerActionRebuildResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// AttachISO attaches an ISO to a server.
func (c *ServerClient) AttachISO(ctx context.Context, server *Server, iso *ISO) (*Action, *Response, error) {
reqBody := schema.ServerActionAttachISORequest{}
if iso.ID != 0 {
reqBody.ISO = iso.ID
} else {
reqBody.ISO = iso.Name
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/servers/%d/actions/attach_iso", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionAttachISOResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// DetachISO detaches the currently attached ISO from a server.
func (c *ServerClient) DetachISO(ctx context.Context, server *Server) (*Action, *Response, error) {
path := fmt.Sprintf("/servers/%d/actions/detach_iso", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, nil)
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionDetachISOResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// EnableBackup enables backup for a server. Pass in an empty backup window to let the
// API pick a window for you. See the API documentation at docs.hetzner.cloud for a list
// of valid backup windows.
func (c *ServerClient) EnableBackup(ctx context.Context, server *Server, window string) (*Action, *Response, error) {
reqBody := schema.ServerActionEnableBackupRequest{}
if window != "" {
reqBody.BackupWindow = String(window)
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/servers/%d/actions/enable_backup", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionEnableBackupResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// DisableBackup disables backup for a server.
func (c *ServerClient) DisableBackup(ctx context.Context, server *Server) (*Action, *Response, error) {
path := fmt.Sprintf("/servers/%d/actions/disable_backup", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, nil)
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionDisableBackupResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// ServerChangeTypeOpts specifies options for changing a server's type.
type ServerChangeTypeOpts struct {
ServerType *ServerType // new server type
UpgradeDisk bool // whether disk should be upgraded
}
// ChangeType changes a server's type.
func (c *ServerClient) ChangeType(ctx context.Context, server *Server, opts ServerChangeTypeOpts) (*Action, *Response, error) {
reqBody := schema.ServerActionChangeTypeRequest{
UpgradeDisk: opts.UpgradeDisk,
}
if opts.ServerType.ID != 0 {
reqBody.ServerType = opts.ServerType.ID
} else {
reqBody.ServerType = opts.ServerType.Name
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/servers/%d/actions/change_type", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionChangeTypeResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// ChangeDNSPtr changes or resets the reverse DNS pointer for a server IP address.
// Pass a nil ptr to reset the reverse DNS pointer to its default value.
func (c *ServerClient) ChangeDNSPtr(ctx context.Context, server *Server, ip string, ptr *string) (*Action, *Response, error) {
reqBody := schema.ServerActionChangeDNSPtrRequest{
IP: ip,
DNSPtr: ptr,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/servers/%d/actions/change_dns_ptr", server.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionChangeDNSPtrResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// ServerChangeProtectionOpts specifies options for changing the resource protection level of a server.
type ServerChangeProtectionOpts struct {
Rebuild *bool
Delete *bool
}
// ChangeProtection changes the resource protection level of a server.
func (c *ServerClient) ChangeProtection(ctx context.Context, image *Server, opts ServerChangeProtectionOpts) (*Action, *Response, error) {
reqBody := schema.ServerActionChangeProtectionRequest{
Rebuild: opts.Rebuild,
Delete: opts.Delete,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/servers/%d/actions/change_protection", image.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.ServerActionChangeProtectionResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, err
}

View File

@ -0,0 +1,144 @@
package hcloud
import (
"context"
"fmt"
"net/url"
"strconv"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// ServerType represents a server type in the Hetzner Cloud.
type ServerType struct {
ID int
Name string
Description string
Cores int
Memory float32
Disk int
StorageType StorageType
CPUType CPUType
Pricings []ServerTypeLocationPricing
}
// StorageType specifies the type of storage.
type StorageType string
const (
// StorageTypeLocal is the type for local storage.
StorageTypeLocal StorageType = "local"
// StorageTypeCeph is the type for remote storage.
StorageTypeCeph StorageType = "ceph"
)
// CPUType specifies the type of the CPU.
type CPUType string
const (
// CPUTypeShared is the type for shared CPU.
CPUTypeShared CPUType = "shared"
//CPUTypeDedicated is the type for dedicated CPU.
CPUTypeDedicated CPUType = "dedicated"
)
// ServerTypeClient is a client for the server types API.
type ServerTypeClient struct {
client *Client
}
// GetByID retrieves a server type by its ID.
func (c *ServerTypeClient) GetByID(ctx context.Context, id int) (*ServerType, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/server_types/%d", id), nil)
if err != nil {
return nil, nil, err
}
var body schema.ServerTypeGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, nil, err
}
return ServerTypeFromSchema(body.ServerType), resp, nil
}
// GetByName retrieves a server type by its name.
func (c *ServerTypeClient) GetByName(ctx context.Context, name string) (*ServerType, *Response, error) {
path := "/server_types?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.ServerTypeListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
if len(body.ServerTypes) == 0 {
return nil, resp, nil
}
return ServerTypeFromSchema(body.ServerTypes[0]), resp, nil
}
// Get retrieves a server type by its ID if the input can be parsed as an integer, otherwise it retrieves a server type by its name.
func (c *ServerTypeClient) Get(ctx context.Context, idOrName string) (*ServerType, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))
}
return c.GetByName(ctx, idOrName)
}
// ServerTypeListOpts specifies options for listing server types.
type ServerTypeListOpts struct {
ListOpts
}
// List returns a list of server types for a specific page.
func (c *ServerTypeClient) List(ctx context.Context, opts ServerTypeListOpts) ([]*ServerType, *Response, error) {
path := "/server_types?" + valuesForListOpts(opts.ListOpts).Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.ServerTypeListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
serverTypes := make([]*ServerType, 0, len(body.ServerTypes))
for _, s := range body.ServerTypes {
serverTypes = append(serverTypes, ServerTypeFromSchema(s))
}
return serverTypes, resp, nil
}
// All returns all server types.
func (c *ServerTypeClient) All(ctx context.Context) ([]*ServerType, error) {
allServerTypes := []*ServerType{}
opts := ServerTypeListOpts{}
opts.PerPage = 50
_, err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
serverTypes, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allServerTypes = append(allServerTypes, serverTypes...)
return resp, nil
})
if err != nil {
return nil, err
}
return allServerTypes, nil
}

View File

@ -0,0 +1,233 @@
package hcloud
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// SSHKey represents a SSH key in the Hetzner Cloud.
type SSHKey struct {
ID int
Name string
Fingerprint string
PublicKey string
Labels map[string]string
}
// SSHKeyClient is a client for the SSH keys API.
type SSHKeyClient struct {
client *Client
}
// GetByID retrieves a SSH key by its ID.
func (c *SSHKeyClient) GetByID(ctx context.Context, id int) (*SSHKey, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/ssh_keys/%d", id), nil)
if err != nil {
return nil, nil, err
}
var body schema.SSHKeyGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, nil, err
}
return SSHKeyFromSchema(body.SSHKey), resp, nil
}
// GetByName retrieves a SSH key by its name.
func (c *SSHKeyClient) GetByName(ctx context.Context, name string) (*SSHKey, *Response, error) {
path := "/ssh_keys?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.SSHKeyListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
if len(body.SSHKeys) == 0 {
return nil, resp, nil
}
return SSHKeyFromSchema(body.SSHKeys[0]), resp, nil
}
// GetByFingerprint retreives a SSH key by its fingerprint.
func (c *SSHKeyClient) GetByFingerprint(ctx context.Context, fingerprint string) (*SSHKey, *Response, error) {
path := "/ssh_keys?fingerprint=" + url.QueryEscape(fingerprint)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.SSHKeyListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
if len(body.SSHKeys) == 0 {
return nil, resp, nil
}
return SSHKeyFromSchema(body.SSHKeys[0]), resp, nil
}
// Get retrieves a SSH key by its ID if the input can be parsed as an integer, otherwise it retrieves a SSH key by its name.
func (c *SSHKeyClient) Get(ctx context.Context, idOrName string) (*SSHKey, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))
}
return c.GetByName(ctx, idOrName)
}
// SSHKeyListOpts specifies options for listing SSH keys.
type SSHKeyListOpts struct {
ListOpts
}
// List returns a list of SSH keys for a specific page.
func (c *SSHKeyClient) List(ctx context.Context, opts SSHKeyListOpts) ([]*SSHKey, *Response, error) {
path := "/ssh_keys?" + valuesForListOpts(opts.ListOpts).Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.SSHKeyListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
sshKeys := make([]*SSHKey, 0, len(body.SSHKeys))
for _, s := range body.SSHKeys {
sshKeys = append(sshKeys, SSHKeyFromSchema(s))
}
return sshKeys, resp, nil
}
// All returns all SSH keys.
func (c *SSHKeyClient) All(ctx context.Context) ([]*SSHKey, error) {
return c.AllWithOpts(ctx, SSHKeyListOpts{ListOpts{PerPage: 50}})
}
// AllWithOpts returns all SSH keys with the given options.
func (c *SSHKeyClient) AllWithOpts(ctx context.Context, opts SSHKeyListOpts) ([]*SSHKey, error) {
allSSHKeys := []*SSHKey{}
_, err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
sshKeys, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allSSHKeys = append(allSSHKeys, sshKeys...)
return resp, nil
})
if err != nil {
return nil, err
}
return allSSHKeys, nil
}
// SSHKeyCreateOpts specifies parameters for creating a SSH key.
type SSHKeyCreateOpts struct {
Name string
PublicKey string
Labels map[string]string
}
// Validate checks if options are valid.
func (o SSHKeyCreateOpts) Validate() error {
if o.Name == "" {
return errors.New("missing name")
}
if o.PublicKey == "" {
return errors.New("missing public key")
}
return nil
}
// Create creates a new SSH key with the given options.
func (c *SSHKeyClient) Create(ctx context.Context, opts SSHKeyCreateOpts) (*SSHKey, *Response, error) {
if err := opts.Validate(); err != nil {
return nil, nil, err
}
reqBody := schema.SSHKeyCreateRequest{
Name: opts.Name,
PublicKey: opts.PublicKey,
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
req, err := c.client.NewRequest(ctx, "POST", "/ssh_keys", bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
var respBody schema.SSHKeyCreateResponse
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return SSHKeyFromSchema(respBody.SSHKey), resp, nil
}
// Delete deletes a SSH key.
func (c *SSHKeyClient) Delete(ctx context.Context, sshKey *SSHKey) (*Response, error) {
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/ssh_keys/%d", sshKey.ID), nil)
if err != nil {
return nil, err
}
return c.client.Do(req, nil)
}
// SSHKeyUpdateOpts specifies options for updating a SSH key.
type SSHKeyUpdateOpts struct {
Name string
Labels map[string]string
}
// Update updates a SSH key.
func (c *SSHKeyClient) Update(ctx context.Context, sshKey *SSHKey, opts SSHKeyUpdateOpts) (*SSHKey, *Response, error) {
reqBody := schema.SSHKeyUpdateRequest{
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("/ssh_keys/%d", sshKey.ID)
req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.SSHKeyUpdateResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return SSHKeyFromSchema(respBody.SSHKey), resp, nil
}

View File

@ -0,0 +1,356 @@
package hcloud
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
"time"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)
// Volume represents a volume in the Hetzner Cloud.
type Volume struct {
ID int
Name string
Server *Server
Location *Location
Size int
Protection VolumeProtection
Labels map[string]string
LinuxDevice string
Created time.Time
}
// VolumeProtection represents the protection level of a volume.
type VolumeProtection struct {
Delete bool
}
// VolumeClient is a client for the volume API.
type VolumeClient struct {
client *Client
}
// GetByID retrieves a volume by its ID.
func (c *VolumeClient) GetByID(ctx context.Context, id int) (*Volume, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/volumes/%d", id), nil)
if err != nil {
return nil, nil, err
}
var body schema.VolumeGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, nil, err
}
return VolumeFromSchema(body.Volume), resp, nil
}
// GetByName retrieves a volume by its name.
func (c *VolumeClient) GetByName(ctx context.Context, name string) (*Volume, *Response, error) {
path := "/volumes?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.VolumeListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
if len(body.Volumes) == 0 {
return nil, resp, nil
}
return VolumeFromSchema(body.Volumes[0]), resp, nil
}
// Get retrieves a volume by its ID if the input can be parsed as an integer,
// otherwise it retrieves a volume by its name.
func (c *VolumeClient) Get(ctx context.Context, idOrName string) (*Volume, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))
}
return c.GetByName(ctx, idOrName)
}
// VolumeListOpts specifies options for listing volumes.
type VolumeListOpts struct {
ListOpts
}
// List returns a list of volumes for a specific page.
func (c *VolumeClient) List(ctx context.Context, opts VolumeListOpts) ([]*Volume, *Response, error) {
path := "/volumes?" + valuesForListOpts(opts.ListOpts).Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.VolumeListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
volumes := make([]*Volume, 0, len(body.Volumes))
for _, s := range body.Volumes {
volumes = append(volumes, VolumeFromSchema(s))
}
return volumes, resp, nil
}
// All returns all volumes.
func (c *VolumeClient) All(ctx context.Context) ([]*Volume, error) {
return c.AllWithOpts(ctx, VolumeListOpts{ListOpts{PerPage: 50}})
}
// AllWithOpts returns all volumes with the given options.
func (c *VolumeClient) AllWithOpts(ctx context.Context, opts VolumeListOpts) ([]*Volume, error) {
allVolumes := []*Volume{}
_, err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
volumes, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allVolumes = append(allVolumes, volumes...)
return resp, nil
})
if err != nil {
return nil, err
}
return allVolumes, nil
}
// VolumeCreateOpts specifies parameters for creating a volume.
type VolumeCreateOpts struct {
Name string
Size int
Server *Server
Location *Location
Labels map[string]string
}
// Validate checks if options are valid.
func (o VolumeCreateOpts) Validate() error {
if o.Name == "" {
return errors.New("missing name")
}
if o.Size <= 0 {
return errors.New("size must be greater than 0")
}
if o.Server == nil && o.Location == nil {
return errors.New("one of server or location must be provided")
}
if o.Server != nil && o.Location != nil {
return errors.New("only one of server or location must be provided")
}
return nil
}
// VolumeCreateResult is the result of creating a volume.
type VolumeCreateResult struct {
Volume *Volume
Action *Action
}
// Create creates a new volume with the given options.
func (c *VolumeClient) Create(ctx context.Context, opts VolumeCreateOpts) (VolumeCreateResult, *Response, error) {
if err := opts.Validate(); err != nil {
return VolumeCreateResult{}, nil, err
}
reqBody := schema.VolumeCreateRequest{
Name: opts.Name,
Size: opts.Size,
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
if opts.Server != nil {
reqBody.Server = Int(opts.Server.ID)
}
if opts.Location != nil {
if opts.Location.ID != 0 {
reqBody.Location = opts.Location.ID
} else {
reqBody.Location = opts.Location.Name
}
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return VolumeCreateResult{}, nil, err
}
req, err := c.client.NewRequest(ctx, "POST", "/volumes", bytes.NewReader(reqBodyData))
if err != nil {
return VolumeCreateResult{}, nil, err
}
var respBody schema.VolumeCreateResponse
resp, err := c.client.Do(req, &respBody)
if err != nil {
return VolumeCreateResult{}, resp, err
}
var action *Action
if respBody.Action != nil {
action = ActionFromSchema(*respBody.Action)
}
return VolumeCreateResult{
Volume: VolumeFromSchema(respBody.Volume),
Action: action,
}, resp, nil
}
// Delete deletes a volume.
func (c *VolumeClient) Delete(ctx context.Context, volume *Volume) (*Response, error) {
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/volumes/%d", volume.ID), nil)
if err != nil {
return nil, err
}
return c.client.Do(req, nil)
}
// VolumeUpdateOpts specifies options for updating a volume.
type VolumeUpdateOpts struct {
Name string
Labels map[string]string
}
// Update updates a volume.
func (c *VolumeClient) Update(ctx context.Context, volume *Volume, opts VolumeUpdateOpts) (*Volume, *Response, error) {
reqBody := schema.VolumeUpdateRequest{
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("/volumes/%d", volume.ID)
req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.VolumeUpdateResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
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) {
reqBody := schema.VolumeActionAttachVolumeRequest{
Server: server.ID,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/volumes/%d/actions/attach", volume.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
var respBody schema.VolumeActionAttachVolumeResponse
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// Detach detaches a volume from a server.
func (c *VolumeClient) Detach(ctx context.Context, volume *Volume) (*Action, *Response, error) {
var reqBody schema.VolumeActionDetachVolumeRequest
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/volumes/%d/actions/detach", volume.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
var respBody schema.VolumeActionDetachVolumeResponse
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, nil
}
// VolumeChangeProtectionOpts specifies options for changing the resource protection level of a volume.
type VolumeChangeProtectionOpts struct {
Delete *bool
}
// ChangeProtection changes the resource protection level of a volume.
func (c *VolumeClient) ChangeProtection(ctx context.Context, volume *Volume, opts VolumeChangeProtectionOpts) (*Action, *Response, error) {
reqBody := schema.VolumeActionChangeProtectionRequest{
Delete: opts.Delete,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/volumes/%d/actions/change_protection", volume.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.VolumeActionChangeProtectionResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, err
}
// Resize changes the size of a volume.
func (c *VolumeClient) Resize(ctx context.Context, volume *Volume, size int) (*Action, *Response, error) {
reqBody := schema.VolumeActionResizeVolumeRequest{
Size: size,
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/volumes/%d/actions/resize", volume.ID)
req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.VolumeActionResizeVolumeResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return ActionFromSchema(respBody.Action), resp, err
}

6
vendor/vendor.json vendored
View File

@ -1171,6 +1171,12 @@
"path": "github.com/hashicorp/yamux",
"revision": "df949784da9ed028ee76df44652e42d37a09d7e4"
},
{
"checksumSHA1": "lMvR+qhQWll2pXPY3GmJbKqgkUc=",
"path": "github.com/hetznercloud/hcloud-go/hcloud",
"revision": "eaf050e4f37028d2ca5f99a2fb38ead2c9b293d3",
"revisionTime": "2018-10-16T09:48:19Z"
},
{
"checksumSHA1": "3/Bhy+ua/DCv2ElMD5GzOYSGN6g=",
"comment": "0.2.2-2-gc01cf91",

View File

@ -0,0 +1,82 @@
---
description: |
The Hetzner Cloud Packer builder is able to create new images for use with the
Hetzner Cloud. The builder takes a source image, runs any provisioning
necessary on the image after launching it, then snapshots it into a reusable
image. This reusable image can then be used as the foundation of new servers
that are launched within the Hetzner Cloud.
layout: docs
page_title: 'Hetzner Cloud - Builders'
sidebar_current: 'docs-builders-hetzner-cloud'
---
# Hetzner Cloud Builder
Type: `hcloud`
The `hcloud` Packer builder is able to create new images for use with
[Hetzner Cloud](https://www.hetzner.cloud). The builder takes a source image,
runs any provisioning necessary on the image after launching it, then snapshots
it into a reusable image. This reusable image can then be used as the foundation
of new servers that are launched within the Hetzner Cloud.
The builder does *not* manage images. Once it creates an image, it is up to you
to use it or delete it.
## Configuration Reference
There are many configuration options available for the builder. They are
segmented below into two categories: required and optional parameters. Within
each category, the available configuration keys are alphabetized.
In addition to the options listed here, a
[communicator](/docs/templates/communicator.html) can be configured for this
builder.
### Required:
- `token` (string) - The client TOKEN to use to access your account. It
can also be specified via environment variable `HCLOUD_TOKEN`,
if set.
- `image` (string) - ID or name of image to launch server from.
- `location` (string) - The name of the location to launch the server in.
- `server_type` (string) - ID or name of the server type this server should be created with.
### Optional:
- `endpoint` (string) - Non standard api endpoint URL. Set this if you are
using a Hetzner Cloud API compatible service. It can also be specified via
environment variable `HCLOUD_ENDPOINT`.
- `server_name` (string) - The name assigned to the server. The Hetzner Cloud
sets the hostname of the machine to this value.
- `snapshot_name` (string) - The name of the resulting snapshot that will
appear in your account. Defaults to "packer-{{timestamp}}" (see
[configuration templates](/docs/templates/engine.html) for more info).
- `poll_interval` (string) - Configures the interval in which actions are polled by the client. Default `500ms`. Increase this interval if you run into rate limiting errors.
- `user_data` (string) - User data to launch with the server.
- `user_data_file` (string) - Path to a file that will be used for the user
data when launching the server.
## Basic Example
Here is a basic example. It is completely valid as soon as you enter your own
access tokens:
``` json
{
"type": "hcloud",
"token": "YOUR API KEY",
"image": "ubuntu-18.04",
"location": "nbg1",
"server_type": "cx11",
"ssh_username": "root"
}
```