From f03f76bc082a245f852dcd0cc52fef9ff48cdc93 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Mon, 1 Jan 2024 15:33:08 -0600 Subject: [PATCH] initial commit --- .gitignore | 1 + api.go | 36 ++++++ args.go | 33 ++++++ create.go | 293 ++++++++++++++++++++++++++++++++++++++++++++++++ droplet.go | 266 +++++++++++++++++++++++++++++++++++++++++++ json.go | 24 ++++ listKeys.go | 39 +++++++ main.go | 78 +++++++++++++ pollDroplets.go | 50 +++++++++ poweron.go | 82 ++++++++++++++ structs.go | 91 +++++++++++++++ xterm.go | 31 +++++ 12 files changed, 1024 insertions(+) create mode 100644 .gitignore create mode 100644 api.go create mode 100644 args.go create mode 100644 create.go create mode 100644 droplet.go create mode 100644 json.go create mode 100644 listKeys.go create mode 100644 main.go create mode 100644 pollDroplets.go create mode 100644 poweron.go create mode 100644 structs.go create mode 100644 xterm.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1377554 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/api.go b/api.go new file mode 100644 index 0000000..b1568b1 --- /dev/null +++ b/api.go @@ -0,0 +1,36 @@ +package digitalocean + +import ( + "context" + + "golang.org/x/oauth2" + "github.com/digitalocean/godo" + + "go.wit.com/log" +) + +func (d *DigitalOcean) listRegions() []godo.Region { + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: d.token}) + oauthClient := oauth2.NewClient(context.Background(), tokenSource) + client := godo.NewClient(oauthClient) + + ctx := context.TODO() + + // Retrieve all regions. + regions, _, err := client.Regions.List(ctx, &godo.ListOptions{}) + if err != nil { + d.err = err + log.Warn(err, "digitalocean.listRegions() failed") + return nil + } + + /* + // Print details of each region. + fmt.Println("Available Regions:") + for _, region := range regions { + fmt.Printf("Slug: %s, Name: %s, Available: %v\n", region.Slug, region.Name, region.Available) + } + */ + + return regions +} diff --git a/args.go b/args.go new file mode 100644 index 0000000..7a9afd8 --- /dev/null +++ b/args.go @@ -0,0 +1,33 @@ +package digitalocean + +// initializes logging and command line options + +import ( + arg "github.com/alexflint/go-arg" + log "go.wit.com/log" +) + +var INFO log.LogFlag +var POLL log.LogFlag +var argDo ArgsDo + +// This struct can be used with the go-arg package +type ArgsDo struct { + DigitalOceanTimer int `arg:"--digitalocean-poll-interval" help:"how often to poll droplet status (default 60 seconds)"` +} + +func init() { + arg.Register(&argDo) + + INFO.B = false + INFO.Name = "INFO" + INFO.Subsystem = "digitalocean" + INFO.Desc = "Enable log.Info()" + INFO.Register() + + POLL.B = false + POLL.Name = "POLL" + POLL.Subsystem = "digitalocean" + POLL.Desc = "Show droplet status polling" + POLL.Register() +} diff --git a/create.go b/create.go new file mode 100644 index 0000000..dfd12e3 --- /dev/null +++ b/create.go @@ -0,0 +1,293 @@ +package digitalocean + +import ( + "context" + "strings" + "golang.org/x/oauth2" + "github.com/digitalocean/godo" + + "go.wit.com/log" + "go.wit.com/gui/gadgets" + // "go.wit.com/gui" +) + +/* +// createDroplet creates a new droplet in the specified region with the given name. +func createDroplet(token, name, region, size, image string) (*godo.Droplet, error) { + // Create an OAuth2 token. + tokenSource := &oauth2.Token{ + AccessToken: token, + } + + // Create an OAuth2 client. + oauthClient := oauth2.NewClient(context.Background(), tokenSource) + + // Create a DigitalOcean client with the OAuth2 client. + client := godo.NewClient(oauthClient) + + // Define the create request. + createRequest := &godo.DropletCreateRequest{ + Name: name, + Region: region, + Size: size, + Image: godo.DropletCreateImage{ + Slug: image, + }, + } + + // Create the droplet. + ctx := context.TODO() + newDroplet, _, err := client.Droplets.Create(ctx, createRequest) + if err != nil { + return nil, err + } + + return newDroplet, nil +} +*/ + +func (d *DigitalOcean) Create(name string, region string, size string, image string) { + // Create a new droplet. + droplet, err := d.createDropletNew(name, region, size, image) + if err != nil { + log.Fatalf("digitalocean.Create() Something went wrong: %s\n", err) + } + + log.Infof("digitalocean.Create() droplet ID %d with name %s\n", droplet.ID, droplet.Name) +} + +// createDroplet creates a new droplet in the specified region with the given name. +func (d *DigitalOcean) createDropletNew(name, region, size, image string) (*godo.Droplet, error) { + log.Infof("digitalocean.createDropletNew() START name =", name) + // Create an OAuth2 token. + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: d.token}) + + // Create an OAuth2 client. + oauthClient := oauth2.NewClient(context.Background(), tokenSource) + + // Create a DigitalOcean client with the OAuth2 client. + client := godo.NewClient(oauthClient) + + var sshKeys []godo.DropletCreateSSHKey + log.Info("digitalocean.createDropletNew() about to get keys. client =", client) + + // Find the key by name. + for i, key := range d.sshKeys { + log.Info("found ssh i =", i, key.Name) + log.Verbose("found ssh key.Name =", key.Name) + log.Verbose("found ssh key.Fingerprint =", key.Fingerprint) + log.Verbose("found ssh key:", key) + /* + sshKeys = []godo.DropletCreateSSHKey{ + {ID: key.ID}, + } + */ + sshKeys = append(sshKeys, godo.DropletCreateSSHKey{ID: key.ID}) + } + + // Define the create request. + createRequest := &godo.DropletCreateRequest{ + Name: name, + Region: region, + Size: size, + Image: godo.DropletCreateImage{ + Slug: image, + }, + IPv6: true, // Enable IPv6 + SSHKeys: sshKeys, // Add SSH key IDs here + } + + // Create the droplet. + ctx := context.TODO() + log.Info("digitalocean.createDropletNew() about to do client.Create(). ctx =", ctx) + newDroplet, _, err := client.Droplets.Create(ctx, createRequest) + log.Infof("digitalocean.createDropletNew() END newDroplet =", newDroplet) + if err != nil { + return nil, err + } + + return newDroplet, nil +} + +var myCreate *windowCreate + +// This is initializes the main DO object +// You can only have one of these +func InitCreateWindow() *windowCreate { + if ! myDo.Ready() {return nil} + if myCreate != nil { + myCreate.Show() + return myCreate + } + myCreate = new(windowCreate) + myCreate.ready = false + + myCreate.window = myDo.parent.NewWindow("Create Droplet") + + // make a group label and a grid + myCreate.group = myCreate.window.NewGroup("droplets:").Pad() + myCreate.grid = myCreate.group.NewGrid("grid", 2, 1).Pad() + + myCreate.name = gadgets.NewBasicEntry(myCreate.grid, "Name").Set("test.wit.com") + + myCreate.region = gadgets.NewBasicDropdown(myCreate.grid, "Region") + + regions := myDo.listRegions() + + // Print details of each region. + log.Info("Available Regions:") + for i, region := range regions { + log.Infof("i: %d, Slug: %s, Name: %s, Available: %v\n", i, region.Slug, region.Name, region.Available) + log.Spew(i, region) + if len(region.Sizes) == 0 { + log.Info("Skipping region. No available sizes region =", region.Name) + } else { + s := region.Name + " (" + region.Slug + ")" + if (myCreate.regionSlug == "") { + myCreate.regionSlug = region.Slug + } + myCreate.region.Add(s) + } + } + + myCreate.region.Custom = func() { + s := myCreate.region.Get() + log.Info("create droplet region changed to:", s) + for _, region := range regions { + if s == region.Name { + log.Info("Found region! slug =", myCreate.regionSlug, region) + myCreate.regionSelected = region + log.Info("Found region! Now update all the sizes count =", len(region.Sizes)) + for _, size := range region.Sizes { + log.Info("Size: ", size) + } + } + } + } + + myCreate.size = gadgets.NewBasicCombobox(myCreate.grid, "Size") + myCreate.size.Add("s-1vcpu-1gb") + myCreate.size.Add("s-1vcpu-1gb-amd") + myCreate.size.Add("s-1vcpu-1gb-intel") + myCreate.size.Add("s-2vcpu-4gb-120gb-intel") + myCreate.size.Set("s-2vcpu-4gb-120gb-intel") + myCreate.size.Custom = func() { + size := myCreate.size.Get() + log.Info("Create() need to verify size exists in region. Digital Ocean size.Slug =", size) + } + + myCreate.memory = gadgets.NewBasicDropdown(myCreate.grid, "Memory") + myCreate.memory.Add("1 GB") + myCreate.memory.Add("2 GB") + myCreate.memory.Add("4 GB") + myCreate.memory.Add("8 GB") + myCreate.memory.Add("16 GB") + myCreate.memory.Add("32 GB") + myCreate.memory.Add("64 GB") + myCreate.memory.Add("96 GB") + myCreate.memory.Add("128 GB") + myCreate.memory.Add("256 GB") + myCreate.memory.Custom = func() { + for _, size := range myCreate.regionSelected.Sizes { + log.Info("Size: ", size) + } + myCreate.UpdateSize() + } + + myCreate.image = gadgets.NewBasicCombobox(myCreate.grid, "Image") + myCreate.image.Add("debian-12-x64") + myCreate.image.Add("ubuntu-20-04-x64") + myCreate.image.Set("debian-12-x64") + + // myCreate.nvme = gadgets.NewBasicCheckbox(myCreate.grid, "NVMe") + + myCreate.group.NewLabel("Create Droplet") + + // box := myCreate.group.NewBox("vBox", false).Pad() + box := myCreate.group.NewBox("hBox", true).Pad() + box.NewButton("Cancel", func () { + myCreate.Hide() + }) + box.NewButton("Create", func () { + name := myCreate.name.Get() + size := myCreate.size.Get() + region := myCreate.regionSlug + image := myCreate.image.Get() + if (region == "") { + log.Info("Create() droplet name =", name, "region =", region, "size =", size, "image", image) + log.Info("Create() region lookup failed") + return + } + log.Info("Create() droplet name =", name, "region =", region, "size =", size, "image", image) + myDo.Create(name, region, size, image) + myCreate.Hide() + }) + + myCreate.ready = true + myDo.create = myCreate + return myCreate +} + +// Find the size +func (d *windowCreate) UpdateSize() { + if ! d.Ready() {return} + log.Info("Now find the size. sizes count =", len(myCreate.regionSelected.Sizes)) + var s string + m := myCreate.memory.Get() + switch m { + case "1 GB": + s = "cpu-1gb-" + case "2 GB": + s = "cpu-2gb-" + case "4 GB": + s = "cpu-4gb-" + case "8 GB": + s = "cpu-8gb-" + case "16 GB": + s = "cpu-16gb-" + case "32 GB": + s = "cpu-32gb-" + case "64 GB": + s = "cpu-64gb-" + case "96 GB": + s = "cpu-96gb-" + case "128 GB": + s = "cpu-128gb-" + case "256 GB": + s = "cpu-256gb-" + default: + s = "cpu-4gb-" + } + for _, size := range myCreate.regionSelected.Sizes { + if strings.Contains(size, s) { + log.Info("Found Size! size.Slug =", size, "contains", s) + myCreate.size.Set(size) + return + } + } + log.Info("memory =", myCreate.memory.Get()) +} + +// Returns true if the status is valid +func (d *windowCreate) Ready() bool { + if d == nil {return false} + return d.ready +} + +func (d *windowCreate) Show() { + if ! d.Ready() {return} + log.Info("digitalocean.Show() window") + if d.hidden { + d.window.Show() + } + d.hidden = false +} + +func (d *windowCreate) Hide() { + if ! d.Ready() {return} + log.Info("digitalocean.Hide() window") + if ! d.hidden { + d.window.Hide() + } + d.hidden = true +} diff --git a/droplet.go b/droplet.go new file mode 100644 index 0000000..01913cc --- /dev/null +++ b/droplet.go @@ -0,0 +1,266 @@ +package digitalocean + +import ( + "errors" + "sort" + "strings" + "strconv" + "github.com/digitalocean/godo" + + "go.wit.com/log" + // "go.wit.com/gui" +) + +func (d *DigitalOcean) NewDroplet(dd *godo.Droplet) *Droplet { + if ! myDo.Ready() {return nil} + + // check if the droplet ID already exists + if (d.dropMap[dd.ID] != nil) { + log.Error(errors.New("droplet.NewDroplet() already exists")) + return d.dropMap[dd.ID] + } + + droplet := new(Droplet) + droplet.ready = false + droplet.poll = dd // the information polled from the digital ocean API + droplet.ID = dd.ID + droplet.image = dd.Image.Name + " (" + dd.Image.Slug + ")" + + if (d.dGrid == nil) { + d.dGrid = d.group.NewGrid("grid", 12, 1).Pad() + } + + droplet.nameN = d.dGrid.NewLabel(dd.Name) + + d.dGrid.NewLabel(dd.Region.Slug) + + var ipv4 []string + var ipv6 []string + for _, network := range dd.Networks.V4 { + if network.Type == "public" { + ipv4 = append(ipv4, network.IPAddress) + } + } + + for _, network := range dd.Networks.V6 { + if network.Type == "public" { + ipv6 = append(ipv6, network.IPAddress) + } + } + sort.Strings(ipv4) + sort.Strings(ipv6) + droplet.ipv4 = d.dGrid.NewLabel(strings.Join(ipv4, "\n")) + droplet.ipv6 = d.dGrid.NewLabel(strings.Join(ipv6, "\n")) + + droplet.sizeSlugN = d.dGrid.NewLabel(dd.SizeSlug) + droplet.imageN = d.dGrid.NewLabel(dd.Image.Slug) + droplet.statusN = d.dGrid.NewLabel(dd.Status) + + droplet.connect = d.dGrid.NewButton("Connect", func () { + droplet.Connect() + }) + + droplet.edit = d.dGrid.NewButton("Edit", func () { + droplet.Show() + }) + + droplet.poweroff = d.dGrid.NewButton("Power Off", func () { + droplet.PowerOff() + }) + + droplet.poweron = d.dGrid.NewButton("Power On", func () { + droplet.PowerOn() + }) + + droplet.destroy = d.dGrid.NewButton("Destroy", func () { + droplet.Destroy() + }) + + droplet.ready = true + return droplet +} + +func (d *Droplet) Active() bool { + if ! d.Ready() {return false} + log.Log(POLL, "droplet.Active() status: ", d.poll.Status, "d.statusN.GetText() =", d.statusN.GetText()) + if (d.statusN.GetText() == "active") { + return true + } + return false +} + +// Returns true if the droplet is finished installing +func (d *Droplet) Ready() bool { + if d == nil {return false} + return d.ready +} + +// Returns true if the droplet is running +func (d *Droplet) On() bool { + if ! d.Ready() {return false} + return true +} + +func (d *Droplet) HasIPv4() bool { + if ! d.Ready() {return false} + if d.ipv4.GetText() == "" { + return false + } + return true +} +func (d *Droplet) HasIPv6() bool { + if ! d.Ready() {return false} + if d.ipv6.GetText() == "" { + return false + } + return true +} + +func (d *Droplet) GetIPv4() string { + if ! d.Ready() {return ""} + return d.ipv4.GetText() +} + +func (d *Droplet) GetIPv6() string { + if ! d.Ready() {return ""} + log.Info("droplet GetIPv6 has: n.GetText()", d.ipv6.GetText()) + return d.ipv6.GetText() +} + +func (d *Droplet) Connect() { + if ! d.Ready() {return} + if d.HasIPv4() { + ipv4 := d.GetIPv4() + log.Info("droplet has IPv4 =", ipv4) + xterm("ssh root@" + ipv4) + return + } + if d.HasIPv6() { + ipv6 := d.GetIPv6() + log.Info("droplet has IPv6 =", ipv6) + xterm("ssh root@[" + ipv6 + "]") + return + } + log.Info("droplet.Connect() here", d.GetIPv4(), d.GetIPv6()) +} + +func (d *Droplet) Update(dpoll *godo.Droplet) { + if ! d.Exists() {return} + d.poll = dpoll + log.Log(POLL, "droplet", dpoll.Name, "dpoll.Status =", dpoll.Status) + log.Spew(dpoll) + d.statusN.SetText(dpoll.Status) + if d.Active() { + d.poweron.Disable() + d.destroy.Disable() + d.connect.Enable() + d.poweroff.Enable() + } else { + d.poweron.Enable() + d.destroy.Enable() + d.poweroff.Disable() + d.connect.Disable() + } +} + +func (d *Droplet) PowerOn() { + if ! d.Exists() {return} + log.Info("droplet.PowerOn() should do it here") + myDo.PowerOn(d.ID) +} + +func (d *Droplet) PowerOff() { + if ! d.Exists() {return} + log.Info("droplet.PowerOff() here") + myDo.PowerOff(d.ID) +} + +func (d *Droplet) Destroy() { + if ! d.Exists() {return} + log.Info("droplet.Destroy() ID =", d.ID, "Name =", d.nameN.GetText()) + myDo.deleteDroplet(d) +} + +/* +type Droplet struct { + ID int `json:"id,float64,omitempty"` + Name string `json:"name,omitempty"` + Memory int `json:"memory,omitempty"` + Vcpus int `json:"vcpus,omitempty"` + Disk int `json:"disk,omitempty"` + Region *Region `json:"region,omitempty"` + Image *Image `json:"image,omitempty"` + Size *Size `json:"size,omitempty"` + SizeSlug string `json:"size_slug,omitempty"` + BackupIDs []int `json:"backup_ids,omitempty"` + NextBackupWindow *BackupWindow `json:"next_backup_window,omitempty"` + SnapshotIDs []int `json:"snapshot_ids,omitempty"` + Features []string `json:"features,omitempty"` + Locked bool `json:"locked,bool,omitempty"` + Status string `json:"status,omitempty"` + Networks *Networks `json:"networks,omitempty"` + Created string `json:"created_at,omitempty"` + Kernel *Kernel `json:"kernel,omitempty"` + Tags []string `json:"tags,omitempty"` + VolumeIDs []string `json:"volume_ids"` + VPCUUID string `json:"vpc_uuid,omitempty"` +} +*/ +func (d *Droplet) Show() { + if ! d.Exists() {return} + log.Info("droplet: ID =", d.ID) + log.Info("droplet: Name =", d.GetName()) + log.Info("droplet: Size =", d.GetSize()) + log.Info("droplet: Memory =", d.GetMemory()) + log.Info("droplet: Disk =", d.GetDisk()) + log.Info("droplet: Image =", d.GetImage()) + log.Info("droplet: Status =", d.GetStatus()) + log.Info("droplet: ", d.poll.Name, d.poll.Image.Slug, d.poll.Region.Slug) + log.Spew(d.poll) +} + +func (d *Droplet) Hide() { + if ! d.Exists() {return} + log.Info("droplet.Hide() window") + if ! d.hidden { + // d.window.Hide() + } + d.hidden = true +} + +func (d *Droplet) Exists() bool { + if ! myDo.Ready() {return false} + if d == nil {return false} + if d.poll == nil {return false} + return d.ready +} + +func (d *Droplet) GetName() string { + if ! d.Ready() {return ""} + return d.nameN.GetText() +} + +func (d *Droplet) GetSize() string { + if ! d.Ready() {return ""} + return d.sizeSlugN.GetText() +} + +func (d *Droplet) GetMemory() string { + if ! d.Ready() {return ""} + return strconv.Itoa(d.memory) +} + +func (d *Droplet) GetDisk() string { + if ! d.Ready() {return ""} + return strconv.Itoa(d.disk) +} + +func (d *Droplet) GetImage() string { + if ! d.Ready() {return ""} + return d.imageN.GetText() +} + +func (d *Droplet) GetStatus() string { + if ! d.Ready() {return ""} + return d.statusN.GetText() +} diff --git a/json.go b/json.go new file mode 100644 index 0000000..c0aa1a8 --- /dev/null +++ b/json.go @@ -0,0 +1,24 @@ +package digitalocean + +import ( + "encoding/json" +) + +// formatJSON takes an unformatted JSON string and returns a formatted version. +func FormatJSON(unformattedJSON string) (string, error) { + var jsonData interface{} + + // Decode the JSON string into an interface + err := json.Unmarshal([]byte(unformattedJSON), &jsonData) + if err != nil { + return "", err + } + + // Re-encode the JSON with indentation for formatting + formattedJSON, err := json.MarshalIndent(jsonData, "", " ") + if err != nil { + return "", err + } + + return string(formattedJSON), nil +} diff --git a/listKeys.go b/listKeys.go new file mode 100644 index 0000000..4b7da29 --- /dev/null +++ b/listKeys.go @@ -0,0 +1,39 @@ +package digitalocean + +import ( + "context" + "golang.org/x/oauth2" + "github.com/digitalocean/godo" + + "go.wit.com/log" +) + +// func (d *DigitalOcean) ListDroplets() bool { +func (d *DigitalOcean) ListSSHKeyID() error { + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: d.token}) + oauthClient := oauth2.NewClient(context.Background(), tokenSource) + client := godo.NewClient(oauthClient) + + // List all keys. + keys, _, err := client.Keys.List(context.Background(), &godo.ListOptions{}) + if err != nil { + return err + } + + d.sshKeys = keys + + // Find the key by name. + for _, key := range keys { + log.Log(POLL, "found ssh wierd", key.Name) + log.Log(POLL, "found ssh key:", key) + } + /* + sshKeys := []godo.DropletCreateSSHKey{ + {ID: 22994569}, + {ID: 333}, + } + */ + + // return fmt.Errorf("SSH Key not found") + return nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..2b62d12 --- /dev/null +++ b/main.go @@ -0,0 +1,78 @@ +package digitalocean + +import ( + "os" + "go.wit.com/log" + "go.wit.com/gui/gui" +) + +var myDo *DigitalOcean + +// This is initializes the main DO object +// You can only have one of these +func New(p *gui.Node) *DigitalOcean { + if myDo != nil {return myDo} + myDo = new(DigitalOcean) + myDo.ready = false + myDo.parent = p + + myDo.dropMap = make(map[int]*Droplet) + + // Your personal API token from DigitalOcean. + myDo.token = os.Getenv("DIGITALOCEAN_TOKEN") + + myDo.window = p.NewWindow("DigitalOcean Control Panel") + + // make a group label and a grid + myDo.group = myDo.window.NewGroup("droplets:").Pad() + myDo.grid = myDo.group.NewGrid("grid", 2, 1).Pad() + + myDo.ready = true + myDo.Hide() + return myDo +} + +// Returns true if the status is valid +func (d *DigitalOcean) Ready() bool { + if d == nil {return false} + return d.ready +} + +func (d *DigitalOcean) Show() { + if ! d.Ready() {return} + log.Info("digitalocean.Show() window") + if d.hidden { + d.window.Show() + } + d.hidden = false +} + +func (d *DigitalOcean) Hide() { + if ! d.Ready() {return} + log.Info("digitalocean.Hide() window") + if ! d.hidden { + d.window.Hide() + } + d.hidden = true +} + +func (d *DigitalOcean) Update() bool { + if ! d.Ready() {return false} + d.ListSSHKeyID() + if d.ListDroplets() { + for _, droplet := range d.dpolled { + // check if the droplet ID already exists + if (d.dropMap[droplet.ID] == nil) { + d.dropMap[droplet.ID] = d.NewDroplet(&droplet) + } else { + log.Log(POLL, "droplet.Update()", droplet.ID, droplet.Name, "already exists") + d.dropMap[droplet.ID].Update(&droplet) + continue + } + } + } else { + log.Error(d.err, "Error listing droplets") + return false + } + return true +} diff --git a/pollDroplets.go b/pollDroplets.go new file mode 100644 index 0000000..b150978 --- /dev/null +++ b/pollDroplets.go @@ -0,0 +1,50 @@ +package digitalocean + +import ( + "context" + + "golang.org/x/oauth2" + + "github.com/digitalocean/godo" +) + +// ListDroplets fetches and prints out the droplets along with their IPv4 and IPv6 addresses. +func (d *DigitalOcean) ListDroplets() bool { + // OAuth token for authentication. + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: d.token}) + + // OAuth2 client. + oauthClient := oauth2.NewClient(context.Background(), tokenSource) + + // DigitalOcean client. + client := godo.NewClient(oauthClient) + + // Context. + ctx := context.TODO() + + // List all droplets. + d.dpolled, _, d.err = client.Droplets.List(ctx, &godo.ListOptions{}) + if d.err != nil { + return false + } + + // Iterate over droplets and print their details. + /* + for _, droplet := range d.polled { + fmt.Printf("Droplet: %s\n", droplet.Name) + for _, network := range droplet.Networks.V4 { + if network.Type == "public" { + fmt.Printf("IPv4: %s\n", network.IPAddress) + } + } + for _, network := range droplet.Networks.V6 { + if network.Type == "public" { + fmt.Printf("IPv6: %s\n", network.IPAddress) + } + } + fmt.Println("-------------------------") + } + */ + + return true +} diff --git a/poweron.go b/poweron.go new file mode 100644 index 0000000..51f8a24 --- /dev/null +++ b/poweron.go @@ -0,0 +1,82 @@ +package digitalocean + +import ( + "context" + + "golang.org/x/oauth2" + + "github.com/digitalocean/godo" + + "go.wit.com/log" +) + +func (d *DigitalOcean) PowerOn(dropletID int) error { + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: d.token}) + oauthClient := oauth2.NewClient(context.Background(), tokenSource) + client := godo.NewClient(oauthClient) + + ctx := context.TODO() + + // Create a request to power on the droplet. + _, _, err := client.DropletActions.PowerOn(ctx, dropletID) + if err != nil { + return err + } + + log.Printf("Power-on signal sent to droplet with ID: %d\n", dropletID) + return nil +} + +func (d *DigitalOcean) PowerOff(dropletID int) error { + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: d.token}) + oauthClient := oauth2.NewClient(context.Background(), tokenSource) + client := godo.NewClient(oauthClient) + + ctx := context.TODO() + + // Create a request to power on the droplet. + _, _, err := client.DropletActions.PowerOff(ctx, dropletID) + if err != nil { + return err + } + + log.Printf("Power-off signal sent to droplet with ID: %d\n", dropletID) + return nil +} + +/* +func (d *DigitalOcean) Destroy(dropletID int) error { + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: d.token}) + oauthClient := oauth2.NewClient(context.Background(), tokenSource) + client := godo.NewClient(oauthClient) + + ctx := context.TODO() + + // Create a request to power on the droplet. + _, _, err := client.DropletActions.Delete(ctx, dropletID) + if err != nil { + return err + } + + log.Printf("Destroy sent to droplet with ID: %d\n", dropletID) + return nil +} +*/ + +// createDroplet creates a new droplet in the specified region with the given name. +func (d *DigitalOcean) deleteDroplet(drop *Droplet) error { + // Create an OAuth2 token. + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: d.token}) + + // Create an OAuth2 client. + oauthClient := oauth2.NewClient(context.Background(), tokenSource) + + // Create a DigitalOcean client with the OAuth2 client. + client := godo.NewClient(oauthClient) + + ctx := context.TODO() + log.Warn("deleteDroplet() going to delete ID =", drop.ID, "Name =", drop.GetName()) + response, err := client.Droplets.Delete(ctx, drop.ID) + log.Warn(response) + return err +} diff --git a/structs.go b/structs.go new file mode 100644 index 0000000..fdef242 --- /dev/null +++ b/structs.go @@ -0,0 +1,91 @@ +/* + The Digital Ocean Struct +*/ + +package digitalocean + +import ( + "github.com/digitalocean/godo" + + "go.wit.com/gui/gui" + "go.wit.com/gui/gadgets" +) + +type DigitalOcean struct { + ready bool + hidden bool + err error + + token string // You're Digital Ocean API key + dpolled []godo.Droplet + sshKeys []godo.Key + + dropMap map[int]*Droplet + create *windowCreate + + parent *gui.Node // should be the root of the 'gui' package binary tree + window *gui.Node // our window for displaying digital ocean droplets + group *gui.Node + grid *gui.Node + + dGrid *gui.Node // the grid for the droplets + + // Primary Directives + status *gadgets.OneLiner + summary *gadgets.OneLiner + statusIPv4 *gadgets.OneLiner + statusIPv6 *gadgets.OneLiner +} + +type windowCreate struct { + ready bool + hidden bool + err error + + parent *gui.Node // should be the root of the 'gui' package binary tree + window *gui.Node // our window for displaying digital ocean droplets + group *gui.Node + grid *gui.Node + + regionSelected godo.Region + regionSlug string + tag *gadgets.OneLiner + name *gadgets.BasicEntry + region *gadgets.BasicDropdown + size *gadgets.BasicCombobox + memory *gadgets.BasicDropdown + image *gadgets.BasicCombobox + // nvme *gadgets.BasicCheckbox +} + +type ipButton struct { + ip *gui.Node + c *gui.Node +} + +type Droplet struct { + ID int + image string + memory int + disk int + + ready bool + hidden bool + err error + + poll *godo.Droplet // store what the digital ocean API returned + + nameN *gui.Node + sizeSlugN *gui.Node + statusN *gui.Node + imageN *gui.Node + + destroy *gui.Node + connect *gui.Node + poweron *gui.Node + poweroff *gui.Node + edit *gui.Node + + ipv4 *gui.Node + ipv6 *gui.Node +} diff --git a/xterm.go b/xterm.go new file mode 100644 index 0000000..5c94560 --- /dev/null +++ b/xterm.go @@ -0,0 +1,31 @@ +package digitalocean + +import ( + "os/exec" + "go.wit.com/log" +) + +var geom string = "120x30+500+500" + +func xterm(cmd string) { + var tmp []string + var argsXterm = []string{"nohup", "xterm", "-geometry", geom} + // tmp = append(argsXterm, "-hold", "-e", cmd) + tmp = append(argsXterm, "-e", cmd) + log.Println("xterm cmd=", cmd) + go runCommand(tmp) +} + +func runCommand(cmdArgs []string) { + log.Println("runCommand() START", cmdArgs) + process := exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...) + // process := exec.Command("xterm", "-e", "ping localhost") + log.Println("runCommand() process.Start()") + process.Start() + log.Println("runCommand() process.Wait()") + err := process.Wait() + log.Error(err, "on process.Wait") + log.Println("runCommand() NEED TO CHECK THE TIME HERE TO SEE IF THIS WORKED") + log.Println("runCommand() OTHERWISE INFORM THE USER") + log.Println("runCommand() END", cmdArgs) +}