Skip to content
Snippets Groups Projects
Commit b8542ce8 authored by JonasS's avatar JonasS
Browse files

refactor: Split in driver package, restructure files

parent 6915fda9
No related branches found
No related tags found
No related merge requests found
MIT License MIT License
Copyright (c) 2017-2021 The docker-machine-driver-hetzner team Copyright (c) 2017-2023 The docker-machine-driver-hetzner team
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
......
package driver
import (
"context"
"fmt"
"github.com/docker/machine/libmachine/log"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/pkg/errors"
)
func (d *Driver) destroyDangling() {
for _, destructor := range d.dangling {
destructor()
}
}
func (d *Driver) removeEmptyServerPlacementGroup(srv *hcloud.Server) error {
pg := srv.PlacementGroup
if pg == nil {
return nil
}
if len(pg.Servers) > 1 {
log.Debugf("more than 1 servers in group, ignoring %v", pg)
return nil
}
if auto, exists := pg.Labels[d.labelName(labelAutoCreated)]; exists && auto == "true" {
_, err := d.getClient().PlacementGroup.Delete(context.Background(), pg)
if err != nil {
return fmt.Errorf("could not remove placement group: %w", err)
}
return nil
} else {
log.Debugf("group not auto-created, ignoring: %v", pg)
return nil
}
}
func (d *Driver) destroyServer() error {
if d.ServerID == 0 {
return nil
}
srv, err := d.getServerHandle()
if err != nil {
return errors.Wrap(err, "could not get server handle")
}
if srv == nil {
log.Infof(" -> Server does not exist anymore")
} else {
log.Infof(" -> Destroying server %s[%d] in...", srv.Name, srv.ID)
res, _, err := d.getClient().Server.DeleteWithResult(context.Background(), srv)
if err != nil {
return errors.Wrap(err, "could not delete server")
}
// failure to remove a placement group is not a hard error
if softErr := d.removeEmptyServerPlacementGroup(srv); softErr != nil {
log.Error(softErr)
}
// wait for the server to actually be deleted
if err = d.waitForAction(res.Action); err != nil {
return errors.Wrap(err, "could not wait for deletion")
}
}
return nil
}
This diff is collapsed.
package main package driver
import ( import (
"github.com/docker/machine/commands/commandstest" "github.com/docker/machine/commands/commandstest"
...@@ -36,7 +36,7 @@ func TestUserData(t *testing.T) { ...@@ -36,7 +36,7 @@ func TestUserData(t *testing.T) {
} }
// mutual exclusion data <=> data file // mutual exclusion data <=> data file
d := NewDriver() d := NewDriver("test")
err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagUserData: inlineContents, flagUserData: inlineContents,
flagUserDataFile: file, flagUserDataFile: file,
...@@ -44,7 +44,7 @@ func TestUserData(t *testing.T) { ...@@ -44,7 +44,7 @@ func TestUserData(t *testing.T) {
assertMutualExclusion(t, err, flagUserData, flagUserDataFile) assertMutualExclusion(t, err, flagUserData, flagUserDataFile)
// mutual exclusion data file <=> legacy flag // mutual exclusion data file <=> legacy flag
d = NewDriver() d = NewDriver("test")
err = d.setConfigFromFlagsImpl(&commandstest.FakeFlagger{ err = d.setConfigFromFlagsImpl(&commandstest.FakeFlagger{
Data: map[string]interface{}{ Data: map[string]interface{}{
legacyFlagUserDataFromFile: true, legacyFlagUserDataFromFile: true,
...@@ -54,7 +54,7 @@ func TestUserData(t *testing.T) { ...@@ -54,7 +54,7 @@ func TestUserData(t *testing.T) {
assertMutualExclusion(t, err, legacyFlagUserDataFromFile, flagUserDataFile) assertMutualExclusion(t, err, legacyFlagUserDataFromFile, flagUserDataFile)
// inline user data // inline user data
d = NewDriver() d = NewDriver("test")
err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagUserData: inlineContents, flagUserData: inlineContents,
})) }))
...@@ -71,7 +71,7 @@ func TestUserData(t *testing.T) { ...@@ -71,7 +71,7 @@ func TestUserData(t *testing.T) {
} }
// file user data // file user data
d = NewDriver() d = NewDriver("test")
err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagUserDataFile: file, flagUserDataFile: file,
})) }))
...@@ -88,7 +88,7 @@ func TestUserData(t *testing.T) { ...@@ -88,7 +88,7 @@ func TestUserData(t *testing.T) {
} }
// legacy file user data // legacy file user data
d = NewDriver() d = NewDriver("test")
err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagUserData: file, flagUserData: file,
legacyFlagUserDataFromFile: true, legacyFlagUserDataFromFile: true,
...@@ -107,7 +107,7 @@ func TestUserData(t *testing.T) { ...@@ -107,7 +107,7 @@ func TestUserData(t *testing.T) {
} }
func TestDisablePublic(t *testing.T) { func TestDisablePublic(t *testing.T) {
d := NewDriver() d := NewDriver("test")
err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagDisablePublic: true, flagDisablePublic: true,
})) }))
...@@ -127,7 +127,7 @@ func TestDisablePublic(t *testing.T) { ...@@ -127,7 +127,7 @@ func TestDisablePublic(t *testing.T) {
} }
func TestDisablePublic46(t *testing.T) { func TestDisablePublic46(t *testing.T) {
d := NewDriver() d := NewDriver("test")
err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagDisablePublic4: true, flagDisablePublic4: true,
})) }))
...@@ -146,7 +146,7 @@ func TestDisablePublic46(t *testing.T) { ...@@ -146,7 +146,7 @@ func TestDisablePublic46(t *testing.T) {
} }
// 6 // 6
d = NewDriver() d = NewDriver("test")
err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagDisablePublic6: true, flagDisablePublic6: true,
})) }))
...@@ -166,7 +166,7 @@ func TestDisablePublic46(t *testing.T) { ...@@ -166,7 +166,7 @@ func TestDisablePublic46(t *testing.T) {
} }
func TestDisablePublic46Legacy(t *testing.T) { func TestDisablePublic46Legacy(t *testing.T) {
d := NewDriver() d := NewDriver("test")
err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
legacyFlagDisablePublic4: true, legacyFlagDisablePublic4: true,
// any truthy flag should take precedence // any truthy flag should take precedence
...@@ -187,7 +187,7 @@ func TestDisablePublic46Legacy(t *testing.T) { ...@@ -187,7 +187,7 @@ func TestDisablePublic46Legacy(t *testing.T) {
} }
// 6 // 6
d = NewDriver() d = NewDriver("test")
err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
legacyFlagDisablePublic6: true, legacyFlagDisablePublic6: true,
// any truthy flag should take precedence // any truthy flag should take precedence
...@@ -210,7 +210,7 @@ func TestDisablePublic46Legacy(t *testing.T) { ...@@ -210,7 +210,7 @@ func TestDisablePublic46Legacy(t *testing.T) {
func TestImageFlagExclusions(t *testing.T) { func TestImageFlagExclusions(t *testing.T) {
// both id and name given // both id and name given
d := NewDriver() d := NewDriver("test")
err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagImageID: 42, flagImageID: 42,
flagImage: "answer", flagImage: "answer",
...@@ -218,7 +218,7 @@ func TestImageFlagExclusions(t *testing.T) { ...@@ -218,7 +218,7 @@ func TestImageFlagExclusions(t *testing.T) {
assertMutualExclusion(t, err, flagImageID, flagImage) assertMutualExclusion(t, err, flagImageID, flagImage)
// both id and arch given // both id and arch given
d = NewDriver() d = NewDriver("test")
err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagImageID: 42, flagImageID: 42,
flagImageArch: string(hcloud.ArchitectureX86), flagImageArch: string(hcloud.ArchitectureX86),
...@@ -228,7 +228,7 @@ func TestImageFlagExclusions(t *testing.T) { ...@@ -228,7 +228,7 @@ func TestImageFlagExclusions(t *testing.T) {
func TestImageArch(t *testing.T) { func TestImageArch(t *testing.T) {
// no explicit arch // no explicit arch
d := NewDriver() d := NewDriver("test")
err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagImage: "answer", flagImage: "answer",
})) }))
...@@ -245,7 +245,7 @@ func TestImageArch(t *testing.T) { ...@@ -245,7 +245,7 @@ func TestImageArch(t *testing.T) {
testArchFlag(t, hcloud.ArchitectureX86) testArchFlag(t, hcloud.ArchitectureX86)
// invalid // invalid
d = NewDriver() d = NewDriver("test")
err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err = d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagImage: "answer", flagImage: "answer",
flagImageArch: "hal9000", flagImageArch: "hal9000",
...@@ -256,7 +256,7 @@ func TestImageArch(t *testing.T) { ...@@ -256,7 +256,7 @@ func TestImageArch(t *testing.T) {
} }
func testArchFlag(t *testing.T, arch hcloud.Architecture) { func testArchFlag(t *testing.T, arch hcloud.Architecture) {
d := NewDriver() d := NewDriver("test")
err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{ err := d.setConfigFromFlagsImpl(makeFlags(map[string]interface{}{
flagImage: "answer", flagImage: "answer",
flagImageArch: string(arch), flagImageArch: string(arch),
......
//go:build !flag_debug && !instrumented //go:build !flag_debug && !instrumented
package main package driver
import ( import (
"github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/drivers"
......
//go:build flag_debug || instrumented //go:build flag_debug || instrumented
package main package driver
import ( import (
"encoding/json" "encoding/json"
......
package driver
import (
"github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/log"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/pkg/errors"
"strings"
)
func (d *Driver) setImageArch(arch string) error {
switch arch {
case "":
d.ImageArch = emptyImageArchitecture
case string(hcloud.ArchitectureARM):
d.ImageArch = hcloud.ArchitectureARM
case string(hcloud.ArchitectureX86):
d.ImageArch = hcloud.ArchitectureX86
default:
return errors.Errorf("unknown architecture %v", arch)
}
return nil
}
func (d *Driver) verifyImageFlags() error {
if d.ImageID != 0 && d.Image != "" && d.Image != defaultImage /* support legacy behaviour */ {
return d.flagFailure("--%v and --%v are mutually exclusive", flagImage, flagImageID)
} else if d.ImageID != 0 && d.ImageArch != "" {
return d.flagFailure("--%v and --%v are mutually exclusive", flagImageArch, flagImageID)
} else if d.ImageID == 0 && d.Image == "" {
d.Image = defaultImage
}
return nil
}
func (d *Driver) verifyNetworkFlags() error {
if d.DisablePublic4 && d.DisablePublic6 && !d.UsePrivateNetwork {
return d.flagFailure("--%v must be used if public networking is disabled (hint: implicitly set by --%v)",
flagUsePrivateNetwork, flagDisablePublic)
}
if d.DisablePublic4 && d.PrimaryIPv4 != "" {
return d.flagFailure("--%v and --%v are mutually exclusive", flagPrimary4, flagDisablePublic4)
}
if d.DisablePublic6 && d.PrimaryIPv6 != "" {
return d.flagFailure("--%v and --%v are mutually exclusive", flagPrimary6, flagDisablePublic6)
}
return nil
}
func (d *Driver) deprecatedBooleanFlag(opts drivers.DriverOptions, flag, deprecatedFlag string) bool {
if opts.Bool(deprecatedFlag) {
log.Warnf("--%v is deprecated, use --%v instead", deprecatedFlag, flag)
return true
}
return opts.Bool(flag)
}
func (d *Driver) setUserDataFlags(opts drivers.DriverOptions) error {
userData := opts.String(flagUserData)
userDataFile := opts.String(flagUserDataFile)
if opts.Bool(legacyFlagUserDataFromFile) {
if userDataFile != "" {
return d.flagFailure("--%v and --%v are mutually exclusive", flagUserDataFile, legacyFlagUserDataFromFile)
}
log.Warnf("--%v is deprecated, pass '--%v \"%v\"'", legacyFlagUserDataFromFile, flagUserDataFile, userData)
d.userDataFile = userData
return nil
}
d.userData = userData
d.userDataFile = userDataFile
if d.userData != "" && d.userDataFile != "" {
return d.flagFailure("--%v and --%v are mutually exclusive", flagUserData, flagUserDataFile)
}
return nil
}
func (d *Driver) setLabelsFromFlags(opts drivers.DriverOptions) error {
d.ServerLabels = make(map[string]string)
for _, label := range opts.StringSlice(flagServerLabel) {
split := strings.SplitN(label, "=", 2)
if len(split) != 2 {
return d.flagFailure("server label %v is not in key=value format", label)
}
d.ServerLabels[split[0]] = split[1]
}
d.keyLabels = make(map[string]string)
for _, label := range opts.StringSlice(flagKeyLabel) {
split := strings.SplitN(label, "=", 2)
if len(split) != 2 {
return errors.Errorf("key label %v is not in key=value format", label)
}
d.keyLabels[split[0]] = split[1]
}
return nil
}
package driver
import (
"context"
"fmt"
"github.com/docker/machine/libmachine/log"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh"
"time"
)
func (d *Driver) getClient() *hcloud.Client {
return hcloud.NewClient(hcloud.WithToken(d.AccessToken), hcloud.WithApplication("docker-machine-driver", d.version))
}
func (d *Driver) getLocation() (*hcloud.Location, error) {
if d.cachedLocation != nil {
return d.cachedLocation, nil
}
location, _, err := d.getClient().Location.GetByName(context.Background(), d.Location)
if err != nil {
return location, errors.Wrap(err, "could not get location by name")
}
d.cachedLocation = location
return location, nil
}
func (d *Driver) getType() (*hcloud.ServerType, error) {
if d.cachedType != nil {
return d.cachedType, nil
}
stype, _, err := d.getClient().ServerType.GetByName(context.Background(), d.Type)
if err != nil {
return stype, errors.Wrap(err, "could not get type by name")
}
d.cachedType = stype
return instrumented(stype), nil
}
func (d *Driver) getImage() (*hcloud.Image, error) {
if d.cachedImage != nil {
return d.cachedImage, nil
}
var image *hcloud.Image
var err error
if d.ImageID != 0 {
image, _, err = d.getClient().Image.GetByID(context.Background(), d.ImageID)
if err != nil {
return image, errors.Wrap(err, fmt.Sprintf("could not get image by id %v", d.ImageID))
}
} else {
arch, err := d.getImageArchitectureForLookup()
if err != nil {
return nil, errors.Wrap(err, "could not determine image architecture")
}
image, _, err = d.getClient().Image.GetByNameAndArchitecture(context.Background(), d.Image, arch)
if err != nil {
return image, errors.Wrap(err, fmt.Sprintf("could not get image by name %v", d.Image))
}
}
d.cachedImage = image
return instrumented(image), nil
}
func (d *Driver) getImageArchitectureForLookup() (hcloud.Architecture, error) {
if d.ImageArch != emptyImageArchitecture {
return d.ImageArch, nil
}
serverType, err := d.getType()
if err != nil {
return "", err
}
return serverType.Architecture, nil
}
func (d *Driver) getKey() (*hcloud.SSHKey, error) {
if d.cachedKey != nil {
return d.cachedKey, nil
}
stype, _, err := d.getClient().SSHKey.GetByID(context.Background(), d.KeyID)
if err != nil {
return stype, errors.Wrap(err, "could not get sshkey by ID")
}
d.cachedKey = stype
return instrumented(stype), nil
}
func (d *Driver) getRemoteKeyWithSameFingerprint(publicKeyBytes []byte) (*hcloud.SSHKey, error) {
publicKey, _, _, _, err := ssh.ParseAuthorizedKey(publicKeyBytes)
if err != nil {
return nil, errors.Wrap(err, "could not parse ssh public key")
}
fp := ssh.FingerprintLegacyMD5(publicKey)
remoteKey, _, err := d.getClient().SSHKey.GetByFingerprint(context.Background(), fp)
if err != nil {
return remoteKey, errors.Wrap(err, "could not get sshkey by fingerprint")
}
return instrumented(remoteKey), nil
}
func (d *Driver) getServerHandle() (*hcloud.Server, error) {
if d.cachedServer != nil {
return d.cachedServer, nil
}
if d.ServerID == 0 {
return nil, errors.New("server ID was 0")
}
srv, _, err := d.getClient().Server.GetByID(context.Background(), d.ServerID)
if err != nil {
return nil, errors.Wrap(err, "could not get client by ID")
}
d.cachedServer = srv
return srv, nil
}
func (d *Driver) waitForAction(a *hcloud.Action) error {
for {
act, _, err := d.getClient().Action.GetByID(context.Background(), a.ID)
if err != nil {
return errors.Wrap(err, "could not get client by ID")
}
if act.Status == hcloud.ActionStatusSuccess {
log.Debugf(" -> finished %s[%d]", act.Command, act.ID)
break
} else if act.Status == hcloud.ActionStatusRunning {
log.Debugf(" -> %s[%d]: %d %%", act.Command, act.ID, act.Progress)
} else if act.Status == hcloud.ActionStatusError {
return act.Error()
}
time.Sleep(1 * time.Second)
}
return nil
}
//go:build instrumented //go:build instrumented
package main package driver
import ( import (
"encoding/json" "encoding/json"
...@@ -9,6 +9,8 @@ import ( ...@@ -9,6 +9,8 @@ import (
"github.com/docker/machine/libmachine/log" "github.com/docker/machine/libmachine/log"
) )
const runningInstrumented = false
func instrumented[T any](input T) T { func instrumented[T any](input T) T {
j, err := json.Marshal(input) j, err := json.Marshal(input)
if err != nil { if err != nil {
......
//go:build !instrumented //go:build !instrumented
package main package driver
const runningInstrumented = false
func instrumented[T any](input T) T { func instrumented[T any](input T) T {
return input return input
......
package driver
const labelNamespace = "docker-machine"
func (d *Driver) labelName(name string) string {
return labelNamespace + "/" + name
}
package driver
import (
"context"
"fmt"
"github.com/docker/machine/libmachine/log"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/pkg/errors"
"net"
"time"
)
func (d *Driver) getPrimaryIPv4() (*hcloud.PrimaryIP, error) {
raw := d.PrimaryIPv4
if raw == "" {
return nil, nil
} else if d.cachedPrimaryIPv4 != nil {
return d.cachedPrimaryIPv4, nil
}
ip, err := d.resolvePrimaryIP(raw)
d.cachedPrimaryIPv4 = ip
return ip, err
}
func (d *Driver) getPrimaryIPv6() (*hcloud.PrimaryIP, error) {
raw := d.PrimaryIPv6
if raw == "" {
return nil, nil
} else if d.cachedPrimaryIPv6 != nil {
return d.cachedPrimaryIPv6, nil
}
ip, err := d.resolvePrimaryIP(raw)
d.cachedPrimaryIPv6 = ip
return ip, err
}
func (d *Driver) resolvePrimaryIP(raw string) (*hcloud.PrimaryIP, error) {
client := d.getClient().PrimaryIP
var getter func(context.Context, string) (*hcloud.PrimaryIP, *hcloud.Response, error)
if net.ParseIP(raw) != nil {
getter = client.GetByIP
} else {
getter = client.Get
}
ip, _, err := getter(context.Background(), raw)
if err != nil {
return nil, fmt.Errorf("could not get primary IP: %w", err)
}
if ip != nil {
return instrumented(ip), nil
}
return nil, fmt.Errorf("primary IP not found: %v", raw)
}
func (d *Driver) setPublicNetIfRequired(srvopts *hcloud.ServerCreateOpts) error {
pip4, err := d.getPrimaryIPv4()
if err != nil {
return err
}
pip6, err := d.getPrimaryIPv6()
if err != nil {
return err
}
if d.DisablePublic4 || d.DisablePublic6 || pip4 != nil || pip6 != nil {
srvopts.PublicNet = &hcloud.ServerCreatePublicNet{
EnableIPv4: !d.DisablePublic4 || pip4 != nil,
EnableIPv6: !d.DisablePublic6 || pip6 != nil,
IPv4: pip4,
IPv6: pip6,
}
}
return nil
}
func (d *Driver) configureNetworkAccess(srv hcloud.ServerCreateResult) error {
if d.UsePrivateNetwork {
for {
// we need to wait until network is attached
log.Infof("Wait until private network attached ...")
server, _, err := d.getClient().Server.GetByID(context.Background(), srv.Server.ID)
if err != nil {
return errors.Wrapf(err, "could not get newly created server [%d]", srv.Server.ID)
}
if server.PrivateNet != nil {
d.IPAddress = server.PrivateNet[0].IP.String()
break
}
time.Sleep(1 * time.Second)
}
} else if d.DisablePublic4 {
log.Infof("Using public IPv6 network ...")
pv6 := srv.Server.PublicNet.IPv6
ip := pv6.IP
if ip.Mask(pv6.Network.Mask).Equal(pv6.Network.IP) { // no host given
ip[net.IPv6len-1] |= 0x01 // TODO make this configurable
}
ips := ip.String()
log.Infof(" -> resolved %v ...", ips)
d.IPAddress = ips
} else {
log.Infof("Using public network ...")
d.IPAddress = srv.Server.PublicNet.IPv4.IP.String()
}
return nil
}
package driver
import (
"context"
"fmt"
"github.com/docker/machine/libmachine/log"
"github.com/hetznercloud/hcloud-go/hcloud"
)
const (
labelAutoSpreadPg = "auto-spread"
labelAutoCreated = "auto-created"
autoSpreadPgName = "__auto_spread"
)
func (d *Driver) getAutoPlacementGroup() (*hcloud.PlacementGroup, error) {
res, err := d.getClient().PlacementGroup.AllWithOpts(context.Background(), hcloud.PlacementGroupListOpts{
ListOpts: hcloud.ListOpts{LabelSelector: d.labelName(labelAutoSpreadPg)},
})
if err != nil {
return nil, err
}
if len(res) != 0 {
return res[0], nil
}
grp, err := d.makePlacementGroup("Docker-Machine auto spread", map[string]string{
d.labelName(labelAutoSpreadPg): "true",
d.labelName(labelAutoCreated): "true",
})
return instrumented(grp), err
}
func (d *Driver) makePlacementGroup(name string, labels map[string]string) (*hcloud.PlacementGroup, error) {
grp, _, err := d.getClient().PlacementGroup.Create(context.Background(), instrumented(hcloud.PlacementGroupCreateOpts{
Name: name,
Labels: labels,
Type: "spread",
}))
if grp.PlacementGroup != nil {
d.dangling = append(d.dangling, func() {
_, err := d.getClient().PlacementGroup.Delete(context.Background(), grp.PlacementGroup)
if err != nil {
log.Errorf("could not delete placement group: %v", err)
}
})
}
if err != nil {
return nil, fmt.Errorf("could not create placement group: %w", err)
}
return instrumented(grp.PlacementGroup), nil
}
func (d *Driver) getPlacementGroup() (*hcloud.PlacementGroup, error) {
if d.placementGroup == "" {
return nil, nil
} else if d.cachedPGrp != nil {
return d.cachedPGrp, nil
}
name := d.placementGroup
if name == autoSpreadPgName {
grp, err := d.getAutoPlacementGroup()
d.cachedPGrp = grp
return grp, err
} else {
client := d.getClient().PlacementGroup
grp, _, err := client.Get(context.Background(), name)
if err != nil {
return nil, fmt.Errorf("could not get placement group: %w", err)
}
if grp != nil {
return grp, nil
}
return d.makePlacementGroup(name, map[string]string{d.labelName(labelAutoCreated): "true"})
}
}
package driver
import (
"context"
"github.com/docker/machine/libmachine/state"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/pkg/errors"
"os"
"time"
)
func (d *Driver) waitForRunningServer() error {
for {
srvstate, err := d.GetState()
if err != nil {
return errors.Wrap(err, "could not get state")
}
if srvstate == state.Running {
break
}
time.Sleep(1 * time.Second)
}
return nil
}
func (d *Driver) makeCreateServerOptions() (*hcloud.ServerCreateOpts, error) {
pgrp, err := d.getPlacementGroup()
if err != nil {
return nil, err
}
userData, err := d.getUserData()
if err != nil {
return nil, err
}
srvopts := hcloud.ServerCreateOpts{
Name: d.GetMachineName(),
UserData: userData,
Labels: d.ServerLabels,
PlacementGroup: pgrp,
}
err = d.setPublicNetIfRequired(&srvopts)
if err != nil {
return nil, err
}
networks, err := d.createNetworks()
if err != nil {
return nil, err
}
srvopts.Networks = networks
firewalls, err := d.createFirewalls()
if err != nil {
return nil, err
}
srvopts.Firewalls = firewalls
volumes, err := d.createVolumes()
if err != nil {
return nil, err
}
srvopts.Volumes = volumes
if srvopts.Location, err = d.getLocation(); err != nil {
return nil, errors.Wrap(err, "could not get location")
}
if srvopts.ServerType, err = d.getType(); err != nil {
return nil, errors.Wrap(err, "could not get type")
}
if srvopts.Image, err = d.getImage(); err != nil {
return nil, errors.Wrap(err, "could not get image")
}
key, err := d.getKey()
if err != nil {
return nil, errors.Wrap(err, "could not get ssh key")
}
srvopts.SSHKeys = append(d.cachedAdditionalKeys, key)
return &srvopts, nil
}
func (d *Driver) getUserData() (string, error) {
file := d.userDataFile
if file == "" {
return d.userData, nil
}
readUserData, err := os.ReadFile(file)
if err != nil {
return "", err
}
return string(readUserData), nil
}
func (d *Driver) createNetworks() ([]*hcloud.Network, error) {
networks := []*hcloud.Network{}
for _, networkIDorName := range d.Networks {
network, _, err := d.getClient().Network.Get(context.Background(), networkIDorName)
if err != nil {
return nil, errors.Wrap(err, "could not get network by ID or name")
}
if network == nil {
return nil, errors.Errorf("network '%s' not found", networkIDorName)
}
networks = append(networks, network)
}
return instrumented(networks), nil
}
func (d *Driver) createFirewalls() ([]*hcloud.ServerCreateFirewall, error) {
firewalls := []*hcloud.ServerCreateFirewall{}
for _, firewallIDorName := range d.Firewalls {
firewall, _, err := d.getClient().Firewall.Get(context.Background(), firewallIDorName)
if err != nil {
return nil, errors.Wrap(err, "could not get firewall by ID or name")
}
if firewall == nil {
return nil, errors.Errorf("firewall '%s' not found", firewallIDorName)
}
firewalls = append(firewalls, &hcloud.ServerCreateFirewall{Firewall: *firewall})
}
return instrumented(firewalls), nil
}
func (d *Driver) createVolumes() ([]*hcloud.Volume, error) {
volumes := []*hcloud.Volume{}
for _, volumeIDorName := range d.Volumes {
volume, _, err := d.getClient().Volume.Get(context.Background(), volumeIDorName)
if err != nil {
return nil, errors.Wrap(err, "could not get volume by ID or name")
}
if volume == nil {
return nil, errors.Errorf("volume '%s' not found", volumeIDorName)
}
volumes = append(volumes, volume)
}
return instrumented(volumes), nil
}
package driver
import (
"context"
"fmt"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnutils"
mcnssh "github.com/docker/machine/libmachine/ssh"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh"
"os"
)
func (d *Driver) setupExistingKey() error {
if !d.IsExistingKey {
return nil
}
if d.originalKey == "" {
return d.flagFailure("specifying an existing key ID requires the existing key path to be set as well")
}
key, err := d.getKey()
if err != nil {
return errors.Wrap(err, "could not get key")
}
buf, err := os.ReadFile(d.originalKey + ".pub")
if err != nil {
return errors.Wrap(err, "could not read public key")
}
// Will also parse `ssh-rsa w309jwf0e39jf asdf` public keys
pubk, _, _, _, err := ssh.ParseAuthorizedKey(buf)
if err != nil {
return errors.Wrap(err, "could not parse authorized key")
}
if key.Fingerprint != ssh.FingerprintLegacyMD5(pubk) &&
key.Fingerprint != ssh.FingerprintSHA256(pubk) {
return errors.Errorf("remote key %d does not match local key %s", d.KeyID, d.originalKey)
}
return nil
}
func (d *Driver) copySSHKeyPair(src string) error {
if err := mcnutils.CopyFile(src, d.GetSSHKeyPath()); err != nil {
return errors.Wrap(err, "could not copy ssh key")
}
if err := mcnutils.CopyFile(src+".pub", d.GetSSHKeyPath()+".pub"); err != nil {
return errors.Wrap(err, "could not copy ssh public key")
}
if err := os.Chmod(d.GetSSHKeyPath(), 0600); err != nil {
return errors.Wrap(err, "could not set permissions on the ssh key")
}
return nil
}
func (d *Driver) createRemoteKeys() error {
if d.KeyID == 0 {
log.Infof("Creating SSH key...")
buf, err := os.ReadFile(d.GetSSHKeyPath() + ".pub")
if err != nil {
return errors.Wrap(err, "could not read ssh public key")
}
key, err := d.getRemoteKeyWithSameFingerprint(buf)
if err != nil {
return errors.Wrap(err, "error retrieving potentially existing key")
}
if key == nil {
log.Infof("SSH key not found in Hetzner. Uploading...")
key, err = d.makeKey(d.GetMachineName(), string(buf), d.keyLabels)
if err != nil {
return err
}
} else {
d.IsExistingKey = true
log.Debugf("SSH key found in Hetzner. ID: %d", key.ID)
}
d.KeyID = key.ID
}
for i, pubkey := range d.AdditionalKeys {
key, err := d.getRemoteKeyWithSameFingerprint([]byte(pubkey))
if err != nil {
return errors.Wrapf(err, "error checking for existing key for %v", pubkey)
}
if key == nil {
log.Infof("Creating new key for %v...", pubkey)
key, err = d.makeKey(fmt.Sprintf("%v-additional-%d", d.GetMachineName(), i), pubkey, d.keyLabels)
if err != nil {
return errors.Wrapf(err, "error creating new key for %v", pubkey)
}
log.Infof(" -> Created %v", key.ID)
d.AdditionalKeyIDs = append(d.AdditionalKeyIDs, key.ID)
} else {
log.Infof("Using existing key (%v) %v", key.ID, key.Name)
}
d.cachedAdditionalKeys = append(d.cachedAdditionalKeys, key)
}
return nil
}
func (d *Driver) prepareLocalKey() error {
if d.originalKey != "" {
log.Debugf("Copying SSH key...")
if err := d.copySSHKeyPair(d.originalKey); err != nil {
return errors.Wrap(err, "could not copy ssh key pair")
}
} else {
log.Debugf("Generating SSH key...")
if err := mcnssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
return errors.Wrap(err, "could not generate ssh key")
}
}
return nil
}
// Creates a new key for the machine and appends it to the dangling key list
func (d *Driver) makeKey(name string, pubkey string, labels map[string]string) (*hcloud.SSHKey, error) {
keyopts := hcloud.SSHKeyCreateOpts{
Name: name,
PublicKey: pubkey,
Labels: labels,
}
key, _, err := d.getClient().SSHKey.Create(context.Background(), instrumented(keyopts))
if err != nil {
return nil, errors.Wrap(err, "could not create ssh key")
} else if key == nil {
return nil, errors.Errorf("key upload did not return an error, but key was nil")
}
d.dangling = append(d.dangling, func() {
_, err := d.getClient().SSHKey.Delete(context.Background(), key)
if err != nil {
log.Error(fmt.Errorf("could not delete ssh key: %w", err))
}
})
return key, nil
}
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/JonasProgrammer/docker-machine-driver-hetzner/driver"
"github.com/docker/machine/libmachine/drivers/plugin" "github.com/docker/machine/libmachine/drivers/plugin"
) )
...@@ -14,10 +15,9 @@ var version string ...@@ -14,10 +15,9 @@ var version string
func main() { func main() {
versionFlag := flag.Bool("v", false, "prints current docker-machine-driver-hetzner version") versionFlag := flag.Bool("v", false, "prints current docker-machine-driver-hetzner version")
flag.Parse() flag.Parse()
instrumented("sadf")
if *versionFlag { if *versionFlag {
fmt.Printf("Version: %s\n", version) fmt.Printf("Version: %s\n", version)
os.Exit(0) os.Exit(0)
} }
plugin.RegisterDriver(NewDriver()) plugin.RegisterDriver(driver.NewDriver(version))
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment