Skip to content

Commit bcf0ea8

Browse files
feat(jobs):instance-snapshot-cleaner (#117)
* feat(jobs):instance-snapshot-cleaner * use slog
1 parent 7f63235 commit bcf0ea8

4 files changed

Lines changed: 71 additions & 23 deletions

File tree

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1-
# Using apline/golang image
2-
FROM golang:1.24-alpine
1+
# Build stage
2+
FROM golang:1.25-alpine AS builder
33

4-
# Set destination for COPY
54
WORKDIR /app
65

76
# Copy required files
8-
COPY go.mod ./
9-
COPY go.sum ./
7+
COPY go.mod go.sum ./
8+
RUN go mod download
9+
1010
COPY *.go ./
1111

1212
# Build the executable
13-
RUN go build -o /jobs-snapshot-cleaner
13+
RUN CGO_ENABLED=0 GOOS=linux go build -o /jobs-snapshot-cleaner
14+
15+
# Final stage
16+
FROM alpine:latest
17+
18+
WORKDIR /app
19+
20+
# Install CA certificates for HTTPS
21+
RUN apk --no-cache add ca-certificates
22+
23+
# Copy the binary from the builder stage
24+
COPY --from=builder /jobs-snapshot-cleaner /app/jobs-snapshot-cleaner
1425

1526
# Run the executable
16-
ENTRYPOINT [ "/jobs-snapshot-cleaner" ]
27+
ENTRYPOINT [ "/app/jobs-snapshot-cleaner" ]

jobs/instances-snapshot-cleaner/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module github.com/scaleway/serverless-examples/jobs/instances-snapshot
22

3-
go 1.24
3+
go 1.25
44

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

77
require (
88
github.com/kr/pretty v0.3.1 // indirect
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2-
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
3-
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
42
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
53
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
64
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -12,10 +10,16 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
1210
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
1311
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
1412
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
15-
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33 h1:KhF0WejiUTDbL5X55nXowP7zNopwpowa6qaMAWyIE+0=
16-
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33/go.mod h1:792k1RTU+5JeMXm35/e2Wgp71qPH/DmDoZrRc+EFZDk=
13+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo=
14+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM=
15+
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
16+
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
17+
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
18+
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
1719
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1820
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
1921
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
22+
gopkg.in/dnaeon/go-vcr.v4 v4.0.6 h1:PiJkrakkmzc5s7EfBnZOnyiLwi7o7A9fwPzN0X2uwe0=
23+
gopkg.in/dnaeon/go-vcr.v4 v4.0.6/go.mod h1:sbq5oMEcM4PXngbcNbHhzfCP9OdZodLhrbRYoyg09HY=
2024
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
2125
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

jobs/instances-snapshot-cleaner/main.go

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package main
22

33
import (
4+
"context"
45
"encoding/json"
56
"errors"
67
"fmt"
8+
"log/slog"
79
"os"
810
"strconv"
911
"time"
@@ -18,6 +20,7 @@ const (
1820
envSecretKey = "SCW_SECRET_KEY"
1921
envProjectID = "SCW_DEFAULT_PROJECT_ID"
2022
envZone = "SCW_ZONE"
23+
envLogLevel = "LOG_LEVEL"
2124

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

32+
var logger *slog.Logger
33+
2934
func main() {
30-
fmt.Println("cleaning instances snapshots...")
35+
// Initialize structured logger
36+
logLevel := slog.LevelInfo
37+
if lvl := os.Getenv(envLogLevel); lvl != "" {
38+
switch lvl {
39+
case "DEBUG":
40+
logLevel = slog.LevelDebug
41+
case "WARN":
42+
logLevel = slog.LevelWarn
43+
case "ERROR":
44+
logLevel = slog.LevelError
45+
}
46+
}
47+
48+
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})
49+
logger = slog.New(h)
50+
slog.SetDefault(logger)
51+
52+
ctx := context.Background()
53+
logger.InfoContext(ctx, "starting instances snapshots cleaner")
3154

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

@@ -55,35 +79,41 @@ func main() {
5579
if deleteAfterDaysVar != "" {
5680
deleteAfterDays, err = strconv.Atoi(deleteAfterDaysVar)
5781
if err != nil {
82+
logger.ErrorContext(ctx, "failed to parse delete after days", "value", deleteAfterDaysVar, "error", err)
5883
panic(err)
5984
}
6085
}
6186

62-
if err := cleanSnapshots(deleteAfterDays, instanceAPI); err != nil {
87+
logger.InfoContext(ctx, "cleaning snapshots", "delete_after_days", deleteAfterDays)
88+
89+
if err := cleanSnapshotsWithLogging(ctx, deleteAfterDays, instanceAPI); err != nil {
6390
var precondErr *scw.PreconditionFailedError
6491

6592
if errors.As(err, &precondErr) {
66-
fmt.Println("\nExtracted Error Details:")
67-
fmt.Println("Precondition:", precondErr.Precondition)
68-
fmt.Println("Help Message:", precondErr.HelpMessage)
93+
logger.ErrorContext(ctx, "scaleway precondition failed",
94+
"precondition", precondErr.Precondition,
95+
"help_message", precondErr.HelpMessage)
6996

7097
// Decode RawBody (if available)
7198
if len(precondErr.RawBody) > 0 {
7299
var parsedBody map[string]interface{}
73100
if json.Unmarshal(precondErr.RawBody, &parsedBody) == nil {
74-
fmt.Println("RawBody (Decoded):", parsedBody)
101+
logger.ErrorContext(ctx, "scaleway error raw body", "body", parsedBody)
75102
} else {
76-
fmt.Println("RawBody (Raw):", string(precondErr.RawBody))
103+
logger.ErrorContext(ctx, "scaleway error raw body", "body", string(precondErr.RawBody))
77104
}
78105
}
79106
}
107+
logger.ErrorContext(ctx, "failed to clean snapshots", "error", err)
80108
panic(err)
81109
}
110+
111+
logger.InfoContext(ctx, "successfully cleaned snapshots")
82112
}
83113

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

107140
// Delete snapshot found.
108141
err := instanceAPI.DeleteSnapshot(&instance.DeleteSnapshotRequest{

0 commit comments

Comments
 (0)