--- title: "Versioning and object lock" slug: "dev-datastore-guides-versioning-and-object-lock" updated: 2026-10-28T02:15:03Z published: 2026-10-28T02:15:03Z canonical: "docs.vastdata.com/dev-datastore-guides-versioning-and-object-lock" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.vastdata.com/llms.txt > Use this file to discover all available pages before exploring further. # Versioning and object lock *Applies to VAST Cluster 5.5.* With versioning, a bucket keeps every earlier version of an object when it's overwritten or deleted. With object locking, versions can also be protected from deletion, for a retention period or under a legal hold. This guide shows how to work with versions (list, read, undelete, restore, delete for good) and with legal holds and retention, and what's different from AWS. It uses the prefix `docs-versioning-and-object-lock/` in two buckets your platform team has set up, and the last block deletes every version it created. Each code block is complete on its own. Below each block is the output you should see. ## Before you start Versioning and object locking can't be turned off once they're on, so these examples need buckets set aside for them. **Admin task: send to your platform team:** ask for - a bucket with versioning enabled, for the versioning examples ([Object Versioning](/docs-5-5/docs/object-versioning)), and - a bucket with object locking enabled and no default retention period, for the object-lock examples. It must be on a view with no protocol other than S3 ([S3 Object Locking](/docs-5-5/docs/s3-object-locking)). Your key needs permission to list, read and delete object versions, and to set and read legal holds and retention. The bucket owner can do all of this unless a policy says otherwise. Set these variables in addition to the ones from the [quickstart](/develop/docs/dev-datastore-quickstart#before-you-start): ``` export DATASTORE_VERSIONED_BUCKET="" export DATASTORE_LOCK_BUCKET="" ``` ## What's different from AWS - **Versioning can't be suspended.** Once it's enabled on a bucket, it stays enabled. `PutBucketVersioning` with `Suspended` is refused with `501 NotImplemented`. - **No MFA Delete.** - **Versioned objects are S3-only for writing.** If the bucket's view also has NFS or SMB, those protocols can read versioned objects but not change them. - **No conditional writes in versioned buckets.** `IfNoneMatch="*"` isn't supported there (`501 NotImplemented`). Conditional deletes do work. - **Object locking is for S3-only buckets.** It can't be enabled on a view that also has NFS or SMB, and it can't be disabled. - **Keys must differ by more than a trailing slash** in a versioned bucket: the cluster reserves the trailing slash for its version directories. ## Check the bucket ``` import os import boto3 from botocore.config import Config s3 = boto3.client( "s3", endpoint_url=os.environ["AWS_S3_ENDPOINT_URL"], region_name="us-east-1", config=Config(signature_version="s3v4", s3={"addressing_style": "path"}), ) bucket = os.environ["DATASTORE_VERSIONED_BUCKET"] print("versioning:", s3.get_bucket_versioning(Bucket=bucket).get("Status")) ``` ``` versioning: Enabled ``` ![In a versioned bucket every write of a key adds a version, newest on top. A plain delete adds a delete marker, so the key disappears from listings but its versions remain; deleting the marker brings the newest version back. To restore an earlier version, copy it over the current one. Versioning can't be suspended once it's on. With object locking, a version can carry a retention period, in governance mode, which protects it until a date, and a legal hold, which protects it until someone removes the hold.](https://cdn.us.document360.io/89b91113-5dc6-49db-86ec-57b16dc8ce56/Images/Documentation/ds-versioning.svg?sv=2026-02-06&spr=https&st=2026-11-08T08%3A55%3A45Z&se=2026-11-08T09%3A55%3A45Z&sr=c&sp=r&sig=N2R26NND8LdVP%2BX4q1ePzAjx4E0qnHju9bH1P5SsG50%3D) *Versions, delete markers, retention and legal holds.* ## Write and list versions Every overwrite adds a version. `list_object_versions` returns them, newest first, with `IsLatest` on the current one: ``` import os import boto3 from botocore.config import Config s3 = boto3.client( "s3", endpoint_url=os.environ["AWS_S3_ENDPOINT_URL"], region_name="us-east-1", config=Config(signature_version="s3v4", s3={"addressing_style": "path"}), ) bucket = os.environ["DATASTORE_VERSIONED_BUCKET"] key = "docs-versioning-and-object-lock/config.json" for n in range(1, 4): s3.put_object(Bucket=bucket, Key=key, Body=f'{{"revision": {n}}}'.encode()) versions = s3.list_object_versions(Bucket=bucket, Prefix=key)["Versions"] print(len(versions), "versions") for v in versions: body = s3.get_object(Bucket=bucket, Key=key, VersionId=v["VersionId"])["Body"].read() print("latest" if v["IsLatest"] else "older ", body) ``` ``` 3 versions latest b'{"revision": 3}' older b'{"revision": 2}' older b'{"revision": 1}' ``` A plain `get_object` returns the current version; with `VersionId`, any version. `head_object` takes `VersionId` the same way. ## Delete and undelete Deleting an object in a versioned bucket doesn't remove any data. It adds a **delete marker**, which becomes the current version, so the object looks deleted. Deleting the marker (by its version ID) brings the object back: ``` import os import boto3 from botocore.config import Config from botocore.exceptions import ClientError s3 = boto3.client( "s3", endpoint_url=os.environ["AWS_S3_ENDPOINT_URL"], region_name="us-east-1", config=Config(signature_version="s3v4", s3={"addressing_style": "path"}), ) bucket = os.environ["DATASTORE_VERSIONED_BUCKET"] key = "docs-versioning-and-object-lock/config.json" s3.delete_object(Bucket=bucket, Key=key) try: s3.get_object(Bucket=bucket, Key=key) except ClientError as e: print("after delete:", e.response["Error"]["Code"]) listing = s3.list_object_versions(Bucket=bucket, Prefix=key) markers = listing.get("DeleteMarkers", []) print(len(listing["Versions"]), "versions and", len(markers), "delete marker") s3.delete_object(Bucket=bucket, Key=key, VersionId=markers[0]["VersionId"]) print("after removing the marker:", s3.get_object(Bucket=bucket, Key=key)["Body"].read()) ``` ``` after delete: NoSuchKey 3 versions and 1 delete marker after removing the marker: b'{"revision": 3}' ``` ## Restore an earlier version To make an earlier version current again, copy it onto the same key. The copy becomes a new version, and all the others are kept: ``` import os import boto3 from botocore.config import Config s3 = boto3.client( "s3", endpoint_url=os.environ["AWS_S3_ENDPOINT_URL"], region_name="us-east-1", config=Config(signature_version="s3v4", s3={"addressing_style": "path"}), ) bucket = os.environ["DATASTORE_VERSIONED_BUCKET"] key = "docs-versioning-and-object-lock/config.json" oldest = s3.list_object_versions(Bucket=bucket, Prefix=key)["Versions"][-1] s3.copy_object(Bucket=bucket, Key=key, CopySource={"Bucket": bucket, "Key": key, "VersionId": oldest["VersionId"]}) print("current:", s3.get_object(Bucket=bucket, Key=key)["Body"].read()) print(len(s3.list_object_versions(Bucket=bucket, Prefix=key)["Versions"]), "versions") ``` ``` current: b'{"revision": 1}' 4 versions ``` The other way to restore is to delete the current version by its version ID: the previous version becomes current. ## Object locking In a bucket with object locking, each object version can have: - **A retention period** with a mode. In **governance** mode, nobody can delete or overwrite the version, shorten its retention or change its mode, unless they have the `s3:BypassGovernanceRetention` permission and send `x-amz-bypass-governance-retention: true` (`BypassGovernanceRetention=True` in boto3). You can extend a retention period at any time. - **A legal hold,** which protects the version with no end date until someone with `s3:PutObjectLegalHold` removes it. It's independent of retention. - **Nothing.** If the bucket has a default retention period, each new version gets it automatically. Protection applies to versions. Deleting a locked object without a version ID still works: it adds a delete marker, and the protected version stays. Design for governance mode and legal holds. In compliance mode, no one can delete the version or shorten its retention; ask your platform team whether compliance mode is available on your cluster before you depend on it. ## Use a legal hold Put a legal hold on a version, see that it can't be deleted, then release it and delete the version: ``` import os import boto3 from botocore.config import Config from botocore.exceptions import ClientError s3 = boto3.client( "s3", endpoint_url=os.environ["AWS_S3_ENDPOINT_URL"], region_name="us-east-1", config=Config(signature_version="s3v4", s3={"addressing_style": "path"}), ) bucket = os.environ["DATASTORE_LOCK_BUCKET"] key = "docs-versioning-and-object-lock/evidence.txt" lock = s3.get_object_lock_configuration(Bucket=bucket)["ObjectLockConfiguration"] print("object lock:", lock["ObjectLockEnabled"], "| default retention:", "Rule" in lock) version = s3.put_object(Bucket=bucket, Key=key, Body=b"keep me")["VersionId"] s3.put_object_legal_hold(Bucket=bucket, Key=key, VersionId=version, LegalHold={"Status": "ON"}) print("legal hold:", s3.get_object_legal_hold(Bucket=bucket, Key=key, VersionId=version)["LegalHold"]["Status"]) try: s3.delete_object(Bucket=bucket, Key=key, VersionId=version) except ClientError as e: print("delete while on hold:", e.response["Error"]["Code"]) s3.put_object_legal_hold(Bucket=bucket, Key=key, VersionId=version, LegalHold={"Status": "OFF"}) s3.delete_object(Bucket=bucket, Key=key, VersionId=version) print("deleted after release") ``` ``` object lock: Enabled | default retention: False legal hold: ON delete while on hold: AccessDenied deleted after release ``` ## Set a retention period Retention is set per version with `put_object_retention`, or per upload with `ObjectLockMode` and `ObjectLockRetainUntilDate` on `put_object`. This fragment isn't run here, because a version under retention can't be deleted until the date passes unless you have the bypass permission: ``` from datetime import datetime, timedelta, timezone until = datetime.now(timezone.utc) + timedelta(days=1) s3.put_object_retention( Bucket=bucket, Key=key, VersionId=version, Retention={"Mode": "GOVERNANCE", "RetainUntilDate": until}, ) print(s3.get_object_retention(Bucket=bucket, Key=key, VersionId=version)["Retention"]) # Removing it early: only with s3:BypassGovernanceRetention. s3.delete_object(Bucket=bucket, Key=key, VersionId=version, BypassGovernanceRetention=True) ``` `get_object` and `head_object` return the lock mode, retain-until date and legal-hold status (`ObjectLockMode`, `ObjectLockRetainUntilDate`, `ObjectLockLegalHoldStatus`) if your key may read them. ## Clean up Delete every version and delete marker under the prefix, in both buckets. Deleting by version ID is permanent: ``` import os import boto3 from botocore.config import Config s3 = boto3.client( "s3", endpoint_url=os.environ["AWS_S3_ENDPOINT_URL"], region_name="us-east-1", config=Config(signature_version="s3v4", s3={"addressing_style": "path"}), ) prefix = "docs-versioning-and-object-lock/" buckets = {"versioned bucket": os.environ["DATASTORE_VERSIONED_BUCKET"], "lock bucket": os.environ["DATASTORE_LOCK_BUCKET"]} for label, bucket in buckets.items(): listing = s3.list_object_versions(Bucket=bucket, Prefix=prefix) entries = listing.get("Versions", []) + listing.get("DeleteMarkers", []) for v in entries: s3.delete_object(Bucket=bucket, Key=v["Key"], VersionId=v["VersionId"]) print(f"{label}: {len(entries)} versions and markers deleted") ``` ``` versioned bucket: 4 versions and markers deleted lock bucket: 0 versions and markers deleted ``` ## See also - [S3 objects](/develop/docs/dev-datastore-guides-s3-objects) and [multiprotocol access](/develop/docs/dev-datastore-guides-multiprotocol#versioned-and-locked-buckets). - KB: [Object Versioning](/docs-5-5/docs/object-versioning), [S3 Object Locking](/docs-5-5/docs/s3-object-locking) and, for read-only views, [Write Once Read Many (WORM) Views](/docs-5-5/docs/write-once-read-many-worm-views).