Fil.Adapter.S3 (Fil v0.1.0)

Copy Markdown View Source

Amazon S3 and services that implement its API, such as MinIO, Adobe S3Mock, Cloudflare R2, Backblaze B2, Tigris and Ceph.

disk =
  Fil.disk(
    adapter: Fil.Adapter.S3,
    bucket: "invoices",
    region: "eu-central-1",
    access_key_id: System.fetch_env!("AWS_ACCESS_KEY_ID"),
    secret_access_key: System.fetch_env!("AWS_SECRET_ACCESS_KEY")
  )

Requests are sent and signed (SigV4) by Req, configured with :req_options. Listings are parsed with OTP's :xmerl_sax_parser.

Checksums

S3 stores a checksum with an object when the write sends one. With checksum: :sha256 (or :sha1, :crc32), Fil.write/4 sends the checksum of the content, S3 rejects the upload if what it received doesn't match, and Fil.stat/3 with the same :checksum option returns the stored value. Fil.read/3 with verify_checksum: true asks S3 for the stored checksum and compares it with the downloaded content. Objects stored with another algorithm, or with none, are read without a check.

Options

  • :bucket (String.t/0) - Required. The bucket all paths are stored in.

  • :region (String.t/0) - The bucket's region. The default value is "us-east-1".

  • :root (String.t/0) - The key prefix every path is resolved against, e.g. "uploads". Paths on the disk stay relative to it, and listings strip it again. Defaults to the bucket root. The default value is ".".

  • :access_key_id (String.t/0) - The access key. Fil doesn't look up credentials itself (no environment variables, no instance metadata), so where they come from is up to the application. Leave out all credentials to access a public bucket without signing.

  • :secret_access_key (String.t/0) - The secret matching :access_key_id.

  • :session_token (String.t/0) - The session token, for temporary STS credentials.

  • :endpoint (String.t/0) - A base URL for S3-compatible services, e.g. "http://localhost:8333". Setting it turns :path_style on.

  • :path_style (boolean/0) - Put the bucket in the path (https://host/bucket/key) instead of the hostname (https://bucket.host/key). Defaults to true when :endpoint is set, false otherwise.

  • :req_options (keyword/0) - Options for every Req request the disk makes, such as :receive_timeout, :connect_options or a shared :finch pool. The adapter always sets :method, :url, :headers and :body, plus retry: false (retrying is up to the caller) and raw: true (no decompression and no body decoding, so a file reads back exactly as it was written). The default value is [].

Operations

FilS3
read/3GetObject (x-amz-checksum-mode: ENABLED for verify_checksum: true)
write/4PutObject (If-None-Match: * for if_exists: :error, x-amz-checksum-* for checksum:)
rm/3DeleteObject, which S3 already treats as idempotent (a 404 for a missing bucket is still an error)
stat/3HeadObject (x-amz-checksum-mode: ENABLED for checksum:), then a prefix probe so dir?/1 works
ls/3ListObjectsV2, delimiter=/ unless recursive, paginated internally
cp/4CopyObject
rename/4CopyObject, then DeleteObject
rm_rf/3ListObjectsV2, then one DeleteObject per key
url/2the object URL, without a signature (works for public objects only)
signed_url/3a presigned GET or PUT URL

Errors

:reason is the error code from the response body ("NoSuchKey"), or {:http_status, status} when there's none. Failures before a response keep Req's reason (:timeout, an exception). The error code decides first, whatever the status, because S3-compatible servers don't all send the same one.

S3 responseFil error
NoSuchKey, or 404Fil.NotFoundError
NoSuchBucketFil.ConfigurationError
a 400 for a copy whose source doesn't exist (checked with HeadObject)Fil.NotFoundError
AccessDenied, or 403Fil.AccessDeniedError
EntityTooLarge, KeyTooLongErrorFil.InvalidRequestError
PreconditionFailed, ConditionalRequestConflictFil.AlreadyExistsError
412, or a 409 without a codeFil.AlreadyExistsError
BadDigestFil.ChecksumMismatchError
a body that doesn't match its stored checksumFil.ChecksumMismatchError, reason: :checksum_mismatch
a signed URL on a disk without credentialsFil.UnsupportedError, reason: :missing_credentials
301, or a 400 that gives another regionFil.ConfigurationError, reason: {:wrong_region, region}
SlowDown, OperationAborted, InternalError, ServiceUnavailableFil.UnavailableError
429, 5xxFil.UnavailableError
timeouts, failed connections, an unreadable listingFil.UnavailableError
anything else, including an error inside the 200 of a CopyObjectFil.UnknownError

Summary

Types

t()

@type t() :: %Fil.Adapter.S3{
  access_key_id: term(),
  bucket: term(),
  endpoint: term(),
  path_style: term(),
  prefix: term(),
  region: term(),
  req_options: term(),
  secret_access_key: term(),
  session_token: term()
}