Add hcloud Builder
This commit is contained in:
parent
4e14710a66
commit
270110767c
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
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),
|
||||
},
|
||||
new(stepCreateServer),
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
Host: getServerIP,
|
||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
||||
},
|
||||
new(common.StepProvision),
|
||||
&common.StepCleanupTempKeys{
|
||||
Comm: &b.config.Comm,
|
||||
},
|
||||
new(stepShutdownServer),
|
||||
new(stepCreateSnapshot),
|
||||
}
|
||||
ui.Say("Steps OK")
|
||||
// Run the steps
|
||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(state)
|
||||
ui.Say("Run OK")
|
||||
// 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 {
|
||||
log.Println("Failed to find snapshot_name in state. Bug?")
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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": ""
|
||||
}]
|
||||
}
|
||||
`
|
|
@ -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
|
||||
}
|
|
@ -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("1Error 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))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
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
|
||||
priv_der := x509.MarshalPKCS1PrivateKey(priv)
|
||||
priv_blk := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: priv_der,
|
||||
}
|
||||
|
||||
// Set the private key in the config for later
|
||||
c.Comm.SSHPrivateKey = pem.EncodeToMemory(&priv_blk)
|
||||
|
||||
// Marshal the public key into SSH compatible format
|
||||
// TODO properly handle the public key error
|
||||
pub, _ := ssh.NewPublicKey(&priv.PublicKey)
|
||||
pub_sshformat := string(ssh.MarshalAuthorizedKey(pub))
|
||||
|
||||
// The name of the public key on DO
|
||||
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||
|
||||
// Create the key!
|
||||
key, _, err := client.SSHKey.Create(context.TODO(), hcloud.SSHKeyCreateOpts{
|
||||
Name: name,
|
||||
PublicKey: pub_sshformat,
|
||||
})
|
||||
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(&priv_blk)); 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
|
||||
}
|
||||
}
|
||||
}
|
||||
ui.Say("SSH Key OK")
|
||||
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))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
|
@ -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 }
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"`
|
||||
}
|
23
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/datacenter.go
generated
vendored
Normal file
23
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/datacenter.go
generated
vendored
Normal 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"`
|
||||
}
|
|
@ -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"`
|
||||
}
|
112
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/floating_ip.go
generated
vendored
Normal file
112
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/floating_ip.go
generated
vendored
Normal 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"`
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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"`
|
||||
}
|
22
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/location.go
generated
vendored
Normal file
22
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/location.go
generated
vendored
Normal 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"`
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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"`
|
||||
}
|
26
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/server_type.go
generated
vendored
Normal file
26
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/server_type.go
generated
vendored
Normal 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"`
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1114,6 +1114,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",
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
description: |
|
||||
The Hetzner Cloud Packer builder is able to create new images for use with
|
||||
DigitalOcean. 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 location to create server in.
|
||||
|
||||
- `location` (string) - The name of the location to launch the
|
||||
droplet 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 DigitalOcean API compatible service. It can also be specified via
|
||||
environment variable `HCLOUD_ENDPOINT`.
|
||||
|
||||
- `server_name` (string) - The name assigned to the droplet. DigitalOcean
|
||||
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 Droplet.
|
||||
|
||||
- `user_data_file` (string) - Path to a file that will be used for the user
|
||||
data when launching the Droplet.
|
||||
|
||||
- `tags` (list) - Tags to apply to the droplet when it is created
|
||||
|
||||
## 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"
|
||||
}
|
||||
```
|
|
@ -80,7 +80,7 @@ description: |-
|
|||
<p>
|
||||
Out of the box Packer comes with support to build images for
|
||||
Amazon EC2, CloudStack, DigitalOcean, Docker, Google Compute
|
||||
Engine, Microsoft Azure, QEMU, VirtualBox, VMware, and more.
|
||||
Engine, Hetzner Cloud, Microsoft Azure, QEMU, VirtualBox, VMware, and more.
|
||||
Support for more platforms is on the way, and anyone can add
|
||||
new platforms via plugins.
|
||||
</p>
|
||||
|
|
Loading…
Reference in New Issue