feat: initial commit
This commit is contained in:
33
cmd/DigitalOceanSnapshotter/digitalOceanContext.go
Normal file
33
cmd/DigitalOceanSnapshotter/digitalOceanContext.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
)
|
||||
|
||||
// DigitalOceanContext is an helper struct to acess Digital Ocean actions
|
||||
type DigitalOceanContext struct {
|
||||
client *godo.Client
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// GetVolume gets Volume by na
|
||||
func (d DigitalOceanContext) GetVolume(id string) (*godo.Volume, *godo.Response, error) {
|
||||
return d.client.Storage.GetVolume(d.ctx, id)
|
||||
}
|
||||
|
||||
// CreateSnapshot creates a snapshot from a Volume
|
||||
func (d DigitalOceanContext) CreateSnapshot(options *godo.SnapshotCreateRequest) (*godo.Snapshot, *godo.Response, error) {
|
||||
return d.client.Storage.CreateSnapshot(d.ctx, options)
|
||||
}
|
||||
|
||||
// ListSnapshots lists all snapshots from a volume
|
||||
func (d DigitalOceanContext) ListSnapshots(volumeID string, opts *godo.ListOptions) ([]godo.Snapshot, *godo.Response, error) {
|
||||
return d.client.Storage.ListSnapshots(d.ctx, volumeID, opts)
|
||||
}
|
||||
|
||||
// DeleteSnapshot Deletes a snaphot by id
|
||||
func (d DigitalOceanContext) DeleteSnapshot(id string) (*godo.Response, error) {
|
||||
return d.client.Storage.DeleteSnapshot(d.ctx, id)
|
||||
}
|
||||
146
cmd/DigitalOceanSnapshotter/main.go
Normal file
146
cmd/DigitalOceanSnapshotter/main.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/slack-go/slack"
|
||||
)
|
||||
|
||||
const createdAtFormat = "2006-01-02T15:04:05Z0700"
|
||||
|
||||
type snapshotterContext struct {
|
||||
DoContext *DigitalOceanContext
|
||||
SlackContext *SlackContext
|
||||
}
|
||||
|
||||
func initLogging() {
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
DisableLevelTruncation: true,
|
||||
})
|
||||
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
initLogging()
|
||||
|
||||
DOToken, present := os.LookupEnv("DO_TOKEN")
|
||||
|
||||
if present == false {
|
||||
log.Fatal("Missing enviroment variable \"DO_TOKEN\"")
|
||||
}
|
||||
|
||||
volumesEnv, present := os.LookupEnv("DO_VOLUMES")
|
||||
|
||||
if present == false {
|
||||
log.Fatal("Missing enviroment variable \"DO_VOLUMES\"")
|
||||
}
|
||||
|
||||
snapshotCountEnv, present := os.LookupEnv("DO_SNAPSHOT_COUNT")
|
||||
|
||||
if present == false {
|
||||
log.Fatal("Missing enviroment variable \"DO_SNAPSHOT_COUNT\"")
|
||||
}
|
||||
|
||||
snapshotCount, err := strconv.Atoi(snapshotCountEnv)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Enviroment variable \"DO_SNAPSHOT_COUNT\" is not an integer")
|
||||
}
|
||||
|
||||
slackEnv := os.Getenv("SLACK_TOKEN")
|
||||
|
||||
var slackContext *SlackContext = nil
|
||||
|
||||
if slackEnv != "" {
|
||||
channelID, present := os.LookupEnv("SLACK_CHANNEL_ID")
|
||||
|
||||
if present == false {
|
||||
log.Fatal("Missing enviroment variable \"SLACK_CHANNEL_ID\"")
|
||||
}
|
||||
|
||||
slackContext = &SlackContext{
|
||||
client: slack.New(slackEnv),
|
||||
channelID: channelID,
|
||||
}
|
||||
}
|
||||
|
||||
ctx := snapshotterContext{
|
||||
DoContext: &DigitalOceanContext{
|
||||
client: godo.NewFromToken(DOToken),
|
||||
ctx: context.TODO(),
|
||||
},
|
||||
SlackContext: slackContext,
|
||||
}
|
||||
|
||||
volumeIDs := strings.Split(volumesEnv, ",")
|
||||
|
||||
for _, volumeID := range volumeIDs {
|
||||
volume, _, err := ctx.DoContext.GetVolume(volumeID)
|
||||
if err != nil {
|
||||
errorHandling(err, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err = ctx.DoContext.CreateSnapshot(&godo.SnapshotCreateRequest{
|
||||
VolumeID: volume.ID,
|
||||
Name: fmt.Sprintf("%s-%s", volume.Name, time.Now().Format("2006-01-02 15:04:05")),
|
||||
})
|
||||
if err != nil {
|
||||
errorHandling(err, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
snapshots, _, err := ctx.DoContext.ListSnapshots(volume.ID, nil)
|
||||
|
||||
snapshotLength := len(snapshots)
|
||||
|
||||
if snapshotLength > snapshotCount {
|
||||
sort.SliceStable(snapshots, func(firstIndex, secondIndex int) bool {
|
||||
firstTime, err := time.Parse(snapshots[firstIndex].Created, createdAtFormat)
|
||||
if err != nil {
|
||||
errorHandling(err, ctx)
|
||||
}
|
||||
|
||||
secondTime, err := time.Parse(snapshots[firstIndex].Created, createdAtFormat)
|
||||
if err != nil {
|
||||
errorHandling(err, ctx)
|
||||
}
|
||||
|
||||
return firstTime.Before(secondTime)
|
||||
})
|
||||
|
||||
snapshotsToDelete := snapshotLength - snapshotCount
|
||||
|
||||
for i := 0; i < snapshotsToDelete; i++ {
|
||||
_, err := ctx.DoContext.DeleteSnapshot(snapshots[i].ID)
|
||||
|
||||
if err != nil {
|
||||
errorHandling(err, ctx)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func errorHandling(err error, ctx snapshotterContext) {
|
||||
log.Error(err.Error())
|
||||
|
||||
if ctx.SlackContext != nil {
|
||||
err = ctx.SlackContext.SendEvent(err.Error(), log.ErrorLevel)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
30
cmd/DigitalOceanSnapshotter/outputSource.go
Normal file
30
cmd/DigitalOceanSnapshotter/outputSource.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/slack-go/slack"
|
||||
)
|
||||
|
||||
// OutputSource is an abstraction for outputting specific events to other services (e.g. Discord, Slack or Whatsapp)
|
||||
type OutputSource interface {
|
||||
SendEvent(string, log.Level) error
|
||||
}
|
||||
|
||||
// SendEvent forwards event to Slack
|
||||
func (s SlackContext) SendEvent(content string, level log.Level) error {
|
||||
|
||||
color := "#00FF00"
|
||||
|
||||
if level == log.ErrorLevel {
|
||||
color = "#FF0000"
|
||||
}
|
||||
|
||||
return s.SendMessageWithEmbed(slack.Attachment{
|
||||
Color: color,
|
||||
AuthorName: "DigitalOceanSnapshotter",
|
||||
AuthorIcon: "https://cdn.top.gg/icons/DO_Logo_icon_blue.png",
|
||||
Text: content,
|
||||
Title: "DigitalOceanSnapshotter",
|
||||
TitleLink: "https://github.com/top-gg/DigitalOceanSnapshotter",
|
||||
})
|
||||
}
|
||||
21
cmd/DigitalOceanSnapshotter/slackContext.go
Normal file
21
cmd/DigitalOceanSnapshotter/slackContext.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import "github.com/slack-go/slack"
|
||||
|
||||
// SlackContext is an helper struct to acess slack actions
|
||||
type SlackContext struct {
|
||||
client *slack.Client
|
||||
channelID string
|
||||
}
|
||||
|
||||
// SendMessageWithContent sends a message with content to the pre defined channel
|
||||
func (s SlackContext) SendMessageWithContent(content string) error {
|
||||
_, _, _, err := s.client.SendMessage(s.channelID, slack.MsgOptionText(content, true))
|
||||
return err
|
||||
}
|
||||
|
||||
// SendMessageWithEmbed sends a message with a Rich Embed to the pre defined channel
|
||||
func (s SlackContext) SendMessageWithEmbed(attachment slack.Attachment) error {
|
||||
_, _, _, err := s.client.SendMessage(s.channelID, slack.MsgOptionAttachments(attachment))
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user