refactor!: abra server interface more coherent
This follows our app new UX and interactive mode design.
This commit is contained in:
parent
94c7f59113
commit
313e3beb1e
|
@ -132,3 +132,130 @@ var DNSPriorityFlag = &cli.IntFlag{
|
||||||
Usage: "Domain name priority value",
|
Usage: "Domain name priority value",
|
||||||
Destination: &DNSPriority,
|
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,
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RecordCreateCommand lists domains.
|
// RecordNewCommand creates a new domain name record.
|
||||||
var RecordCreateCommand = &cli.Command{
|
var RecordNewCommand = &cli.Command{
|
||||||
Name: "new",
|
Name: "new",
|
||||||
Usage: "Create a new domain record",
|
Usage: "Create a new domain record",
|
||||||
Aliases: []string{"n"},
|
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
|
provide an API token on the Abra command-line when using these commands so that
|
||||||
you can authenticate with your provider account.
|
you can authenticate with your provider account.
|
||||||
|
|
||||||
Any new provider can be integrated, we welcome change sets. See the underlying
|
New providers can be integrated, we welcome change sets. See the underlying DNS
|
||||||
DNS library documentation for more. It supports many existing providers and
|
library documentation for more. It supports many existing providers and allows
|
||||||
allows to implement new provider support easily.
|
to implement new provider support easily.
|
||||||
|
|
||||||
https://pkg.go.dev/github.com/libdns/libdns
|
https://pkg.go.dev/github.com/libdns/libdns
|
||||||
|
|
||||||
`,
|
`,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
RecordListCommand,
|
RecordListCommand,
|
||||||
RecordCreateCommand,
|
RecordNewCommand,
|
||||||
RecordRemoveCommand,
|
RecordRemoveCommand,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,207 +1,148 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/formatter"
|
"coopcloud.tech/abra/cli/formatter"
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/libcapsul"
|
"coopcloud.tech/libcapsul"
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var hetznerCloudType string
|
func newHetznerCloudVPS(c *cli.Context) error {
|
||||||
var hetznerCloudImage string
|
if err := internal.EnsureNewHetznerCloudVPSFlags(c); err != nil {
|
||||||
var hetznerCloudSSHKeys cli.StringSlice
|
return err
|
||||||
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.
|
|
||||||
|
|
||||||
This command uses the uses the Hetzner Cloud API bindings to send a server
|
client := hcloud.NewClient(hcloud.WithToken(internal.HetznerCloudAPIToken))
|
||||||
creation request. You must already have a Hetzner Cloud account and an account
|
|
||||||
API token before using this command.
|
|
||||||
|
|
||||||
Your token can be loaded from the environment using the HCLOUD_TOKEN
|
var sshKeysRaw []string
|
||||||
environment variable or otherwise passing the "--token/-T" flag.
|
var sshKeys []*hcloud.SSHKey
|
||||||
`,
|
for _, sshKey := range c.StringSlice("ssh-keys") {
|
||||||
Flags: []cli.Flag{
|
sshKey, _, err := client.SSHKey.GetByName(c.Context, sshKey)
|
||||||
&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)
|
|
||||||
if err != nil {
|
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"}
|
tableColumns := []string{"name", "type", "image", "ssh-keys", "location"}
|
||||||
table := formatter.CreateTable(tableColumns)
|
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 {
|
response := false
|
||||||
table.Append([]string{name, res.Server.PublicNet.IPv4.IP.String(), "N/A (using SSH keys)"})
|
prompt := &survey.Confirm{
|
||||||
} else {
|
Message: "continue with capsul creation?",
|
||||||
table.Append([]string{name, res.Server.PublicNet.IPv4.IP.String(), res.RootPassword})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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
|
func newCapsulVPS(c *cli.Context) error {
|
||||||
var capsulType string
|
if err := internal.EnsureNewCapsulVPSFlags(c); err != nil {
|
||||||
var capsulImage string
|
return err
|
||||||
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.
|
|
||||||
|
|
||||||
This command uses the uses the Capsul API bindings of your chosen instance to
|
capsulCreateURL := fmt.Sprintf("https://%s/api/capsul/create", internal.CapsulInstanceURL)
|
||||||
send a server creation request. You must already have an account on your chosen
|
|
||||||
Capsul instance before using this command.
|
|
||||||
|
|
||||||
Your token can be loaded from the environment using the CAPSUL_TOKEN
|
var sshKeys []string
|
||||||
environment variable or otherwise passing the "--token/-T" flag.
|
for _, sshKey := range c.StringSlice("capsul-ssh-keys") {
|
||||||
`,
|
sshKeys = append(sshKeys, sshKey)
|
||||||
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"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if capsulAPIToken == "" {
|
tableColumns := []string{"instance", "name", "type", "image", "ssh-keys"}
|
||||||
logrus.Fatal("Capsul API token is missing")
|
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)
|
if err := survey.AskOne(prompt, &response); err != nil {
|
||||||
resp, err := capsulClient.Create(capsulName, capsulType, capsulImage, capsulSSHKey)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tableColumns := []string{"Name", "ID"}
|
if !response {
|
||||||
table := formatter.CreateTable(tableColumns)
|
logrus.Fatal("exiting as requested")
|
||||||
table.Append([]string{capsulName, resp.ID})
|
}
|
||||||
table.Render()
|
|
||||||
|
|
||||||
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{
|
var serverNewCommand = &cli.Command{
|
||||||
|
@ -209,12 +150,52 @@ var serverNewCommand = &cli.Command{
|
||||||
Aliases: []string{"n"},
|
Aliases: []string{"n"},
|
||||||
Usage: "Create a new server using a 3rd party provider",
|
Usage: "Create a new server using a 3rd party provider",
|
||||||
Description: `
|
Description: `
|
||||||
Use a provider plugin to create a new server which can then be used to house a
|
This command creates a new server via a 3rd party provider.
|
||||||
new Co-op Cloud installation.
|
|
||||||
|
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>",
|
ArgsUsage: "<provider>",
|
||||||
Subcommands: []*cli.Command{
|
Flags: []cli.Flag{
|
||||||
serverNewHetznerCloudCommand,
|
internal.ServerProviderFlag,
|
||||||
serverNewCapsulCommand,
|
|
||||||
|
// 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 (
|
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/Microsoft/hcsshim v0.8.21 // indirect
|
||||||
github.com/containerd/containerd v1.5.5 // indirect
|
github.com/containerd/containerd v1.5.5 // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.6.4 // 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.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.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
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-20211022074848-c35e78fe3f3e h1:o5OZInc5b9esiN4hlfjZY6u0r+qB2iSv/11jnMGuR38=
|
||||||
coopcloud.tech/libcapsul v0.0.0-20211020153234-f1386b5cf79d/go.mod h1:HEQ9pSJRsDKabMxPfYCCzpVpAreLoC4Gh4SkVyOaKvk=
|
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 h1:Jf+Dnna2kXcNQvcA5JMp6d2Uyvg2pIVJfip9+X5FrH0=
|
||||||
coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
|
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=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
|
Loading…
Reference in New Issue