Skip to content

Commit

Permalink
Changing to connect everytime
Browse files Browse the repository at this point in the history
Since we had problems using this lib in bob, we decided to change the
behaviour to connect everytime you call invoke. The connect function is
now deprecated and only returns a map to avoid breaking the api. You
still are able to use client like before but it is discouraged now
to use connect and only using invoke is also sufficient.

I moved the connect-function to the requests namespace because
it is not part of the official api but is used with the fetch-function
and therefore should reside with it. The documentation is changed
accordingly and fetch needs to call the for now unofficial function
connect*.

Refers into-docker#20
  • Loading branch information
TimoKramer committed Mar 22, 2020
1 parent db8c81d commit bc675f8
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 93 deletions.
48 changes: 22 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,23 +75,8 @@ This assumes Java 11+:

This library aims to be a _as thin layer as possible_ between you and Docker. This consists of following public functions:

#### connect
Connect to the docker daemon's [UNIX socket](https://en.wikipedia.org/wiki/Unix_domain_socket) and create a connection.
```clojure
(def conn (docker/connect {:uri "unix:///var/run/docker.sock"}))
```
Takes optional timeout parameters in **ms**:
```clojure
;; All values default to 0, which signifies no timeout.
(def conn (connect {:uri "unix:///var/run/docker.sock"
:connect-timeout 10
:read-timeout 2000
:write-timeout 2000
:call-timeout 30000}))
```
Thanks [olymk2](https://github.com/olymk2) for the suggestion.

#### categories

Lists the categories of operations supported. Can be bound to an API version.
```clojure
(docker/categories) ; Latest version
Expand Down Expand Up @@ -123,15 +108,28 @@ Lists the categories of operations supported. Can be bound to an API version.
```

#### client
Creates a client scoped to the operations of a given category. Can be bound to an API version.

Connect to the docker daemon's [UNIX socket](https://en.wikipedia.org/wiki/Unix_domain_socket) and
create a client scoped to the operations of a given category. Can be bound to an API version.
```clojure
(def images (docker/client {:category :images
:conn conn})) ; Latest version
:conn {:uri "unix:///var/run/docker.sock"}})) ; Latest version

(def containers (docker/client {:category :containers
:conn conn
:conn {:uri "unix:///var/run/docker.sock"}
:api-version "v1.40"})) ; Container client for v1.40
```
Using a timeout for the connections. Thanks [olymk2](https://github.com/olymk2) for the suggestion.
Docker actions can take quite a long time so set the timeout accordingly. When you don't provide timeouts
then there will be no timeout clientside.
```
(def ping (docker/client {:category :_ping
:conn {:uri "unix:///var/run/docker.sock"
:timeouts {:connect-timeout 10
:read-timeout 30000
:write-timeout 30000
:call-timeout 30000}}}))
```

#### ops
Lists the supported ops by a client.
Expand Down Expand Up @@ -201,10 +199,8 @@ Takes an optional key `as`. Defaults to `:data`. Returns an InputStream if passe

#### Pulling an image
```clojure
(def conn (docker/connect {:uri "unix:///var/run/docker.sock"}))

(def images (docker/client {:category :images
:conn conn}))
:conn {:uri "unix:///var/run/docker.sock"}}))

(docker/invoke images {:op :ImageCreate
:params {:fromImage "busybox:musl"}})
Expand All @@ -213,7 +209,7 @@ Takes an optional key `as`. Defaults to `:data`. Returns an InputStream if passe
#### Creating a container
```clojure
(def containers (docker/client {:category :containers
:conn conn}))
:conn {:uri "unix:///var/run/docker.sock"}}))

(docker/invoke containers {:op :ContainerCreate
:params {:name "conny"
Expand Down Expand Up @@ -290,18 +286,18 @@ fetch takes the following params as a map:

;; This is the undocumented API in the Docker Daemon.
;; See https://github.com/moby/moby/pull/22049/files#diff-8038ade87553e3a654366edca850f83dR11
(req/fetch {:conn (docker/connect {:uri "unix:///var/run/docker.sock"})
(req/fetch {:conn (req/connect* {:uri "unix:///var/run/docker.sock"})
:url "/v1.40/containers/conny/checkpoints"})
```

More examples of low level calls:
```clojure
;; Ping the server
(req/fetch {:conn (docker/connect {:uri "unix:///var/run/docker.sock"})
(req/fetch {:conn (req/connect* {:uri "unix:///var/run/docker.sock"})
:url "/v1.40/_ping"})

;; Copy a folder to a container
(req/fetch {:conn (docker/connect {:uri "unix:///var/run/docker.sock"})
(req/fetch {:conn (req/connect* {:uri "unix:///var/run/docker.sock"})
:url "/v1.40/containers/conny/archive"
:method :put
:query {:path "/root/src"}
Expand Down
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
; You should have received a copy of the GNU Lesser General Public License
; along with clj-docker-client. If not, see <http://www.gnu.org/licenses/>.

(defproject lispyclouds/clj-docker-client "0.5.1"
(defproject lispyclouds/clj-docker-client "0.5.2"
:author "Rahul De <rahul@mailbox.org>"
:url "https://github.com/lispyclouds/clj-docker-client"
:description "An idiomatic data-driven clojure client for Docker."
Expand Down
67 changes: 25 additions & 42 deletions src/clj_docker_client/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -17,44 +17,16 @@
(:require [clojure.string :as s]
[jsonista.core :as json]
[clj-docker-client.requests :as req]
[clj-docker-client.specs :as spec])
(:import (java.time Duration)
(java.net URI)
(okhttp3 OkHttpClient$Builder)))
[clj-docker-client.specs :as spec]))

(defn- panic!
"Helper for erroring out for wrong args."
[^String message]
(throw (IllegalArgumentException. message)))

(defn connect
"Connects to the provided :uri in the connection options.
Optionally takes connect, read, write and call timeout in ms.
All are set to 0 by default, which is no timeout.
Returns the connection.
The url must be fully qualified with the protocol.
eg. unix:///var/run/docker.sock or https://my.docker.host:6375"
(defn ^:deprecated connect
"Deprecated but still there for compatibility reasons."
[{:keys [uri connect-timeout read-timeout write-timeout call-timeout]}]
(when (nil? uri)
(panic! ":uri is required"))
(let [uri (URI. uri)
scheme (.getScheme uri)
path (.getPath uri)
{:keys [^OkHttpClient$Builder builder
socket]} (case scheme
"unix" (req/unix-socket-client-builder path)
(panic! (format "Protocol '%s' not supported yet." scheme)))
timeout-from #(Duration/ofMillis (or % 0))
builder+opts (-> builder
(.connectTimeout (timeout-from connect-timeout))
(.readTimeout (timeout-from read-timeout))
(.writeTimeout (timeout-from write-timeout))
(.callTimeout (timeout-from call-timeout)))]
{:client (.build builder+opts)
:socket socket}))
(let [timeouts {:connect-timeout connect-timeout
:read-timeout read-timeout
:write-timeout write-timeout
:call-timeout call-timeout}]
{:uri uri :timeouts timeouts}))

(defn categories
"Returns the available categories.
Expand All @@ -76,7 +48,7 @@
Examples are: :containers, :images, etc"
[{:keys [category conn api-version] :as args}]
(when (some nil? [category conn])
(panic! ":category, :conn are required"))
(req/panic! ":category, :conn are required"))
(assoc args
:paths
(:paths (spec/get-paths-of-category category api-version))))
Expand All @@ -94,7 +66,7 @@
"Returns the doc of the supplied category and operation"
[{:keys [category api-version]} operation]
(when (nil? category)
(panic! ":category is required"))
(req/panic! ":category is required"))
(update-in
(select-keys (spec/request-info-of category
operation
Expand All @@ -118,10 +90,10 @@
If a :socket is requested, the underlying UNIX socket is returned."
[{:keys [category conn api-version]} {:keys [op params as]}]
(when (some nil? [category conn op])
(panic! ":category, :conn are required in client, :op is required in operation map"))
(req/panic! ":category, :conn are required in client, :op is required in operation map"))
(let [request-info (spec/request-info-of category op api-version)
_ (when (nil? request-info)
(panic! "Invalid params for invoking op."))
(req/panic! "Invalid params for invoking op."))
{:keys [body
query
header
Expand All @@ -130,7 +102,7 @@
(reduce (partial spec/gather-request-params
params)
{}))
response (req/fetch {:conn conn
response (req/fetch {:conn (req/connect* {:uri (:uri conn) :timeouts (:timeouts conn)})
:url (:path request-info)
:method (:method request-info)
:query query
Expand All @@ -155,6 +127,8 @@

(connect {:uri "unix:///var/run/docker.sock"})

(connect* {:uri "unix:///var/run/docker.sock"})

(connect {:uri "https://my.docker.host:6375"})

(req/fetch {:conn (connect {:uri "unix:///var/run/docker.sock"})
Expand Down Expand Up @@ -183,6 +157,11 @@
:write-timeout 0
:call-timeout 0}))

(def ping (client {:category :_ping
:conn conn}))

(invoke ping {:op :SystemPing})

(categories)

(categories "v1.40")
Expand All @@ -192,7 +171,11 @@
:api-version "v1.40"}))

(def images (client {:category :images
:conn conn}))
:conn {:uri "unix:///var/run/docker.sock"}}))

(invoke {:category :_ping
:conn {:uri "unix:///var/run/docker.sock"}}
{:op :SystemPing})

(ops images)

Expand Down
41 changes: 40 additions & 1 deletion src/clj_docker_client/requests.clj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
(:require [clojure.string :as s]
[jsonista.core :as json])
(:import (java.io InputStream)
(java.time Duration)
(java.net URI)
(java.util.regex Pattern)
(okhttp3 HttpUrl
HttpUrl$Builder
Expand All @@ -41,6 +43,40 @@
{:socket (.getSocket socket-factory)
:builder builder}))

(defn panic!
"Helper for erroring out for wrong args."
[^String message]
(throw (IllegalArgumentException. message)))

(defn connect*
"Connects to the provided :uri in the connection options.
Optionally takes connect, read, write and call timeout in ms.
All are set to 0 by default, which is no timeout.
Returns the connection.
The url must be fully qualified with the protocol.
eg. unix:///var/run/docker.sock or https://my.docker.host:6375"
[{:keys [uri timeouts]}]
(when (nil? uri)
(panic! ":uri is required"))
(let [uri (URI. uri)
scheme (.getScheme uri)
path (.getPath uri)
{:keys [^OkHttpClient$Builder builder
socket]} (case scheme
"unix" (unix-socket-client-builder path)
(panic! (format "Protocol '%s' not supported yet." scheme)))
timeout-from #(Duration/ofMillis (or % 0))
builder+opts (-> builder
(.connectTimeout (timeout-from (:connect-timeout timeouts)))
(.readTimeout (timeout-from (:read-timeout timeouts)))
(.writeTimeout (timeout-from (:write-timeout timeouts)))
(.callTimeout (timeout-from (:call-timeout timeouts))))]
{:client (.build builder+opts)
:socket socket}))

(defn stream->req-body
"Converts an InputStream to OkHttp RequestBody."
[^InputStream stream]
Expand Down Expand Up @@ -148,4 +184,7 @@
(interpolate-path "a/{id}/path/to/{something-else}/and/{xid}/{not-this}"
{:id "a-id"
:xid "b-id"
:something-else "stuff"}))
:something-else "stuff"})

(fetch {:conn (connect* {:uri "unix:///var/run/docker.sock"})
:url "/v1.40/containers/conny/checkpoints"}))
24 changes: 6 additions & 18 deletions test/clj_docker_client/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,15 @@
(ns clj-docker-client.core-test
(:require [clojure.test :refer :all]
[clojure.java.io :as io]
[clj-docker-client.core :refer :all]))
[clj-docker-client.core :refer :all])
(:import (clj_docker_client.socket TunnelingUnixSocket)
(okhttp3 OkHttpClient)))

(def latest-version "v1.40")

(deftest docker-connection
(testing "successful connection to the socket"
(is (map? (connect {:uri "unix:///var/run/docker.sock"}))))

(testing "connection with usupported protocol"
(is (thrown? IllegalArgumentException
(connect {:uri "http://this-does-not-work"}))))

(testing "connection with set timeouts"
(let [{:keys [client]} (connect {:uri "unix:///var/run/docker.sock"
:connect-timeout 10
:read-timeout 2000
:write-timeout 3000})]
(is (and (= 10 (.connectTimeoutMillis client))
(= 2000 (.readTimeoutMillis client))
(= 3000 (.writeTimeoutMillis client))
(= 0 (.callTimeoutMillis client)))))))
(deftest docker-connect-map
(testing "deprecated connect returns map"
(is (map? (connect {:uri "unix:///var/run/docker.sock"})))))

(deftest fetch-categories
(testing "listing all available categories in latest version"
Expand Down
30 changes: 25 additions & 5 deletions test/clj_docker_client/requests_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
(:require [clojure.test :refer :all]
[clojure.string :as s]
[clojure.java.io :as io]
[clj-docker-client.requests :refer :all]
[clj-docker-client.core :as core])
[clj-docker-client.requests :refer :all])
(:import (java.io InputStream)
(okhttp3 HttpUrl
HttpUrl$Builder
Expand All @@ -27,6 +26,27 @@
RequestBody
Request$Builder)))

(deftest docker-connection
(testing "successful connection to the socket"
(is (instance? clj_docker_client.socket.TunnelingUnixSocket
(:socket (connect* {:uri "unix:///var/run/docker.sock"}))))
(is (instance? okhttp3.OkHttpClient
(:client (connect* {:uri "unix:///var/run/docker.sock"})))))

(testing "connection with usupported protocol"
(is (thrown? IllegalArgumentException
(connect* {:uri "http://this-does-not-work"}))))

(testing "connection with set timeouts"
(let [{:keys [client]} (connect* {:uri "unix:///var/run/docker.sock"
:timeouts {:connect-timeout 10
:read-timeout 2000
:write-timeout 3000}})]
(is (and (= 10 (.connectTimeoutMillis client))
(= 2000 (.readTimeoutMillis client))
(= 3000 (.writeTimeoutMillis client))
(= 0 (.callTimeoutMillis client)))))))

(deftest unix-sockets
(testing "creating a unix socket client builder"
(let [conn (unix-socket-client-builder "unix:///var/run/docker.sock")]
Expand Down Expand Up @@ -119,17 +139,17 @@

(deftest fetching-stuff
(testing "normal response"
(is (= "OK" (fetch {:conn (core/connect {:uri "unix:///var/run/docker.sock"})
(is (= "OK" (fetch {:conn (connect* {:uri "unix:///var/run/docker.sock"})
:url "/_ping"}))))

(testing "streaming response"
(is (instance? InputStream
(fetch {:conn (core/connect {:uri "unix:///var/run/docker.sock"})
(fetch {:conn (connect* {:uri "unix:///var/run/docker.sock"})
:url "/_ping"
:as :stream}))))

(testing "exposing socket"
(let [socket (fetch {:conn (core/connect {:uri "unix:///var/run/docker.sock"})
(let [socket (fetch {:conn (connect* {:uri "unix:///var/run/docker.sock"})
:url "/_ping"
:as :socket})]
(is (instance? java.net.Socket socket))
Expand Down

0 comments on commit bc675f8

Please sign in to comment.