Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions jobs/instances-snapshot-cleaner/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
# Using apline/golang image
FROM golang:1.24-alpine
# Build stage
FROM golang:1.25-alpine AS builder

# Set destination for COPY
WORKDIR /app

# Copy required files
COPY go.mod ./
COPY go.sum ./
COPY go.mod go.sum ./
RUN go mod download

COPY *.go ./

# Build the executable
RUN go build -o /jobs-snapshot-cleaner
RUN CGO_ENABLED=0 GOOS=linux go build -o /jobs-snapshot-cleaner

# Final stage
FROM alpine:latest

WORKDIR /app

# Install CA certificates for HTTPS
RUN apk --no-cache add ca-certificates

# Copy the binary from the builder stage
COPY --from=builder /jobs-snapshot-cleaner /app/jobs-snapshot-cleaner

# Run the executable
ENTRYPOINT [ "/jobs-snapshot-cleaner" ]
ENTRYPOINT [ "/app/jobs-snapshot-cleaner" ]
4 changes: 2 additions & 2 deletions jobs/instances-snapshot-cleaner/go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/scaleway/serverless-examples/jobs/instances-snapshot

go 1.24
go 1.25

require github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33
require github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36

require (
github.com/kr/pretty v0.3.1 // indirect
Expand Down
12 changes: 8 additions & 4 deletions jobs/instances-snapshot-cleaner/go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
Expand All @@ -12,10 +10,16 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33 h1:KhF0WejiUTDbL5X55nXowP7zNopwpowa6qaMAWyIE+0=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33/go.mod h1:792k1RTU+5JeMXm35/e2Wgp71qPH/DmDoZrRc+EFZDk=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM=
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/dnaeon/go-vcr.v4 v4.0.6 h1:PiJkrakkmzc5s7EfBnZOnyiLwi7o7A9fwPzN0X2uwe0=
gopkg.in/dnaeon/go-vcr.v4 v4.0.6/go.mod h1:sbq5oMEcM4PXngbcNbHhzfCP9OdZodLhrbRYoyg09HY=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
53 changes: 43 additions & 10 deletions jobs/instances-snapshot-cleaner/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package main

import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"os"
"strconv"
"time"
Expand All @@ -18,6 +20,7 @@ const (
envSecretKey = "SCW_SECRET_KEY"
envProjectID = "SCW_DEFAULT_PROJECT_ID"
envZone = "SCW_ZONE"
envLogLevel = "LOG_LEVEL"

// envDeleteAfter name of env variable to deleter older images.
envDeleteAfter = "SCW_DELETE_AFTER_DAYS"
Expand All @@ -26,8 +29,28 @@ const (
defaultDaysDeleteAfter = int(90)
)

var logger *slog.Logger

func main() {
fmt.Println("cleaning instances snapshots...")
// Initialize structured logger
logLevel := slog.LevelInfo
if lvl := os.Getenv(envLogLevel); lvl != "" {
switch lvl {
case "DEBUG":
logLevel = slog.LevelDebug
case "WARN":
logLevel = slog.LevelWarn
case "ERROR":
logLevel = slog.LevelError
}
}

h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})
logger = slog.New(h)
slog.SetDefault(logger)

ctx := context.Background()
logger.InfoContext(ctx, "starting instances snapshots cleaner")

// Create a Scaleway client with credentials from environment variables.
client, err := scw.NewClient(
Expand All @@ -42,6 +65,7 @@ func main() {
scw.WithDefaultRegion(scw.RegionFrPar),
)
if err != nil {
logger.ErrorContext(ctx, "failed to create scaleway client", "error", err)
panic(err)
}

Expand All @@ -55,35 +79,41 @@ func main() {
if deleteAfterDaysVar != "" {
deleteAfterDays, err = strconv.Atoi(deleteAfterDaysVar)
if err != nil {
logger.ErrorContext(ctx, "failed to parse delete after days", "value", deleteAfterDaysVar, "error", err)
panic(err)
}
}

if err := cleanSnapshots(deleteAfterDays, instanceAPI); err != nil {
logger.InfoContext(ctx, "cleaning snapshots", "delete_after_days", deleteAfterDays)

if err := cleanSnapshotsWithLogging(ctx, deleteAfterDays, instanceAPI); err != nil {
var precondErr *scw.PreconditionFailedError

if errors.As(err, &precondErr) {
fmt.Println("\nExtracted Error Details:")
fmt.Println("Precondition:", precondErr.Precondition)
fmt.Println("Help Message:", precondErr.HelpMessage)
logger.ErrorContext(ctx, "scaleway precondition failed",
"precondition", precondErr.Precondition,
"help_message", precondErr.HelpMessage)

// Decode RawBody (if available)
if len(precondErr.RawBody) > 0 {
var parsedBody map[string]interface{}
if json.Unmarshal(precondErr.RawBody, &parsedBody) == nil {
fmt.Println("RawBody (Decoded):", parsedBody)
logger.ErrorContext(ctx, "scaleway error raw body", "body", parsedBody)
} else {
fmt.Println("RawBody (Raw):", string(precondErr.RawBody))
logger.ErrorContext(ctx, "scaleway error raw body", "body", string(precondErr.RawBody))
}
}
}
logger.ErrorContext(ctx, "failed to clean snapshots", "error", err)
panic(err)
}

logger.InfoContext(ctx, "successfully cleaned snapshots")
}

// cleanSnapshots when called will clean snapshots in the project (if specified)
// cleanSnapshotsWithLogging when called will clean snapshots in the project (if specified)
// that are older than the number of days.
func cleanSnapshots(days int, instanceAPI *instance.API) error {
func cleanSnapshotsWithLogging(ctx context.Context, days int, instanceAPI *instance.API) error {
// Get the list of all snapshots
snapshotsList, err := instanceAPI.ListSnapshots(&instance.ListSnapshotsRequest{
Zone: scw.Zone(os.Getenv(envZone)),
Expand All @@ -102,7 +132,10 @@ func cleanSnapshots(days int, instanceAPI *instance.API) error {
for _, snapshot := range snapshotsList.Snapshots {
// Check if snapshot is in ready state and if it's older than the number of days definied.
if snapshot.State == instance.SnapshotStateAvailable && (currentTime.Sub(*snapshot.CreationDate).Hours()/hoursPerDay) > float64(days) {
fmt.Printf("\nDeleting snapshot <%s>:%s created at: %s\n", snapshot.ID, snapshot.Name, snapshot.CreationDate.Format(time.RFC3339))
logger.InfoContext(ctx, "deleting snapshot",
"id", snapshot.ID,
"name", snapshot.Name,
"created_at", snapshot.CreationDate.Format(time.RFC3339))

// Delete snapshot found.
err := instanceAPI.DeleteSnapshot(&instance.DeleteSnapshotRequest{
Expand Down
Loading