diff --git a/addresses.go b/addresses.go index 107122a..16682eb 100644 --- a/addresses.go +++ b/addresses.go @@ -1,64 +1,89 @@ /* Copyright © 2018 Harald Sitter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( + "fmt" "sync" "time" ) // Could/should use atomic really. the manual mutex playing is meh var _addresses []string var _addressesMutex sync.RWMutex func GetAddresses() []string { // NB: we don't need to manually copy this or anything. The slice is fixed // once it is assigned to _addresses, we only need the mutex to ensure the // var doesn't get assigned while we read it. Once we have the slice // we are good to go. _addressesMutex.RLock() ret := _addresses _addressesMutex.RUnlock() return ret } +func addressReset() { + _addressesMutex.Lock() + _addresses = []string{} + _addressesMutex.Unlock() +} + func addressPoll() { addresses := []string{} - addresses = append(addresses, dropletsPoll()...) - addresses = append(addresses, packetsPoll()...) + // If there was an error, enter startup state. Errors mustn't be fatal + // to prevent breakage in production. + // This is particularly important in case the API endpoints go down for + // whatever reason. + + list, err := dropletsPoll() + if err != nil { + fmt.Println(err) + addressReset() + return + } + addresses = append(addresses, list...) + + list, err = packetsPoll() + addresses = append(addresses, list...) + if err != nil { + fmt.Println(err) + addressReset() + return + } _addressesMutex.Lock() _addresses = addresses _addressesMutex.Unlock() } func addressesPollTick() { // This would cause 360 polls an hour, we have a limit of 5000 requests per // hour on the DO API side, so we should be well within the limit here. pollTicker := time.NewTicker(10 * time.Second) go func() { for { addressPoll() <-pollTicker.C } }() } diff --git a/droplets.go b/droplets.go index c2df9fa..3f98349 100644 --- a/droplets.go +++ b/droplets.go @@ -1,102 +1,102 @@ /* Copyright © 2018 Harald Sitter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "context" "github.com/digitalocean/godo" "golang.org/x/oauth2" ) type tokenSource struct { AccessToken string } func (t *tokenSource) Token() (*oauth2.Token, error) { token := &oauth2.Token{ AccessToken: t.AccessToken, } return token, nil } func listAddresses(ctx context.Context, client *godo.Client) ([]string, error) { addresses := []string{} // api docs say maximum is 200 per page, so get that. We'll want as few // requets as possible so as to not hit the api rate limit. opt := &godo.ListOptions{PerPage: 200} for { droplets, resp, err := client.Droplets.List(ctx, opt) if err != nil { return nil, err } for _, d := range droplets { for _, network := range d.Networks.V4 { addresses = append(addresses, network.IPAddress) } for _, network := range d.Networks.V6 { addresses = append(addresses, network.IPAddress) } } // last page if resp.Links == nil || resp.Links.IsLastPage() { break } page, err := resp.Links.CurrentPage() if err != nil { return nil, err } opt.Page = page + 1 } return addresses, nil } -func dropletsPoll() []string { +func dropletsPoll() ([]string, error) { // FIXME: needs rate limiter addresses := []string{} for _, accessToken := range conf.DOAccessTokens { token := &tokenSource{ AccessToken: accessToken, } oauthClient := oauth2.NewClient(context.Background(), token) client := godo.NewClient(oauthClient) ctx := context.TODO() list, err := listAddresses(ctx, client) if err != nil { - panic(err) + return addresses, err } addresses = append(addresses, list...) } - return addresses + return addresses, nil } diff --git a/packet.go b/packet.go index d3b4dd7..719021d 100644 --- a/packet.go +++ b/packet.go @@ -1,72 +1,77 @@ /* Copyright © 2018 Harald Sitter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "log" "os" "github.com/packethost/packngo" ) -func packetsList(c *packngo.Client) []string { +func packetsList(c *packngo.Client) ([]string, error) { + addresses := []string{} + projects, _, err := c.Projects.List(nil) if err != nil { - panic(err) + return addresses, err } - addresses := []string{} - for _, project := range projects { listOpt := &packngo.ListOptions{} devices, _, err := c.Devices.List(project.ID, listOpt) if err != nil { - panic(err) + return addresses, err } for _, device := range devices { for _, network := range device.Network { addresses = append(addresses, network.Address) } } } - return addresses + return addresses, nil } -func packetsPoll() []string { +func packetsPoll() ([]string, error) { // WARNING: packngo is not reentrant!!!! it gets the token from the env // exclusively, so new clients must only be made serially addresses := []string{} for _, token := range conf.PacketAccessTokens { os.Setenv("PACKET_AUTH_TOKEN", token) c, err := packngo.NewClient() os.Unsetenv("PACKET_AUTH_TOKEN") if err != nil { log.Fatal(err) } - addresses = append(addresses, packetsList(c)...) + list, err := packetsList(c) + if err != nil { + return addresses, err + } + + addresses = append(addresses, list...) } - return addresses + return addresses, nil }