diff --git a/jobs/instances-snapshot-cleaner/Dockerfile b/jobs/instances-snapshot-cleaner/Dockerfile index a70eb8c..11aa2ff 100644 --- a/jobs/instances-snapshot-cleaner/Dockerfile +++ b/jobs/instances-snapshot-cleaner/Dockerfile @@ -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" ] diff --git a/jobs/instances-snapshot-cleaner/go.mod b/jobs/instances-snapshot-cleaner/go.mod index a00eab1..66426ff 100644 --- a/jobs/instances-snapshot-cleaner/go.mod +++ b/jobs/instances-snapshot-cleaner/go.mod @@ -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 diff --git a/jobs/instances-snapshot-cleaner/go.sum b/jobs/instances-snapshot-cleaner/go.sum index 43e00dc..cad1bab 100644 --- a/jobs/instances-snapshot-cleaner/go.sum +++ b/jobs/instances-snapshot-cleaner/go.sum @@ -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= @@ -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= diff --git a/jobs/instances-snapshot-cleaner/main.go b/jobs/instances-snapshot-cleaner/main.go index ee072d8..d8be25f 100644 --- a/jobs/instances-snapshot-cleaner/main.go +++ b/jobs/instances-snapshot-cleaner/main.go @@ -1,9 +1,11 @@ package main import ( + "context" "encoding/json" "errors" "fmt" + "log/slog" "os" "strconv" "time" @@ -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" @@ -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( @@ -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) } @@ -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)), @@ -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{