forked from toolshed/abra
		
	refactor!: abra server interface more coherent
This follows our app new UX and interactive mode design.
This commit is contained in:
		@ -132,3 +132,130 @@ var DNSPriorityFlag = &cli.IntFlag{
 | 
			
		||||
	Usage:       "Domain name priority value",
 | 
			
		||||
	Destination: &DNSPriority,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var ServerProvider string
 | 
			
		||||
 | 
			
		||||
var ServerProviderFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "provider",
 | 
			
		||||
	Aliases:     []string{"p"},
 | 
			
		||||
	Usage:       "3rd party server provider",
 | 
			
		||||
	Destination: &ServerProvider,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var CapsulInstanceURL string
 | 
			
		||||
 | 
			
		||||
var CapsulInstanceURLFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "capsul-url",
 | 
			
		||||
	Value:       "",
 | 
			
		||||
	Aliases:     []string{"cu"},
 | 
			
		||||
	Usage:       "Capsul instance URL",
 | 
			
		||||
	Destination: &CapsulInstanceURL,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var CapsulName string
 | 
			
		||||
 | 
			
		||||
var CapsulNameFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "capsul-name",
 | 
			
		||||
	Value:       "",
 | 
			
		||||
	Aliases:     []string{"cn"},
 | 
			
		||||
	Usage:       "Capsul name",
 | 
			
		||||
	Destination: &CapsulName,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var CapsulType string
 | 
			
		||||
 | 
			
		||||
var CapsulTypeFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "capsul-type",
 | 
			
		||||
	Value:       "",
 | 
			
		||||
	Aliases:     []string{"ct"},
 | 
			
		||||
	Usage:       "Capsul type",
 | 
			
		||||
	Destination: &CapsulType,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var CapsulImage string
 | 
			
		||||
 | 
			
		||||
var CapsulImageFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "capsul-image",
 | 
			
		||||
	Value:       "debian10",
 | 
			
		||||
	Aliases:     []string{"ci"},
 | 
			
		||||
	Usage:       "Capsul image",
 | 
			
		||||
	Destination: &CapsulImage,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var CapsulSSHKeys cli.StringSlice
 | 
			
		||||
 | 
			
		||||
var CapsulSSHKeysFlag = &cli.StringSliceFlag{
 | 
			
		||||
	Name:        "capsul-ssh-keys",
 | 
			
		||||
	Aliases:     []string{"cs"},
 | 
			
		||||
	Usage:       "Capsul SSH key (e.g. me@foo.com)",
 | 
			
		||||
	Destination: &CapsulSSHKeys,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var CapsulAPIToken string
 | 
			
		||||
 | 
			
		||||
var CapsulAPITokenFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "capsul-token",
 | 
			
		||||
	Aliases:     []string{"ca"},
 | 
			
		||||
	Usage:       "Capsul API token",
 | 
			
		||||
	EnvVars:     []string{"CAPSUL_TOKEN"},
 | 
			
		||||
	Destination: &CapsulAPIToken,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var HetznerCloudName string
 | 
			
		||||
 | 
			
		||||
var HetznerCloudNameFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "hetzner-name",
 | 
			
		||||
	Value:       "",
 | 
			
		||||
	Aliases:     []string{"hn"},
 | 
			
		||||
	Usage:       "hetzner cloud name",
 | 
			
		||||
	Destination: &HetznerCloudName,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var HetznerCloudType string
 | 
			
		||||
 | 
			
		||||
var HetznerCloudTypeFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "hetzner-type",
 | 
			
		||||
	Aliases:     []string{"ht"},
 | 
			
		||||
	Usage:       "hetzner cloud type",
 | 
			
		||||
	Destination: &HetznerCloudType,
 | 
			
		||||
	Value:       "cx11",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var HetznerCloudImage string
 | 
			
		||||
 | 
			
		||||
var HetznerCloudImageFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "hetzner-image",
 | 
			
		||||
	Aliases:     []string{"hi"},
 | 
			
		||||
	Usage:       "hetzner cloud image",
 | 
			
		||||
	Value:       "debian-10",
 | 
			
		||||
	Destination: &HetznerCloudImage,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var HetznerCloudSSHKeys cli.StringSlice
 | 
			
		||||
 | 
			
		||||
var HetznerCloudSSHKeysFlag = &cli.StringSliceFlag{
 | 
			
		||||
	Name:        "hetzner-ssh-keys",
 | 
			
		||||
	Aliases:     []string{"hs"},
 | 
			
		||||
	Usage:       "hetzner cloud SSH keys (e.g. me@foo.com)",
 | 
			
		||||
	Destination: &HetznerCloudSSHKeys,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var HetznerCloudLocation string
 | 
			
		||||
 | 
			
		||||
var HetznerCloudLocationFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "hetzner-location",
 | 
			
		||||
	Aliases:     []string{"hl"},
 | 
			
		||||
	Usage:       "hetzner cloud server location",
 | 
			
		||||
	Value:       "hel1",
 | 
			
		||||
	Destination: &HetznerCloudLocation,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var HetznerCloudAPIToken string
 | 
			
		||||
 | 
			
		||||
var HetznerCloudAPITokenFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "hetzner-token",
 | 
			
		||||
	Aliases:     []string{"ha"},
 | 
			
		||||
	Usage:       "hetzner cloud API token",
 | 
			
		||||
	EnvVars:     []string{"HCLOUD_TOKEN"},
 | 
			
		||||
	Destination: &HetznerCloudAPIToken,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										191
									
								
								cli/internal/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								cli/internal/server.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,191 @@
 | 
			
		||||
package internal
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// EnsureServerProvider ensures a 3rd party server provider is chosen.
 | 
			
		||||
func EnsureServerProvider() error {
 | 
			
		||||
	if ServerProvider == "" && !NoInput {
 | 
			
		||||
		prompt := &survey.Select{
 | 
			
		||||
			Message: "Select server provider",
 | 
			
		||||
			Options: []string{"capsul", "hetzner-cloud"},
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := survey.AskOne(prompt, &ServerProvider); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ServerProvider == "" {
 | 
			
		||||
		return fmt.Errorf("missing server provider?")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureCapsulNewFlags ensure all flags are present.
 | 
			
		||||
func EnsureNewCapsulVPSFlags(c *cli.Context) error {
 | 
			
		||||
	if CapsulName == "" && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify capsul name",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &CapsulName); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if CapsulInstanceURL == "" && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify capsul instance URL",
 | 
			
		||||
			Default: "yolo.servers.coop",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &CapsulInstanceURL); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if CapsulType == "" && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify capsul type",
 | 
			
		||||
			Default: "f1-xs",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &CapsulType); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if CapsulImage == "" && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify capsul image",
 | 
			
		||||
			Default: "debian10",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &CapsulImage); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(CapsulSSHKeys.Value()) == 0 && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify capsul SSH keys",
 | 
			
		||||
			Default: "me@foo.com,you@bar.com",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &CapsulSSHKeys); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if CapsulAPIToken == "" && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify capsul API token",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &CapsulAPIToken); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if CapsulName == "" {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul name?"))
 | 
			
		||||
	}
 | 
			
		||||
	if CapsulInstanceURL == "" {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul instance url?"))
 | 
			
		||||
	}
 | 
			
		||||
	if CapsulType == "" {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul type?"))
 | 
			
		||||
	}
 | 
			
		||||
	if CapsulImage == "" {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul image?"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(CapsulSSHKeys.Value()) == 0 {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul ssh keys?"))
 | 
			
		||||
	}
 | 
			
		||||
	if CapsulAPIToken == "" {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul API token?"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureNewHetznerCloudVPSFlags ensure all flags are present.
 | 
			
		||||
func EnsureNewHetznerCloudVPSFlags(c *cli.Context) error {
 | 
			
		||||
	if HetznerCloudName == "" && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify hetzner cloud VPS name",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &HetznerCloudName); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if HetznerCloudType == "" && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify hetzner cloud VPS type",
 | 
			
		||||
			Default: "cx11",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &HetznerCloudType); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if HetznerCloudImage == "" && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify hetzner cloud VPS image",
 | 
			
		||||
			Default: "debian-10",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &HetznerCloudImage); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(HetznerCloudSSHKeys.Value()) == 0 && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify hetzner cloud SSH keys",
 | 
			
		||||
			Default: "me@foo.com,you@bar.com",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &HetznerCloudSSHKeys); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if HetznerCloudLocation == "" && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify hetzner cloud VPS location",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &HetznerCloudLocation); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if HetznerCloudAPIToken == "" && !NoInput {
 | 
			
		||||
		prompt := &survey.Input{
 | 
			
		||||
			Message: "specify hetzner cloud API token",
 | 
			
		||||
		}
 | 
			
		||||
		if err := survey.AskOne(prompt, &HetznerCloudAPIToken); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if HetznerCloudName == "" {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing hetzner cloud VPS name?"))
 | 
			
		||||
	}
 | 
			
		||||
	if HetznerCloudType == "" {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing hetzner cloud VPS type?"))
 | 
			
		||||
	}
 | 
			
		||||
	if HetznerCloudImage == "" {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing hetzner cloud image?"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(HetznerCloudSSHKeys.Value()) == 0 {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing hetzner cloud ssh keys?"))
 | 
			
		||||
	}
 | 
			
		||||
	if HetznerCloudLocation == "" {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing hetzner cloud VPS location?"))
 | 
			
		||||
	}
 | 
			
		||||
	if HetznerCloudAPIToken == "" {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, fmt.Errorf("missing hetzner cloud API token?"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@ -15,8 +15,8 @@ import (
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RecordCreateCommand lists domains.
 | 
			
		||||
var RecordCreateCommand = &cli.Command{
 | 
			
		||||
// RecordNewCommand creates a new domain name record.
 | 
			
		||||
var RecordNewCommand = &cli.Command{
 | 
			
		||||
	Name:      "new",
 | 
			
		||||
	Usage:     "Create a new domain record",
 | 
			
		||||
	Aliases:   []string{"n"},
 | 
			
		||||
 | 
			
		||||
@ -23,16 +23,16 @@ You need an account with such a provider already. Typically, you need to
 | 
			
		||||
provide an API token on the Abra command-line when using these commands so that
 | 
			
		||||
you can authenticate with your provider account.
 | 
			
		||||
 | 
			
		||||
Any new provider can be integrated, we welcome change sets. See the underlying
 | 
			
		||||
DNS library documentation for more. It supports many existing providers and
 | 
			
		||||
allows to implement new provider support easily.
 | 
			
		||||
New providers can be integrated, we welcome change sets. See the underlying DNS
 | 
			
		||||
library documentation for more. It supports many existing providers and allows
 | 
			
		||||
to implement new provider support easily.
 | 
			
		||||
 | 
			
		||||
    https://pkg.go.dev/github.com/libdns/libdns
 | 
			
		||||
 | 
			
		||||
`,
 | 
			
		||||
	Subcommands: []*cli.Command{
 | 
			
		||||
		RecordListCommand,
 | 
			
		||||
		RecordCreateCommand,
 | 
			
		||||
		RecordNewCommand,
 | 
			
		||||
		RecordRemoveCommand,
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,207 +1,148 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/formatter"
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/libcapsul"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/hetznercloud/hcloud-go/hcloud"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var hetznerCloudType string
 | 
			
		||||
var hetznerCloudImage string
 | 
			
		||||
var hetznerCloudSSHKeys cli.StringSlice
 | 
			
		||||
var hetznerCloudLocation string
 | 
			
		||||
var hetznerCloudAPIToken string
 | 
			
		||||
var serverNewHetznerCloudCommand = &cli.Command{
 | 
			
		||||
	Name:      "hetzner",
 | 
			
		||||
	Usage:     "Create a new Hetzner virtual server",
 | 
			
		||||
	ArgsUsage: "<name>",
 | 
			
		||||
	Description: `
 | 
			
		||||
Create a new Hetzner virtual server.
 | 
			
		||||
func newHetznerCloudVPS(c *cli.Context) error {
 | 
			
		||||
	if err := internal.EnsureNewHetznerCloudVPSFlags(c); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
This command uses the uses the Hetzner Cloud API bindings to send a server
 | 
			
		||||
creation request. You must already have a Hetzner Cloud account and an account
 | 
			
		||||
API token before using this command.
 | 
			
		||||
	client := hcloud.NewClient(hcloud.WithToken(internal.HetznerCloudAPIToken))
 | 
			
		||||
 | 
			
		||||
Your token can be loaded from the environment using the HCLOUD_TOKEN
 | 
			
		||||
environment variable or otherwise passing the "--token/-T" flag.
 | 
			
		||||
`,
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:        "type",
 | 
			
		||||
			Aliases:     []string{"t"},
 | 
			
		||||
			Usage:       "Server type",
 | 
			
		||||
			Destination: &hetznerCloudType,
 | 
			
		||||
			Value:       "cx11",
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:        "image",
 | 
			
		||||
			Aliases:     []string{"i"},
 | 
			
		||||
			Usage:       "Image type",
 | 
			
		||||
			Value:       "debian-10",
 | 
			
		||||
			Destination: &hetznerCloudImage,
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringSliceFlag{
 | 
			
		||||
			Name:        "ssh-keys",
 | 
			
		||||
			Aliases:     []string{"s"},
 | 
			
		||||
			Usage:       "SSH keys",
 | 
			
		||||
			Destination: &hetznerCloudSSHKeys,
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:        "location",
 | 
			
		||||
			Aliases:     []string{"l"},
 | 
			
		||||
			Usage:       "Server location",
 | 
			
		||||
			Value:       "hel1",
 | 
			
		||||
			Destination: &hetznerCloudLocation,
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:        "token",
 | 
			
		||||
			Aliases:     []string{"T"},
 | 
			
		||||
			Usage:       "Hetzner Cloud API token",
 | 
			
		||||
			EnvVars:     []string{"HCLOUD_TOKEN"},
 | 
			
		||||
			Destination: &hetznerCloudAPIToken,
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		name := c.Args().First()
 | 
			
		||||
		if name == "" {
 | 
			
		||||
			internal.ShowSubcommandHelpAndError(c, errors.New("no name provided"))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if hetznerCloudAPIToken == "" {
 | 
			
		||||
			logrus.Fatal("Hetzner Cloud API token is missing")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx := context.Background()
 | 
			
		||||
		client := hcloud.NewClient(hcloud.WithToken(hetznerCloudAPIToken))
 | 
			
		||||
 | 
			
		||||
		logrus.Debugf("successfully created hetzner cloud API client")
 | 
			
		||||
 | 
			
		||||
		var sshKeys []*hcloud.SSHKey
 | 
			
		||||
		for _, sshKey := range c.StringSlice("ssh-keys") {
 | 
			
		||||
			sshKey, _, err := client.SSHKey.GetByName(ctx, sshKey)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			sshKeys = append(sshKeys, sshKey)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		serverOpts := hcloud.ServerCreateOpts{
 | 
			
		||||
			Name:       name,
 | 
			
		||||
			ServerType: &hcloud.ServerType{Name: hetznerCloudType},
 | 
			
		||||
			Image:      &hcloud.Image{Name: hetznerCloudImage},
 | 
			
		||||
			SSHKeys:    sshKeys,
 | 
			
		||||
			Location:   &hcloud.Location{Name: hetznerCloudLocation},
 | 
			
		||||
		}
 | 
			
		||||
		res, _, err := client.Server.Create(ctx, serverOpts)
 | 
			
		||||
	var sshKeysRaw []string
 | 
			
		||||
	var sshKeys []*hcloud.SSHKey
 | 
			
		||||
	for _, sshKey := range c.StringSlice("ssh-keys") {
 | 
			
		||||
		sshKey, _, err := client.SSHKey.GetByName(c.Context, sshKey)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		sshKeys = append(sshKeys, sshKey)
 | 
			
		||||
		sshKeysRaw = append(sshKeysRaw, sshKey.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		logrus.Debugf("new server '%s' created", name)
 | 
			
		||||
	serverOpts := hcloud.ServerCreateOpts{
 | 
			
		||||
		Name:       internal.HetznerCloudName,
 | 
			
		||||
		ServerType: &hcloud.ServerType{Name: internal.HetznerCloudType},
 | 
			
		||||
		Image:      &hcloud.Image{Name: internal.HetznerCloudImage},
 | 
			
		||||
		SSHKeys:    sshKeys,
 | 
			
		||||
		Location:   &hcloud.Location{Name: internal.HetznerCloudLocation},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		tableColumns := []string{"Name", "IPv4", "Root Password"}
 | 
			
		||||
		table := formatter.CreateTable(tableColumns)
 | 
			
		||||
	tableColumns := []string{"name", "type", "image", "ssh-keys", "location"}
 | 
			
		||||
	table := formatter.CreateTable(tableColumns)
 | 
			
		||||
	table.Append([]string{
 | 
			
		||||
		internal.HetznerCloudName,
 | 
			
		||||
		internal.HetznerCloudType,
 | 
			
		||||
		internal.HetznerCloudImage,
 | 
			
		||||
		strings.Join(sshKeysRaw, "\n"),
 | 
			
		||||
		internal.HetznerCloudLocation,
 | 
			
		||||
	})
 | 
			
		||||
	table.Render()
 | 
			
		||||
 | 
			
		||||
		if len(sshKeys) > 0 {
 | 
			
		||||
			table.Append([]string{name, res.Server.PublicNet.IPv4.IP.String(), "N/A (using SSH keys)"})
 | 
			
		||||
		} else {
 | 
			
		||||
			table.Append([]string{name, res.Server.PublicNet.IPv4.IP.String(), res.RootPassword})
 | 
			
		||||
		}
 | 
			
		||||
	response := false
 | 
			
		||||
	prompt := &survey.Confirm{
 | 
			
		||||
		Message: "continue with capsul creation?",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		table.Render()
 | 
			
		||||
	if err := survey.AskOne(prompt, &response); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
	if !response {
 | 
			
		||||
		logrus.Fatal("exiting as requested")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res, _, err := client.Server.Create(c.Context, serverOpts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tableColumns = []string{"name", "ipv4", "root password"}
 | 
			
		||||
	table = formatter.CreateTable(tableColumns)
 | 
			
		||||
	if len(sshKeys) > 0 {
 | 
			
		||||
		table.Append([]string{
 | 
			
		||||
			internal.HetznerCloudName,
 | 
			
		||||
			res.Server.PublicNet.IPv4.IP.String(),
 | 
			
		||||
			"N/A (using SSH keys)",
 | 
			
		||||
		})
 | 
			
		||||
	} else {
 | 
			
		||||
		table.Append([]string{
 | 
			
		||||
			internal.HetznerCloudName,
 | 
			
		||||
			res.Server.PublicNet.IPv4.IP.String(),
 | 
			
		||||
			res.RootPassword,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	table.SetCaption(true, "hetzner cloud creation response")
 | 
			
		||||
	table.Render()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var capsulInstance string
 | 
			
		||||
var capsulType string
 | 
			
		||||
var capsulImage string
 | 
			
		||||
var capsulSSHKey string
 | 
			
		||||
var capsulAPIToken string
 | 
			
		||||
var serverNewCapsulCommand = &cli.Command{
 | 
			
		||||
	Name:      "capsul",
 | 
			
		||||
	Usage:     "Create a new Capsul virtual server",
 | 
			
		||||
	ArgsUsage: "<name>",
 | 
			
		||||
	Description: `
 | 
			
		||||
Create a new Capsul virtual server.
 | 
			
		||||
func newCapsulVPS(c *cli.Context) error {
 | 
			
		||||
	if err := internal.EnsureNewCapsulVPSFlags(c); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
This command uses the uses the Capsul API bindings of your chosen instance to
 | 
			
		||||
send a server creation request. You must already have an account on your chosen
 | 
			
		||||
Capsul instance before using this command.
 | 
			
		||||
	capsulCreateURL := fmt.Sprintf("https://%s/api/capsul/create", internal.CapsulInstanceURL)
 | 
			
		||||
 | 
			
		||||
Your token can be loaded from the environment using the CAPSUL_TOKEN
 | 
			
		||||
environment variable or otherwise passing the "--token/-T" flag.
 | 
			
		||||
`,
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:        "instance",
 | 
			
		||||
			Aliases:     []string{"I"},
 | 
			
		||||
			Usage:       "Capsul instance",
 | 
			
		||||
			Destination: &capsulInstance,
 | 
			
		||||
			Value:       "yolo.servers.coop",
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:        "type",
 | 
			
		||||
			Aliases:     []string{"t"},
 | 
			
		||||
			Usage:       "Server type",
 | 
			
		||||
			Value:       "f1-xs",
 | 
			
		||||
			Destination: &capsulType,
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:        "image",
 | 
			
		||||
			Aliases:     []string{"i"},
 | 
			
		||||
			Usage:       "Image type",
 | 
			
		||||
			Value:       "debian10",
 | 
			
		||||
			Destination: &capsulImage,
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:        "ssh-key",
 | 
			
		||||
			Aliases:     []string{"s"},
 | 
			
		||||
			Usage:       "SSH key",
 | 
			
		||||
			Value:       "",
 | 
			
		||||
			Destination: &capsulSSHKey,
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:        "token",
 | 
			
		||||
			Aliases:     []string{"T"},
 | 
			
		||||
			Usage:       "Capsul instance API token",
 | 
			
		||||
			EnvVars:     []string{"CAPSUL_TOKEN"},
 | 
			
		||||
			Destination: &capsulAPIToken,
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		capsulName := c.Args().First()
 | 
			
		||||
		if capsulName == "" {
 | 
			
		||||
			internal.ShowSubcommandHelpAndError(c, errors.New("no name provided"))
 | 
			
		||||
		}
 | 
			
		||||
	var sshKeys []string
 | 
			
		||||
	for _, sshKey := range c.StringSlice("capsul-ssh-keys") {
 | 
			
		||||
		sshKeys = append(sshKeys, sshKey)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		if capsulAPIToken == "" {
 | 
			
		||||
			logrus.Fatal("Capsul API token is missing")
 | 
			
		||||
		}
 | 
			
		||||
	tableColumns := []string{"instance", "name", "type", "image", "ssh-keys"}
 | 
			
		||||
	table := formatter.CreateTable(tableColumns)
 | 
			
		||||
	table.Append([]string{
 | 
			
		||||
		internal.CapsulInstanceURL,
 | 
			
		||||
		internal.CapsulName,
 | 
			
		||||
		internal.CapsulType,
 | 
			
		||||
		internal.CapsulImage,
 | 
			
		||||
		strings.Join(sshKeys, "\n"),
 | 
			
		||||
	})
 | 
			
		||||
	table.Render()
 | 
			
		||||
 | 
			
		||||
		capsulCreateURL := fmt.Sprintf("https://%s/api/capsul/create", capsulInstance)
 | 
			
		||||
	response := false
 | 
			
		||||
	prompt := &survey.Confirm{
 | 
			
		||||
		Message: "continue with capsul creation?",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		capsulClient := libcapsul.New(capsulCreateURL, capsulAPIToken)
 | 
			
		||||
		resp, err := capsulClient.Create(capsulName, capsulType, capsulImage, capsulSSHKey)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	if err := survey.AskOne(prompt, &response); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		tableColumns := []string{"Name", "ID"}
 | 
			
		||||
		table := formatter.CreateTable(tableColumns)
 | 
			
		||||
		table.Append([]string{capsulName, resp.ID})
 | 
			
		||||
		table.Render()
 | 
			
		||||
	if !response {
 | 
			
		||||
		logrus.Fatal("exiting as requested")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
	capsulClient := libcapsul.New(capsulCreateURL, internal.CapsulAPIToken)
 | 
			
		||||
	resp, err := capsulClient.Create(
 | 
			
		||||
		internal.CapsulName,
 | 
			
		||||
		internal.CapsulType,
 | 
			
		||||
		internal.CapsulImage,
 | 
			
		||||
		sshKeys,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tableColumns = []string{"Name", "ID"}
 | 
			
		||||
	table = formatter.CreateTable(tableColumns)
 | 
			
		||||
	table.Append([]string{internal.CapsulName, resp.ID})
 | 
			
		||||
	table.SetCaption(true, "capsul creation response")
 | 
			
		||||
	table.Render()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var serverNewCommand = &cli.Command{
 | 
			
		||||
@ -209,12 +150,52 @@ var serverNewCommand = &cli.Command{
 | 
			
		||||
	Aliases: []string{"n"},
 | 
			
		||||
	Usage:   "Create a new server using a 3rd party provider",
 | 
			
		||||
	Description: `
 | 
			
		||||
Use a provider plugin to create a new server which can then be used to house a
 | 
			
		||||
new Co-op Cloud installation.
 | 
			
		||||
This command creates a new server via a 3rd party provider.
 | 
			
		||||
 | 
			
		||||
The following providers are supported:
 | 
			
		||||
 | 
			
		||||
		Capsul https://git.cyberia.club/Cyberia/capsul-flask
 | 
			
		||||
    Hetzner Cloud https://docs.hetzner.com/cloud
 | 
			
		||||
 | 
			
		||||
You may invoke this command in "wizard" mode and be prompted for input:
 | 
			
		||||
 | 
			
		||||
    abra record new
 | 
			
		||||
`,
 | 
			
		||||
	ArgsUsage: "<provider>",
 | 
			
		||||
	Subcommands: []*cli.Command{
 | 
			
		||||
		serverNewHetznerCloudCommand,
 | 
			
		||||
		serverNewCapsulCommand,
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		internal.ServerProviderFlag,
 | 
			
		||||
 | 
			
		||||
		// Capsul
 | 
			
		||||
		internal.CapsulInstanceURLFlag,
 | 
			
		||||
		internal.CapsulTypeFlag,
 | 
			
		||||
		internal.CapsulImageFlag,
 | 
			
		||||
		internal.CapsulSSHKeysFlag,
 | 
			
		||||
		internal.CapsulAPITokenFlag,
 | 
			
		||||
 | 
			
		||||
		// Hetzner
 | 
			
		||||
		internal.HetznerCloudNameFlag,
 | 
			
		||||
		internal.HetznerCloudTypeFlag,
 | 
			
		||||
		internal.HetznerCloudImageFlag,
 | 
			
		||||
		internal.HetznerCloudSSHKeysFlag,
 | 
			
		||||
		internal.HetznerCloudLocationFlag,
 | 
			
		||||
		internal.HetznerCloudAPITokenFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		if err := internal.EnsureServerProvider(); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch internal.ServerProvider {
 | 
			
		||||
		case "capsul":
 | 
			
		||||
			if err := newCapsulVPS(c); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		case "hetzner-cloud":
 | 
			
		||||
			if err := newHetznerCloudVPS(c); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@ -25,7 +25,7 @@ require (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	coopcloud.tech/libcapsul v0.0.0-20211020153234-f1386b5cf79d
 | 
			
		||||
	coopcloud.tech/libcapsul v0.0.0-20211022074848-c35e78fe3f3e
 | 
			
		||||
	github.com/Microsoft/hcsshim v0.8.21 // indirect
 | 
			
		||||
	github.com/containerd/containerd v1.5.5 // indirect
 | 
			
		||||
	github.com/docker/docker-credential-helpers v0.6.4 // indirect
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							@ -21,8 +21,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
 | 
			
		||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 | 
			
		||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 | 
			
		||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 | 
			
		||||
coopcloud.tech/libcapsul v0.0.0-20211020153234-f1386b5cf79d h1:5A69AFx2BP5J43Y9SaB9LlAIMLr2SWqbzfgjUh8sgKM=
 | 
			
		||||
coopcloud.tech/libcapsul v0.0.0-20211020153234-f1386b5cf79d/go.mod h1:HEQ9pSJRsDKabMxPfYCCzpVpAreLoC4Gh4SkVyOaKvk=
 | 
			
		||||
coopcloud.tech/libcapsul v0.0.0-20211022074848-c35e78fe3f3e h1:o5OZInc5b9esiN4hlfjZY6u0r+qB2iSv/11jnMGuR38=
 | 
			
		||||
coopcloud.tech/libcapsul v0.0.0-20211022074848-c35e78fe3f3e/go.mod h1:HEQ9pSJRsDKabMxPfYCCzpVpAreLoC4Gh4SkVyOaKvk=
 | 
			
		||||
coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb h1:Jf+Dnna2kXcNQvcA5JMp6d2Uyvg2pIVJfip9+X5FrH0=
 | 
			
		||||
coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
 | 
			
		||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user