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"
|
dockerbuilder "github.com/hashicorp/packer/builder/docker"
|
||||||
filebuilder "github.com/hashicorp/packer/builder/file"
|
filebuilder "github.com/hashicorp/packer/builder/file"
|
||||||
googlecomputebuilder "github.com/hashicorp/packer/builder/googlecompute"
|
googlecomputebuilder "github.com/hashicorp/packer/builder/googlecompute"
|
||||||
|
hcloudbuilder "github.com/hashicorp/packer/builder/hcloud"
|
||||||
hypervisobuilder "github.com/hashicorp/packer/builder/hyperv/iso"
|
hypervisobuilder "github.com/hashicorp/packer/builder/hyperv/iso"
|
||||||
hypervvmcxbuilder "github.com/hashicorp/packer/builder/hyperv/vmcx"
|
hypervvmcxbuilder "github.com/hashicorp/packer/builder/hyperv/vmcx"
|
||||||
lxcbuilder "github.com/hashicorp/packer/builder/lxc"
|
lxcbuilder "github.com/hashicorp/packer/builder/lxc"
|
||||||
|
@ -95,6 +96,7 @@ var Builders = map[string]packer.Builder{
|
||||||
"docker": new(dockerbuilder.Builder),
|
"docker": new(dockerbuilder.Builder),
|
||||||
"file": new(filebuilder.Builder),
|
"file": new(filebuilder.Builder),
|
||||||
"googlecompute": new(googlecomputebuilder.Builder),
|
"googlecompute": new(googlecomputebuilder.Builder),
|
||||||
|
"hcloud": new(hcloudbuilder.Builder),
|
||||||
"hyperv-iso": new(hypervisobuilder.Builder),
|
"hyperv-iso": new(hypervisobuilder.Builder),
|
||||||
"hyperv-vmcx": new(hypervvmcxbuilder.Builder),
|
"hyperv-vmcx": new(hypervvmcxbuilder.Builder),
|
||||||
"lxc": new(lxcbuilder.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",
|
"path": "github.com/hashicorp/yamux",
|
||||||
"revision": "df949784da9ed028ee76df44652e42d37a09d7e4"
|
"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=",
|
"checksumSHA1": "3/Bhy+ua/DCv2ElMD5GzOYSGN6g=",
|
||||||
"comment": "0.2.2-2-gc01cf91",
|
"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>
|
<p>
|
||||||
Out of the box Packer comes with support to build images for
|
Out of the box Packer comes with support to build images for
|
||||||
Amazon EC2, CloudStack, DigitalOcean, Docker, Google Compute
|
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
|
Support for more platforms is on the way, and anyone can add
|
||||||
new platforms via plugins.
|
new platforms via plugins.
|
||||||
</p>
|
</p>
|
||||||
|
|
Loading…
Reference in New Issue