Skip to content

Commit

Permalink
Pulumi module for expiring stuff instead of deleting it.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zemnmez committed Nov 28, 2024
1 parent 83dedc0 commit 3ff3afb
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 13 deletions.
3 changes: 3 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ go_deps.from_file(go_mod = "//:go.mod")
use_repo(
go_deps,
"co_honnef_go_tools",
"com_github_aws_aws_lambda_go",
"com_github_aws_aws_sdk_go",
"com_github_bazelbuild_bazel_watcher",
"com_github_bazelbuild_buildtools",
"com_github_go_delve_delve",
Expand All @@ -131,6 +133,7 @@ use_repo(
"com_github_oapi_codegen_oapi_codegen_v2",
"com_github_oapi_codegen_runtime",
"com_github_sergi_go_diff",
"com_github_stretchr_testify",
"com_github_tdewolff_parse_v2",
"com_github_twilio_twilio_go",
"org_golang_x_sync",
Expand Down
6 changes: 5 additions & 1 deletion gazelle_python.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ go 1.22.7
toolchain go1.23.3

require (
github.com/aws/aws-lambda-go v1.47.0
github.com/aws/aws-sdk-go v1.55.5
github.com/bazelbuild/bazel-gazelle v0.39.1
github.com/bazelbuild/bazel-watcher v0.25.3
github.com/bazelbuild/buildtools v0.0.0-20240827154017-dd10159baa91
Expand All @@ -20,6 +22,7 @@ require (
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1
github.com/oapi-codegen/runtime v1.1.1
github.com/sergi/go-diff v1.3.1
github.com/stretchr/testify v1.9.0
github.com/tdewolff/parse/v2 v2.7.19
github.com/twilio/twilio-go v1.23.6
golang.org/x/sync v0.9.0
Expand All @@ -35,6 +38,7 @@ require (
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cosiner/argv v0.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d // indirect
github.com/docker/cli v27.1.1+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
Expand Down Expand Up @@ -62,6 +66,7 @@ require (
github.com/invopop/yaml v0.3.1 // indirect
github.com/itchyny/timefmt-go v0.1.6 // indirect
github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
Expand All @@ -77,6 +82,7 @@ require (
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
Expand Down
9 changes: 9 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions ts/pulumi/lib/expire_on_delete/lambda/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
load("@aspect_bazel_lib//lib:testing.bzl", "assert_archive_contains")
load("@rules_oci//oci:defs.bzl", "oci_image")
load("@rules_pkg//:pkg.bzl", "pkg_tar")
load("//bzl:rules.bzl", "bazel_lint")
load("//go:rules.bzl", "go_binary", "go_library")
load("//ts/pulumi/lib/oci:rules.bzl", "pulumi_image")

bazel_lint(
name = "bazel_lint",
srcs = ["BUILD.bazel"],
)

pulumi_image(
name = "expire_on_delete_image",
src = ":oci_image",
)

go_library(
name = "lambda_lib",
srcs = ["main.go"],
importpath = "github.com/zemn-me/monorepo/ts/pulumi/lib/expire_on_delete/lambda",
visibility = ["//visibility:private"],
deps = [
"@com_github_aws_aws_lambda_go//events",
"@com_github_aws_aws_lambda_go//lambda",
"@com_github_aws_aws_sdk_go//aws",
"@com_github_aws_aws_sdk_go//aws/session",
"@com_github_aws_aws_sdk_go//service/s3",
],
)

go_binary(
name = "app",
embed = [":lambda_lib"],
visibility = ["//visibility:public"],
)

# Put app go_binary into a tar layer.
pkg_tar(
name = "app_layer",
srcs = [":app"],
# If the binary depends on RUNFILES, uncomment the attribute below.
# include_runfiles = True
)

# Prove that the application is at the path we expect in that tar.
assert_archive_contains(
name = "test_app_layer",
archive = "app_layer.tar",
expected = ["app"],
)

oci_image(
name = "oci_image",
base = "@distroless_base",
entrypoint = ["/app"],
tars = [":app_layer"],
)
114 changes: 114 additions & 0 deletions ts/pulumi/lib/expire_on_delete/lambda/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Package main implements an AWS Lambda function that handles S3 delete marker events.
// This function removes delete markers and sets expiration tags on versioned objects.
// It is intended for use in scenarios such as managing a CDN backing store where objects
// need to be retained for a short period after delete markers are created, ensuring proper
// propagation of updates before objects are fully expired.
package main

import (
"context"
"fmt"
"log"
"time"

"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)

const expirationDays = 7

type S3EventRecord struct {
BucketName string
ObjectKey string
VersionID string
}

func handleRequest(ctx context.Context, event events.S3Event) (map[string]string, error) {
sess := session.Must(session.NewSession())
s3Client := s3.New(sess)

// Extract bucket name, object key, and version ID from the event
record := event.Records[0]
bucketName := record.S3.Bucket.Name
objectKey := record.S3.Object.Key
versionID := record.S3.Object.VersionID

// Check if this event corresponds to a delete marker
headObjectInput := &s3.HeadObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
VersionId: aws.String(versionID),
}

headObjectOutput, err := s3Client.HeadObject(headObjectInput)
if err != nil {
log.Printf("Error getting object metadata for %s: %v", objectKey, err)
return map[string]string{
"statusCode": "500",
"body": fmt.Sprintf("Error: %v", err),
}, err
}

if aws.BoolValue(headObjectOutput.DeleteMarker) {
// Remove the delete marker
deleteObjectInput := &s3.DeleteObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
VersionId: aws.String(versionID),
}

_, err := s3Client.DeleteObject(deleteObjectInput)
if err != nil {
log.Printf("Error removing delete marker for %s: %v", objectKey, err)
return map[string]string{
"statusCode": "500",
"body": fmt.Sprintf("Error: %v", err),
}, err
}
log.Printf("Removed delete marker for %s in %s", objectKey, bucketName)

// Calculate the expiration date
expirationDate := time.Now().Add(expirationDays * 24 * time.Hour).Format(time.RFC3339)

// Add an expiration tag to the object
taggingInput := &s3.PutObjectTaggingInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
Tagging: &s3.Tagging{
TagSet: []*s3.Tag{
{
Key: aws.String("ExpireOn"),
Value: aws.String(expirationDate),
},
},
},
}

_, err = s3Client.PutObjectTagging(taggingInput)
if err != nil {
log.Printf("Error setting expiration tag for %s: %v", objectKey, err)
return map[string]string{
"statusCode": "500",
"body": fmt.Sprintf("Error: %v", err),
}, err
}
log.Printf("Set expiration tag for %s to %s", objectKey, expirationDate)

return map[string]string{
"statusCode": "200",
"body": fmt.Sprintf("Removed delete marker and set expiration for %s.", objectKey),
}, nil
}

return map[string]string{
"statusCode": "200",
"body": "No action needed.",
}, nil
}

func main() {
lambda.Start(handleRequest)
}
57 changes: 57 additions & 0 deletions ts/pulumi/lib/expiring_bucket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Pulumi component for a bucket that expires files after some time
* instead of deleting them.
*/

import * as fs from 'node:fs/promises';

import { lambda, s3 } from "@pulumi/aws";
import { ComponentResource, ComponentResourceOptions, interpolate, output } from "@pulumi/pulumi";

import { ExpireOnDeleteImage } from "#root/ts/pulumi/lib/expire_on_delete/lambda/ExpireOnDeleteImage.js";


export interface Args {
bucketId: string
ECRBaseURI: string
}

/**
* Expires files after some time instead of deleting them.
*/
export class FileExpirer extends ComponentResource {
constructor(
name: string,
args: Args,
opts?: ComponentResourceOptions
) {
super('ts:pulumi:lib:ExpiringBucket', name, args, opts);

const img = new ExpireOnDeleteImage(`${name}_img`, {
repository: args.ECRBaseURI
}, { parent: this });

const fn = new lambda.Function(`${name}_lambda`, {
"imageUri": interpolate`${args.ECRBaseURI}@${
img.digest.path.then(p => output(
fs.readFile(p)))
}`
}, { parent: this });


new s3.BucketNotification(
`${name}_bucketnotification`,
{
bucket: args.bucketId,
lambdaFunctions: [
{
lambdaFunctionArn: fn.arn,
events: ["s3:ObjectRemoved:*"],
}
]
},
{ parent: this}
)
}
}

16 changes: 10 additions & 6 deletions ts/pulumi/lib/oci/oci_image.tmpl.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import { local } from '@pulumi/command';
import { ComponentResource, ComponentResourceOptions } from "@pulumi/pulumi";
import { ComponentResource, ComponentResourceOptions, Input, output } from "@pulumi/pulumi";
import { FileAsset } from '@pulumi/pulumi/asset';


export interface Args {
repository: string
repository: Input<string>
}

export class __ClassName extends ComponentResource {
digest: FileAsset
constructor(
name: string,
args: Args,
opts?: ComponentResourceOptions
) {
super('__TYPE', name, args, opts);

this.digest = new FileAsset("_DIGEST_PATH");

const upload = new local.Command(`${name}_push`, {
create: [
create: output(args.repository).apply(repo => [
"__PUSH_BIN",
"--repository",
args.repository,
].join(" ")
repo,
].join(" "))
}, { parent: this })


super.registerOutputs({ upload })
super.registerOutputs({ upload, digest: this.digest })
}


Expand Down
Loading

0 comments on commit 3ff3afb

Please sign in to comment.