S3 and Plant Tracer¶
Plant Tracer uses three distinct S3 locations:
Location |
Configuration |
Purpose and lifetime |
|---|---|---|
Runtime movie archive |
|
Upload staging, original movies, and derived movie artifacts. The bucket is pre-existing, shared, and outlives every application stack. |
Operational artifacts |
|
Temporary diagnostics such as CloudWatch Synthetics output. These are not movie data and expire according to bucket lifecycle rules. |
SAM deployment artifacts |
SAM CLI managed bucket selected by |
Packaged Lambda deployment files consumed by CloudFormation. |
Runtime Movie Archive¶
The runtime bucket is supplied to the stack; template.yaml does not create
or delete it. DynamoDB stores complete S3 URNs so existing rows remain readable
when they refer to an older bucket or object layout.
Creating a Runtime Movie Bucket¶
The following procedure creates a new runtime movie archive for stacks in the same AWS account and region. Bucket names are globally unique. Use a project-specific bucket name and the region in which the Plant Tracer stacks run; an S3 bucket sends its events to EventBridge in that same region.
Load the AWS environment, choose the bucket name and region, verify the active AWS identity, and create the bucket:
source .envrc export PT_BUCKET=planttracer-example export PT_BUCKET_REGION=us-east-1 aws sts get-caller-identity --no-cli-pager aws s3 mb "s3://${PT_BUCKET}" --region "${PT_BUCKET_REGION}"
Keep the archive private and make the bucket owner the owner of every object. Plant Tracer uses presigned requests; it does not require public bucket or object access:
aws s3api put-public-access-block \ --bucket "${PT_BUCKET}" \ --public-access-block-configuration \ 'BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true' aws s3api put-bucket-ownership-controls \ --bucket "${PT_BUCKET}" \ --ownership-controls 'Rules=[{ObjectOwnership=BucketOwnerEnforced}]'
Explicitly enable server-side encryption with Amazon S3 managed keys:
aws s3api put-bucket-encryption \ --bucket "${PT_BUCKET}" \ --server-side-encryption-configuration \ '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
Configure CORS for browser presigned uploads and downloads. CORS permission does not make objects public; S3 still requires a valid presigned request. The configuration matches
CORS_CONFIGURATIONinsrc/app/s3_presigned.py:aws s3api put-bucket-cors \ --bucket "${PT_BUCKET}" \ --cors-configuration \ '{"CORSRules":[{"AllowedHeaders":["*"],"AllowedMethods":["GET","POST","PUT","DELETE"],"AllowedOrigins":["*"],"MaxAgeSeconds":3600}]}'
Enable S3 delivery to EventBridge. This is a one-time operation for each bucket, regardless of how many Plant Tracer stacks share it. The Makefile helper preserves any existing S3 topic, queue, and Lambda notification configuration:
PLANTTRACER_S3_BUCKET="${PT_BUCKET}" \ CONFIRM_BUCKET="${PT_BUCKET}" \ make s3-eventbridge-enable
Verify the bucket configuration:
aws s3api head-bucket --bucket "${PT_BUCKET}" aws s3api get-public-access-block --bucket "${PT_BUCKET}" aws s3api get-bucket-ownership-controls --bucket "${PT_BUCKET}" aws s3api get-bucket-encryption --bucket "${PT_BUCKET}" aws s3api get-bucket-cors --bucket "${PT_BUCKET}" PLANTTRACER_S3_BUCKET="${PT_BUCKET}" make s3-eventbridge-status
Supply the bucket name, without an
s3://prefix, asImageBucketNamewhen runningSTACK=<name> make sam-deploy-guided(or using the equivalentSTACK_NAME=<name>alias). The SAM template then grants that stack’s Lambda execution roles access to the named bucket and creates an EventBridge rule restricted touploads/<stack-name>/. No bucket policy is required for this same-account setup.
Do not add a bucket-wide expiration rule: objects under movies/ are the
durable archive and must outlive application stacks. S3 Versioning is not
required and is disabled by default. If it is enabled as an operational choice,
remember that application deletes create delete markers and do not immediately
reclaim storage. Cross-account buckets require additional bucket and
EventBridge policies and are outside this procedure.
Current Object Layout¶
PLANTTRACER_STACK_NAME is the deployment identifier used in current object
keys. Application code must use the templates in src/app/constants.py and
the formatting helpers in src/app/s3_presigned.py rather than reconstruct
paths from this page.
Current keys are namespaced by deployment. Legacy un-namespaced keys remain readable through the complete S3 URNs stored in DynamoDB.
The resulting objects are:
Artifact |
Current key |
DynamoDB field |
Lifetime |
|---|---|---|---|
Upload staging |
|
|
Temporary; removed after a verified server-side copy |
Original movie |
|
|
Durable archive |
Traced movie |
|
|
Derived and regenerable |
Analysis-frame ZIP |
|
|
Derived and regenerable |
Persisted JPEG frame |
|
|
Optional and regenerable |
The ZIP key retains the original movie extension for compatibility even though the object contains ZIP bytes. Derived-artifact helpers preserve the original bucket, path, and extension, so legacy un-namespaced URNs remain readable and retraceable. Course migration supports both legacy and deployment-scoped keys.
Upload Processing¶
POST /api/new-movie creates a movie row with status="uploading",
created_at, and upload_bytes_expected. It records both the temporary
upload_staging_urn and durable movie_data_urn and returns a presigned
POST for the staging object. The signed policy requires the exact declared byte
count.
For deployed stacks:
The browser uploads to
uploads/{deployment_id}/....S3 sends an Object Created event to EventBridge.
Each stack’s EventBridge rule matches the shared bucket and its exact
uploads/{deployment_id}/prefix. Other stacks do not match the key.lambda-resize validates the Pydantic event envelope, bucket, deployment, key identifiers, DynamoDB row, and expected byte count.
lambda-resize copies the staging object to its durable
movies/key while preserving metadata, verifies the copy, conditionally recordsuploaded_atandtotal_bytes, and deletes the staging object.The post-upload job records width, height, encoded
fps, frame count, and resize lifecycle timestamps. The browser polls movie metadata before requesting the first frame.
EventBridge and SQS delivery are at least once. Conditional movie updates and verified durable-object checks make duplicate upload events safe. EventBridge retries failed invocations and sends exhausted events to the stack’s upload dead-letter queue.
POST /resize-api/v1/process-upload remains an authenticated local-
development adapter for MinIO, which does not supply the deployed AWS
EventBridge path. It invokes the same completion service; production browsers
do not use it.
Metadata Stored with Original Movies¶
The presigned POST fixes Content-Type to video/mp4 and signs these S3
object metadata fields:
sha256research-usecredit-by-nameattribution-namefpm
The server-side staging-to-durable copy preserves this metadata. Research-use, attribution, and capture-interval metadata are also stored in DynamoDB and, where supported, in MP4 metadata. See Movie attribution and research metadata.
Access, CORS, and Local Development¶
Browser uploads and downloads use time-limited presigned S3 requests. Bucket
CORS must allow the application origin to issue the required methods and
headers. lambda-web and lambda-resize receive the bucket name through
PLANTTRACER_S3_BUCKET and have the read/write permissions required for
their operations.
Local development and integration tests use MinIO and DynamoDB Local, not
mocked clients. AWS_ENDPOINT_URL_S3 selects MinIO and the default local
deployment identifier is local. Use Makefile targets to start services,
create the bucket, and run tests.
Operational and Deployment Artifacts¶
s3://planttracer-artifacts is manually managed project infrastructure for
temporary operational output. Objects under artifacts/ expire; do not put
movie data, .ptb backups, or releases there.
SAM uses its own managed deployment bucket for packaged Lambda artifacts. The application never addresses those keys at runtime.
Implementation Sources¶
src/app/constants.py– canonical templates and deployment identifiersrc/app/s3_presigned.py– key helpers, URNs, presigning, and CORSsrc/app/flask_api.py– staging and durable upload allocationlambda-resize/src/resize_app/upload_event.py– EventBridge validationlambda-resize/src/resize_app/movie_glue.py– upload completion and post-upload processingsrc/app/odb_movie_data.py– object and persisted-frame operationssrc/dbbackup.py– backup/restore and course migrationetc/s3_upload_trigger.py– shared-bucket EventBridge enablementtemplate.yaml– per-stack EventBridge rule, retry, and dead-letter queue