diff --git a/jobs/databases-backup/Dockerfile b/jobs/databases-backup/Dockerfile new file mode 100644 index 0000000..11aaed4 --- /dev/null +++ b/jobs/databases-backup/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.24.1-bookworm + +# Set destination for COPY +WORKDIR /app + +COPY go.mod ./ +COPY go.sum ./ +COPY *.go ./ + +# Build +RUN go build -o /server-image + +# Run +CMD [ "/server-image" ] diff --git a/jobs/databases-backup/go.mod b/jobs/databases-backup/go.mod new file mode 100644 index 0000000..473a401 --- /dev/null +++ b/jobs/databases-backup/go.mod @@ -0,0 +1,7 @@ +module gitlab.infra.online.net/ttacquet/jobs-demo + +go 1.24 + +require github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 + +require gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/jobs/databases-backup/go.sum b/jobs/databases-backup/go.sum new file mode 100644 index 0000000..1eb87cc --- /dev/null +++ b/jobs/databases-backup/go.sum @@ -0,0 +1,6 @@ +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 h1:4+LP7qmsLSGbmc66m1s5dKRMBwztRppfxFKlYqYte/c= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32/go.mod h1:kzh+BSAvpoyHHdHBCDhmSWtBc1NbLMZ2lWHqnBoxFks= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/jobs/databases-backup/main.go b/jobs/databases-backup/main.go new file mode 100644 index 0000000..8b5f7d3 --- /dev/null +++ b/jobs/databases-backup/main.go @@ -0,0 +1,128 @@ +package main + +import ( + "fmt" + "os" + "strconv" + "time" + + "github.com/scaleway/scaleway-sdk-go/api/rdb/v1" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +const ( + envOrgID = "SCW_DEFAULT_ORGANIZATION_ID" // Scaleway organization ID + envAccessKey = "SCW_ACCESS_KEY" // Scaleway API access key + envSecretKey = "SCW_SECRET_KEY" // Scaleway API secret key + envProjectID = "SCW_PROJECT_ID" // Scaleway project ID + + envRegion = "SCW_REGION" + envDatabaseID = "SCW_RDB_ID" + envBackupExpirationDays = "SCW_EXPIRATION_DAYS" +) + +// Check for mandatory variables before starting to work. +func init() { + // Slice of environmental variables that must be set for the application to run + mandatoryVariables := [...]string{envOrgID, envAccessKey, envSecretKey, envProjectID, envRegion} + + // Iterate through the slice and check if any variables are not set + for idx := range mandatoryVariables { + if os.Getenv(mandatoryVariables[idx]) == "" { + panic("missing environment variable " + mandatoryVariables[idx]) + } + } +} + +func main() { + fmt.Println("creating backup of managed database...") + + // Create a Scaleway client with credentials provided via environment variables. + // The client is used to interact with the Scaleway API + client, err := scw.NewClient( + // Get your organization ID at https://console.scaleway.com/organization/settings + scw.WithDefaultOrganizationID(os.Getenv(envOrgID)), + + // Get your credentials at https://console.scaleway.com/iam/api-keys + scw.WithAuth(os.Getenv(envAccessKey), os.Getenv(envSecretKey)), + + // Get more about our availability + // zones at https://www.scaleway.com/en/docs/console/my-account/reference-content/products-availability/ + scw.WithDefaultRegion(scw.Region(os.Getenv(envRegion))), + ) + if err != nil { + panic(err) + } + + rdbAPI := rdb.NewAPI(client) + + if err := createRdbSnapshot(rdbAPI); err != nil { + panic(err) + } +} + +func createRdbSnapshot(rdbAPI *rdb.API) error { + rdbInstance, err := rdbAPI.GetInstance(&rdb.GetInstanceRequest{ + Region: scw.Region(scw.Region(os.Getenv(envRegion))), + InstanceID: os.Getenv(envDatabaseID), + }) + if err != nil { + return fmt.Errorf("error while getting database instance %w", err) + } + + databasesList, err := rdbAPI.ListDatabases(&rdb.ListDatabasesRequest{ + Region: scw.Region(os.Getenv(envRegion)), + InstanceID: rdbInstance.ID, + }) + if err != nil { + return fmt.Errorf("error while listing databases %w", err) + } + + expiresAt, err := getExpirationDate() + if err != nil { + return fmt.Errorf("error while getting expiration date %w", err) + } + + now := time.Now() + + for _, database := range databasesList.Databases { + backupName := fmt.Sprintf("backup-%s-%s-%s", + database.Name, + now, + os.Getenv(envRegion)) + + backup, err := rdbAPI.CreateDatabaseBackup(&rdb.CreateDatabaseBackupRequest{ + Region: scw.Region(os.Getenv(envRegion)), + InstanceID: rdbInstance.ID, + Name: backupName, + DatabaseName: database.Name, + ExpiresAt: expiresAt, + }) + if err != nil { + return fmt.Errorf("error while creating database backup request %w", err) + } + + fmt.Println("Created backup ", backup.Name) + } + + return nil +} + +func getExpirationDate() (*time.Time, error) { + var expiresAt *time.Time + expireDays := os.Getenv(envBackupExpirationDays) + + if expireDays != "" { + expireDaysInt, err := strconv.Atoi(expireDays) + if err != nil { + return nil, fmt.Errorf("error while getting %w", err) + } + + if expireDaysInt > 0 { + expiration := time.Now().AddDate(0, 0, expireDaysInt) + expiresAt = &expiration + } + } + + return expiresAt, nil +}