253 lines
6.5 KiB
Go
253 lines
6.5 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"time"
|
|
|
|
"coopcloud.tech/abra/cli/formatter"
|
|
"coopcloud.tech/abra/cli/internal"
|
|
"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.
|
|
|
|
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.
|
|
|
|
Your token can be loaded from the environment using the HCLOUD_TOKEN
|
|
environment variable or otherwise passing the "--env/-e" 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, cannot continue")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
client := hcloud.NewClient(hcloud.WithToken(hetznerCloudAPIToken))
|
|
|
|
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 {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
tableColumns := []string{"Name", "IPv4", "Root Password"}
|
|
table := formatter.CreateTable(tableColumns)
|
|
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})
|
|
}
|
|
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.
|
|
|
|
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.
|
|
|
|
Your token can be loaded from the environment using the CAPSUL_TOKEN
|
|
environment variable or otherwise passing the "--env/-e" 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 {
|
|
name := c.Args().First()
|
|
if name == "" {
|
|
internal.ShowSubcommandHelpAndError(c, errors.New("no name provided"))
|
|
}
|
|
|
|
if capsulAPIToken == "" {
|
|
logrus.Fatal("Capsul API token is missing, cannot continue")
|
|
}
|
|
|
|
// yep, the response time is quite slow, something to fix Capsul side
|
|
client := &http.Client{Timeout: 20 * time.Second}
|
|
|
|
capsulCreateURL := fmt.Sprintf("https://%s/api/capsul/create", capsulInstance)
|
|
values := map[string]string{
|
|
"name": name,
|
|
"size": capsulType,
|
|
"os": capsulImage,
|
|
"ssh_key_0": capsulSSHKey,
|
|
}
|
|
|
|
payload, err := json.Marshal(values)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", capsulCreateURL, bytes.NewBuffer(payload))
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
req.Header = http.Header{
|
|
"Content-Type": []string{"application/json"},
|
|
"Authorization": []string{capsulAPIToken},
|
|
}
|
|
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
body, err := ioutil.ReadAll(res.Body)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
logrus.Fatal(string(body))
|
|
}
|
|
|
|
type capsulCreateResponse struct{ ID string }
|
|
var resp capsulCreateResponse
|
|
if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
tableColumns := []string{"Name", "ID"}
|
|
table := formatter.CreateTable(tableColumns)
|
|
table.Append([]string{name, resp.ID})
|
|
table.Render()
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var serverNewCommand = &cli.Command{
|
|
Name: "new",
|
|
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.",
|
|
ArgsUsage: "<provider>",
|
|
Subcommands: []*cli.Command{
|
|
serverNewHetznerCloudCommand,
|
|
serverNewCapsulCommand,
|
|
},
|
|
}
|