package cli import ( "context" "fmt" "net" "strings" "coopcloud.tech/abra/client" "coopcloud.tech/abra/config" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) var serverListCommand = &cli.Command{ Name: "list", Aliases: []string{"ls"}, Usage: "List locally-defined servers.", ArgsUsage: emptyArgsUsage, HideHelp: true, Action: func(c *cli.Context) error { dockerContextStore := client.NewDefaultDockerContextStore() contexts, err := dockerContextStore.Store.List() if err != nil { logrus.Fatal(err) } tableColumns := []string{"Name", "Connection"} table := createTable(tableColumns) defer table.Render() serverNames, err := config.ReadServerNames() if err != nil { logrus.Fatal(err) } for _, serverName := range serverNames { var row []string for _, ctx := range contexts { endpoint, err := client.GetContextEndpoint(ctx) if err != nil && strings.Contains(err.Error(), "does not exist") { // No local context found, we can continue safely continue } if ctx.Name == serverName { row = []string{serverName, endpoint} } } if len(row) == 0 { row = []string{serverName, "UNKNOWN"} } table.Append(row) } return nil }, } var serverAddCommand = &cli.Command{ Name: "add", Usage: "Add a new server, reachable on .", ArgsUsage: " [] []", Description: "[], [] SSH connection details", Action: func(c *cli.Context) error { arg_len := c.Args().Len() args := c.Args().Slice() if arg_len < 3 { args = append(args, make([]string, 3-arg_len)...) } if err := client.CreateContext(args[0], args[1], args[2]); err != nil { logrus.Fatal(err) } fmt.Println(args[0]) return nil }, } 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_API_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_API_TOKEN"}, Destination: &HetznerCloudAPIToken, }, }, Action: func(c *cli.Context) error { name := c.Args().First() if name == "" { cli.ShowSubcommandHelp(c) return nil } ctx := context.Background() client := hcloud.NewClient(hcloud.WithToken(HetznerCloudAPIToken)) // var sshkeys []hcloud.SSHKey // for _, sshkey := range HetznerCloudSSHKeys { // sshkeys = append(sshkeys, hcloud.SSHKey{Name: sshkey}) // } // TODO: finish passing arguments serverOpts := hcloud.ServerCreateOpts{ Name: name, ServerType: &hcloud.ServerType{Name: HetznerCloudType}, Image: &hcloud.Image{Name: HetznerCloudImage}, // SSHKeys: HetznerCloudSSHKeys, // Location: HetznerCloudLocation, } _, _, err := client.Server.Create(ctx, serverOpts) if err != nil { logrus.Fatal(err) } 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: "", Subcommands: []*cli.Command{ serverNewHetznerCloudCommand, }, } var serverRemoveCommand = &cli.Command{ Name: "remove", Aliases: []string{"rm", "delete"}, Usage: "Remove a locally-defined server", HideHelp: true, Action: func(c *cli.Context) error { server := c.Args().First() if server == "" { cli.ShowSubcommandHelp(c) return nil } if err := client.DeleteContext(server); err != nil { logrus.Fatal(err) } return nil }, } var serverInitCommand = &cli.Command{ Name: "init", Usage: "Initialise server for deploying apps", HideHelp: true, ArgsUsage: "", Description: ` Initialise swarm mode on the target . This initialisation explicitly chooses the "single host swarm" mode which uses the default IPv4 address as the advertising address. This can be re-configured later for more advanced use cases. `, Action: func(c *cli.Context) error { host := c.Args().First() if host == "" { cli.ShowSubcommandHelp(c) return nil } cl, err := client.NewClientWithContext(host) if err != nil { return err } var ipv4 net.IP ips, _ := net.LookupIP(host) for _, ip := range ips { ipv4 = ip.To4() } if string(ipv4) == "" { return fmt.Errorf("unable to retrieve ipv4 address for %s", host) } ctx := context.Background() initReq := swarm.InitRequest{ ListenAddr: "0.0.0.0:2377", AdvertiseAddr: string(ipv4), } if _, err := cl.SwarmInit(ctx, initReq); err != nil { return err } netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"} if _, err := cl.NetworkCreate(ctx, "proxy", netOpts); err != nil { return err } return nil }, } // Reminder: The list commands are in is the order they appear in the help menu var ServerCommand = &cli.Command{ Name: "server", ArgsUsage: "", Usage: "Manage the servers that host your apps", Description: ` Manage the lifecycle of a server. These commands support creating new servers using 3rd party integrations, initialising existing servers to support Co-op Cloud deployments and managing the connections to those servers. `, Subcommands: []*cli.Command{ serverNewCommand, serverInitCommand, serverAddCommand, serverListCommand, serverRemoveCommand, }, }