clj-s3-client

1.0.0


Clojure AWS s3 client

dependencies

org.clojure/clojure
1.8.0
camel-snake-kebab
0.4.0
com.amazonaws/aws-java-sdk-s3
1.11.75



(this space intentionally left almost blank)
 

Functions to interact with Amazon S3 storage.

All functions take AmazonS3Client instance as the first parameter.

Exception: url->bucket-name-and-object-key

(ns clj-s3-client.core
  (:require [camel-snake-kebab.core :refer [->kebab-case-keyword]]
            [camel-snake-kebab.extras :refer [transform-keys]]
            [clojure.set :refer [difference]])
  (:import [com.amazonaws.services.s3 AmazonS3Client AmazonS3URI]
           [com.amazonaws.services.s3.model CannedAccessControlList PutObjectRequest AmazonS3Exception ObjectMetadata]
           [com.amazonaws ClientConfiguration]
           [com.amazonaws.regions Regions]
           [java.io InputStream]
           [java.nio ByteBuffer]))
(defn- acl->access-control-list [acl]
  (get {:private CannedAccessControlList/Private
        :public-read CannedAccessControlList/PublicRead
        :public-read-write CannedAccessControlList/PublicReadWrite
        :aws-exec-read CannedAccessControlList/AwsExecRead
        :authenticated-read CannedAccessControlList/AuthenticatedRead
        :bucket-owner-read CannedAccessControlList/BucketOwnerRead
        :bucket-owner-full-control CannedAccessControlList/BucketOwnerFullControl
        :log-delivery-write CannedAccessControlList/LogDeliveryWrite} acl CannedAccessControlList/Private))
(defn- object-metadata->map [^ObjectMetadata s3-object-metadata]
  (let [s3-object-raw-metadata (into {} (.getRawMetadata s3-object-metadata))
        s3-user-metadata (into {} (.getUserMetadata s3-object-metadata))]
    (transform-keys ->kebab-case-keyword (conj s3-object-raw-metadata s3-user-metadata))))
(defn- map->object-metadata [metadata]
  (let [object-metadata (ObjectMetadata.)
        supported {:content-length (fn [ob value] (doto ob (.setContentLength value)))
                   :content-type (fn [ob value] (doto ob (.setContentType value)))
                   :content-language (fn [ob value] (doto ob (.setContentLanguage value)))
                   :content-encoding (fn [ob value] (doto ob (.setContentEncoding value)))
                   :content-disposition (fn [ob value] (doto ob (.setContentDisposition value)))
                   :http-expires-date (fn [ob value] (doto ob (.setHttpExpiresDate value)))}
        specific-setters (select-keys supported (keys metadata))
        user-meta-fields (difference (set (keys metadata)) (set (keys specific-setters)))
        user-metadata-setter (reduce (fn [acc k] (assoc acc k (fn [ob value] (doto ob (.addUserMetadata (name k) value))))) {} user-meta-fields)]
    (reduce-kv
      (fn [ob k v] (v ob (get metadata k)))
      object-metadata
      (conj specific-setters user-metadata-setter))))
(defn- suppress-not-found-exception [exception]
  (when-not (= 404 (.getStatusCode exception))
    (throw exception)))

(create-client)

Returns an instance of AmazonS3Client which can be used with all other functions to access S3 resources.

An optional map of options can include any of the following keys:

:max-connections    - the max number of active connections for client.
                      Defaults to 50.

:max-error-retry    - the number of retries before failing.
                      Defaults to 1.

:endpoint           - the endpoint to connect to.

:region             - the region to be used.

:tcp-keep-alive     - set the 'keep connection alive' boolean flag for http connections.
                      Defaults to false.
(defn create-client
  [& {:keys [max-connections max-error-retry endpoint region tcp-keep-alive]
                        :or {max-connections 50 max-error-retry 1 tcp-keep-alive false}}]
  (let [configuration (-> (ClientConfiguration.)
                          (.withMaxErrorRetry max-error-retry)
                          (.withMaxConnections max-connections)
                          (.withTcpKeepAlive tcp-keep-alive))
        client (AmazonS3Client. configuration)]
    (when endpoint
      (.setEndpoint client endpoint))
    (if region
      (.withRegion client (Regions/fromName region))
      client)))

(create-bucket client "my-awesome-bucket")

Creates a bucket bucket-name.

(defn create-bucket
  [^AmazonS3Client client bucket-name]
  (.createBucket client bucket-name))

(delete-bucket client "my-not-so-awesome-bucket")

Deletes the bucket bucket-name.

(defn delete-bucket
  [^AmazonS3Client client bucket-name]
  (.deleteBucket client bucket-name))

(bucket-exists? client "my-awesome-bucket")

Checks if bucket bucket-name exists.

Returns true or false.

(defn bucket-exists?
  [^AmazonS3Client client bucket-name]
  (.doesBucketExist client bucket-name))

(put-object client "my-awesome-bucket" "my-awesome-me.txt" file-input-stream {:acl :private :content-length 1234 :content-type "text/html"})

Store the input stream to s3 with given options.

An optional map of options can include any of the following keys:

:acl                    - sets access rights of the file, supported values are
                            :private
                            :public-read
                            :public-read-write
                            :aws-exec-read
                            :authenticated-read
                            :bucket-owner-read
                            :bucket-owner-full-control
                            :log-delivery-write

:content-length         - the length of the content in bytes.

:content-type           - the mime type of the content (e.g "image/png").

:content-language       - content language of the InputStream (e.g "en").

:content-encoding       - the encoding of the content (e.g. gzip).

:content-disposition    - how the content should be downloaded by browsers (e.g. attachment; filename="filename.jpg").

:http-expires-date      - when the content expires, used in cache headers (e.g. Sun, 7 May 1995 12:45:26 GMT).

The map can also contain other keys which are added as custom headers for the file.

(defn put-object
  [^AmazonS3Client client bucket-name object-key ^InputStream is options]
  (let [metadata (dissoc options :acl)
        acl (:acl options)
        s3-object-metadata (map->object-metadata metadata)
        put-object-req (.withCannedAcl
                         (PutObjectRequest. bucket-name object-key is s3-object-metadata)
                         (acl->access-control-list acl))
        put-object-resp (.putObject client put-object-req)
        result-s3-object-metadata (object-metadata->map (.getMetadata put-object-resp))]
    (assoc result-s3-object-metadata :object-key object-key :bucket-name bucket-name :content is)))

(get-object client "my-awesome-bucket" "my-awesome-me.txt")

Fetches the object from bucket bucket-name with the key object-key. Metadata and content (input stream) are included.

Returns nil if the object is not found.

(defn get-object
  [^AmazonS3Client client bucket-name object-key]
  (try
    (let [s3-object (.getObject client bucket-name object-key)
          s3-object-metadata (object-metadata->map (.getObjectMetadata s3-object))]
      (assoc s3-object-metadata :object-key (.getKey s3-object) :bucket-name (.getBucketName s3-object) :content (.getObjectContent s3-object)))
    (catch AmazonS3Exception e (suppress-not-found-exception e))))

(delete-object client "my-awesome-bucket" "my-awesome-me.txt")

Destroys the object object-key from bucket bucket-name.

(defn delete-object
  [^AmazonS3Client client bucket-name object-key]
  (.deleteObject client bucket-name object-key))

(bucket-name-and-object-key->url client "my-awesome-bucket" "my-awesome-me.txt")

Returns the corresponding url of bucket-name and object-key as a string.

However, depending on the permissions it might or might not work.

(defn bucket-name-and-object-key->url
  [^AmazonS3Client client bucket-name object-key]
  (str (.getUrl client bucket-name object-key)))

(url->bucketname-and-object-key "my-awesome-bucket" "my-awesome-me.txt")

Returns the bucket-name and object-key of url as a map.

(defn url->bucket-name-and-object-key
  [url]
  (let [s3-uri (AmazonS3URI. url true)
        bucket-name (.getBucket s3-uri)
        object-key (.getKey s3-uri)]
    {:bucket-name bucket-name
     :object-key object-key}))