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: "", 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") } 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 { logrus.Fatal(err) } logrus.Debugf("new server '%s' created", name) 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: "", 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") } // yep, the response time is quite slow, something to fix on the Capsul side client := &http.Client{Timeout: 20 * time.Second} capsulCreateURL := fmt.Sprintf("https://%s/api/capsul/create", capsulInstance) logrus.Debugf("using '%s' as capsul create url", capsulCreateURL) 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) } logrus.Debugf("capsul created with ID: '%s'", resp.ID) tableColumns := []string{"Name", "ID"} table := formatter.CreateTable(tableColumns) table.Append([]string{name, resp.ID}) table.Render() return nil }, } var serverNewCommand = &cli.Command{ Name: "new", 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. `, ArgsUsage: "", Subcommands: []*cli.Command{ serverNewHetznerCloudCommand, serverNewCapsulCommand, }, }