s3lock

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 21, 2026 License: MIT Imports: 14 Imported by: 0

README

s3lock

CI Go Reference

s3lock is a locking command using S3.

Exclusive control is implemented using conditional writes.

$ s3lock lock s3://my-bucket/lock-object
s3://my-bucket/lock-object has been locked
create lock-object.lock

# A locked object cannot be double-locked
$ s3lock lock s3://my-bucket/lock-object
s3lock: error: lock already held

$ s3lock unlock lock-object.lock
s3://my-bucket/lock-object has been unlocked
delete lock-object.lock

Installation

brew install winebarrel/s3lock/s3lock

Usage

Usage: s3lock <command> [flags]

Flags:
  -h, --help       Show context-sensitive help.
      --version

Commands:
  lock <s3-url> [flags]

  unlock <lock-file> [flags]

Run "s3lock <command> --help" for more information on a command.
s3lock lock
Usage: s3lock lock <s3-url> [flags]

Arguments:
  <s3-url>    S3 URL of the object to lock, e.g., s3://bucket/lock-obj-key

Flags:
  -h, --help             Show context-sensitive help.
      --version

  -w, --wait=UINT        Wait for the specified number of seconds until it locks.
  -o, --output=STRING    Lock file output path (default: <lock-obj-key>.lock)
  -f, --force            Force lock by overwriting any existing lock.
s3lock unlock
Usage: s3lock unlock <lock-file> [flags]

Arguments:
  <lock-file>    Lock file path.

Flags:
  -h, --help       Show context-sensitive help.
      --version

  -f, --force      Ignore unlocked errors and lock mismatch errors.
Use as a library
package main

import (
	"context"
	"log"

	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/winebarrel/s3lock"
)

func main() {
	ctx := context.Background()

	cfg, _ := config.LoadDefaultConfig(ctx)
	s3cli := s3.NewFromConfig(cfg)

	obj := s3lock.New(s3cli, "my-bucket", "lock-object")
	lock, err := obj.Lock(ctx)

	if err != nil {
		log.Fatal(err)
	}

	defer lock.Unlock()

	// ...
}

Lock Sequence

sequenceDiagram
    autonumber
    actor U as User
    participant C as s3lock CLI
    participant O as s3lock.Object
    participant S as Amazon S3

    U->>C: s3lock lock s3://bucket/key [--wait N] [--force]
    C->>O: New(s3Client, bucket, key)

    alt No --wait, no --force
        C->>O: Lock(ctx)
        O->>S: PutObject(body=uuid, If-None-Match="*")
        alt Object does not exist
            S-->>O: 200 OK (ETag)
            O-->>C: Lock{id, etag}
            C->>C: Save lock JSON to output file
            C-->>U: locked + lock file created
        else Lock already exists
            S-->>O: 412 Precondition Failed
            O-->>C: ErrLockAlreadyHeld
            C-->>U: error: lock already held
        end
    else --force only
        C->>O: ForceLock(ctx)
        O->>S: PutObject(body=uuid)
        S-->>O: 200 OK (ETag)
        O-->>C: Lock{id, etag}
        C->>C: Save lock JSON to output file
        C-->>U: locked + lock file created
    else --wait N only
        C->>C: context.WithTimeout(N seconds)
        C->>O: LockWait(ctx)
        O->>S: PutObject(body=uuid, If-None-Match="*")
        alt First attempt succeeds
            S-->>O: 200 OK (ETag)
            O-->>C: Lock{id, etag}
            C->>C: Save lock JSON to output file
            C-->>U: locked + lock file created
        else First attempt returns ErrLockAlreadyHeld
            S-->>O: 412 Precondition Failed
            loop Every LockWaitInterval (default: 1s)
                O->>S: PutObject(body=uuid, If-None-Match="*")
                alt Succeeds
                    S-->>O: 200 OK (ETag)
                    O-->>C: Lock{id, etag}
                    C->>C: Save lock JSON to output file
                    C-->>U: locked + lock file created
                else Still locked
                    S-->>O: 412 Precondition Failed
                    O-->>O: Continue retrying
                end
            end
            alt Timeout / context done
                O-->>C: ErrLockAlreadyHeld
                C-->>U: error: lock already held
            end
        end
    else --wait N --force
        C->>C: context.WithTimeout(N seconds)
        C->>O: ForceLockWait(ctx)
        O->>S: PutObject(body=uuid, If-None-Match="*")
        alt First attempt succeeds
            S-->>O: 200 OK (ETag)
            O-->>C: Lock{id, etag}
            C->>C: Save lock JSON to output file
            C-->>U: locked + lock file created
        else First attempt returns ErrLockAlreadyHeld
            S-->>O: 412 Precondition Failed
            loop Every LockWaitInterval (default: 1s)
                O->>S: PutObject(body=uuid, If-None-Match="*")
                alt Succeeds
                    S-->>O: 200 OK (ETag)
                    O-->>C: Lock{id, etag}
                    C->>C: Save lock JSON to output file
                    C-->>U: locked + lock file created
                else Still locked
                    S-->>O: 412 Precondition Failed
                    O-->>O: Continue retrying
                end
            end
            alt Timeout / context done
                O->>S: PutObject(body=uuid) [force, no If-None-Match]
                S-->>O: 200 OK (ETag)
                O-->>C: Lock{id, etag}
                C->>C: Save lock JSON to output file
                C-->>U: locked + lock file created
            end
        end
    end

Unlock Sequence

sequenceDiagram
    autonumber
    actor U as User
    participant C as s3lock CLI
    participant L as s3lock.Lock
    participant S as Amazon S3

    U->>C: s3lock unlock lock-file [--force]
    C->>C: Read lock file JSON
    C->>L: NewLockFromJSON(s3Client, data)
    C->>L: Unlock()

    L->>L: validate(ctx)
    L->>S: GetObject(If-Match=etag)

    alt 200 OK and body matches id
        S-->>L: 200 OK (body=id)
        L->>S: DeleteObject(If-Match=etag)
        S-->>L: Success
        L-->>C: nil
        C->>C: Delete lock file
        C-->>U: unlocked + lock file deleted
    else 404 Not Found
        S-->>L: 404 Not Found
        L-->>C: ErrAlreadyUnlocked
        alt --force
            C->>C: Ignore error, delete lock file
            C-->>U: unlocked + lock file deleted
        else No --force
            C-->>U: error: already unlocked
        end
    else 412 Precondition Failed (ETag mismatch)
        S-->>L: 412 Precondition Failed
        L-->>C: ErrLockMismatch
        alt --force
            C->>C: Ignore error, delete lock file
            C-->>U: unlocked + lock file deleted
        else No --force
            C-->>U: error: lock mismatch
        end
    else 200 OK but body does not match id
        S-->>L: 200 OK (body≠id)
        L-->>C: ErrLockMismatch
        alt --force
            C->>C: Ignore error, delete lock file
            C-->>U: unlocked + lock file deleted
        else No --force
            C-->>U: error: lock mismatch
        end
    end

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrLockAlreadyHeld = errors.New("lock already held")
	ErrAlreadyUnlocked = errors.New("already unlocked")
	ErrLockMismatch    = errors.New("lock mismatch")
)
View Source
var LockWaitInterval = 1 * time.Second

Functions

This section is empty.

Types

type Lock

type Lock struct {
	// contains filtered or unexported fields
}

func NewLockFromJSON

func NewLockFromJSON(s3Client *s3.Client, data []byte) (*Lock, error)

func (*Lock) MarshalJSON

func (l *Lock) MarshalJSON() ([]byte, error)

func (*Lock) String added in v0.2.0

func (l *Lock) String() string

func (*Lock) Unlock

func (l *Lock) Unlock() error

func (*Lock) UnlockContext

func (l *Lock) UnlockContext(ctx context.Context) error

type Object

type Object struct {
	// contains filtered or unexported fields
}

func New

func New(s3Client *s3.Client, bucket string, key string) *Object

func (*Object) ForceLock added in v0.3.0

func (obj *Object) ForceLock(ctx context.Context) (*Lock, error)

func (*Object) ForceLockWait added in v0.3.0

func (obj *Object) ForceLockWait(ctx context.Context) (*Lock, error)

func (*Object) Lock

func (obj *Object) Lock(ctx context.Context) (*Lock, error)

func (*Object) LockWait

func (obj *Object) LockWait(ctx context.Context) (*Lock, error)

Directories

Path Synopsis
cmd
s3lock command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL