├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── project.clj ├── resources └── swagger │ └── v1.json ├── scripts └── get_swagger_json.sh ├── src └── kubernetes │ └── api │ ├── swagger.clj │ ├── util.clj │ └── v1.clj └── test └── kubernetes └── api └── v1_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | 13 | doc 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased][unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2015-12-02 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2015-12-02 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [unreleased]: https://github.com/your-name/kubernetes.api/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/kubernetes.api/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jon Eisen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kubernetes-api 2 | 3 | Kubernetes client API library for Clojure. Functions are generated using macros derived from offical [swagger spec](http://kubernetes.io/swagger-spec/api/v1). 4 | 5 | `codox` documentation available at http://joneisen.me/clj-kubernetes-api/ 6 | 7 | ## Installation 8 | 9 | Add the dependency to your `project.clj`. 10 | 11 | [![Clojars Project](http://clojars.org/kubernetes-api/latest-version.svg)](http://clojars.org/kubernetes-api) 12 | 13 | ## Usage 14 | 15 | First, run a kubernetes proxy with `kubectl proxy --port=8080`. 16 | 17 | Each endpoint function returns a [core.async](https://github.com/clojure/core.async) channel. Under the covers, the [http-kit](www.http-kit.org) http client is used. 18 | 19 | ```clojure 20 | (require '[kubernetes.api.v1 :as k8s] 21 | '[clojure.core.async :refer [" 6 | exit 1 7 | fi 8 | 9 | curl -f "http://kubernetes.io/swagger-spec/api/${1}/" -o resources/swagger/${1}.json 10 | -------------------------------------------------------------------------------- /src/kubernetes/api/swagger.clj: -------------------------------------------------------------------------------- 1 | (ns kubernetes.api.swagger 2 | "Render client API functions from swagger doc using macros" 3 | (:require [clojure.string :as str] 4 | [clojure.java.io :as io] 5 | [clojure.data.json :as json] 6 | [kubernetes.api.util :as util])) 7 | 8 | 9 | (defn capital? [c] (let [i (int c)] (and (<= i 90) (>= i 65)))) 10 | 11 | (defn camel->dashed [nickname] 12 | (-> nickname 13 | (str/replace #"([a-z])([A-Z])" "$1-$2") 14 | (str/replace #"([A-Z]+)([A-Z][a-z])" "$1-$2") 15 | str/lower-case)) 16 | 17 | (defn renderable-param? [{:keys [name] :as param}] 18 | (not (#{"watch" "pretty" "timeoutSeconds" "resourceVersion"} name))) 19 | 20 | (defn render-doc-param [{:keys [type name description 21 | required allowMultiple]}] 22 | (str (camel->dashed name) 23 | ": " (if required "required" "optional") 24 | " " (if allowMultiple (str "[" type "]") type) 25 | " " description)) 26 | 27 | (defn body-param-type [params] 28 | (->> params 29 | (filter #(= (:paramType %) "body")) 30 | first 31 | :type)) 32 | 33 | (defn render-doc-str [method path summary ret-type params] 34 | (str "\nCalls " method " on " path 35 | "\n" summary 36 | "\nParameters:" 37 | "\n\t- ctx: required server context" 38 | (if (not= method "GET") 39 | (str "\n\t- body: required " (body-param-type params)) 40 | "") 41 | (if (not-empty params) 42 | (str "\nOptions:\n\t- " (str/join "\n\t- " (map render-doc-param params)))) 43 | "\nReturns " ret-type)) 44 | 45 | (defn renderable-op? 46 | "Return true if op is renderable" 47 | [{:keys [produces nickname] :as op}] 48 | (and (some #{"application/json"} produces) 49 | (not (.startsWith nickname "watch")))) 50 | 51 | (defn param-by-f [params f] 52 | (->> params 53 | (filter f) 54 | (map :name) 55 | (map camel->dashed) 56 | (map keyword) 57 | vec)) 58 | (defn param-by-type [params type] (param-by-f params #(= (:paramType %) type))) 59 | 60 | (defn render-op [{:keys [path] :as api} 61 | {:keys [nickname summary method type] 62 | params :parameters 63 | [{ret-type :responseModel}] :responseMessages 64 | :as op}] 65 | (let [params (filter renderable-param? params) 66 | fn-name (symbol (camel->dashed nickname)) 67 | doc-str (render-doc-str method path summary ret-type params) 68 | method-kw (-> method str/lower-case keyword) 69 | body-param (first (param-by-type params "body"))] 70 | `(defn ~fn-name ~doc-str [ctx# & [body?# opts?#]] 71 | (let [required-body# ~(some? body-param) 72 | [body# opts#] (if required-body# [body?# opts?#] [nil body?#]) 73 | path-params# ~(param-by-type params "path") 74 | req-params# ~(param-by-f params 75 | #(and (:required %) 76 | (not= (:paramType %) "body"))) 77 | query-params# ~(param-by-type params "query")] 78 | (assert (if required-body# (some? body#) (nil? body#)) 79 | (if required-body# "Body is required" "Body is prohibited")) 80 | (assert (every? #(get opts# %) req-params#) 81 | (str "Missing required options: " 82 | (pr-str (filter #(not (get opts# %)) req-params#)))) 83 | (util/request 84 | ctx# 85 | {:method ~method-kw 86 | :path ~path 87 | :params (select-keys opts# path-params#) 88 | :query (select-keys opts# query-params#) 89 | :body body#}))))) 90 | 91 | (defn render-api [{:keys [operations] :as api}] 92 | `(do ~@(->> operations 93 | (filter renderable-op?) 94 | (map (partial render-op api))))) 95 | 96 | (defn render-swagger [{:keys [apis]}] 97 | `(do ~@(map render-api apis))) 98 | 99 | (defmacro render-full-api [version] 100 | (-> (str "swagger/" version ".json") 101 | io/resource 102 | slurp 103 | (json/read-str :key-fn keyword) 104 | render-swagger)) 105 | -------------------------------------------------------------------------------- /src/kubernetes/api/util.clj: -------------------------------------------------------------------------------- 1 | (ns kubernetes.api.util 2 | (:require [clojure.string :as str] 3 | [clojure.core.async :refer [go ! chan]] 4 | [org.httpkit.client :as http] 5 | [clojure.data.json :as json])) 6 | 7 | (defn make-context [server] 8 | {:server server}) 9 | 10 | (defn- parameterize-path [path params] 11 | (reduce-kv (fn [s k v] 12 | (str/replace s (re-pattern (str "\\{" (name k) "\\}")) v)) 13 | path 14 | params)) 15 | 16 | (defn dashed->camel [s] 17 | (str/replace s #"-([a-z])" #(str/upper-case (second %)))) 18 | 19 | (defn- query-str [query] 20 | (->> query 21 | (map (fn [[k v]] (str (dashed->camel (name k)) "=" v))) 22 | (str/join "&"))) 23 | 24 | (defn- url [{:keys [server]} path params query] 25 | (str server 26 | (parameterize-path path params) 27 | (if (empty? query) "" "?") 28 | (query-str query))) 29 | 30 | (defn parse-response [{:keys [status headers body error]}] 31 | (cond 32 | error {:success false :error error} 33 | :else (json/read-str body :key-fn keyword))) 34 | 35 | (defn request [ctx {:keys [method path params query body]}] 36 | (let [c (chan)] 37 | (http/request 38 | (cond-> {:url (url ctx path params query) 39 | :method method 40 | :as :text} 41 | body (assoc :body (json/write-str body) 42 | :content-type :json)) 43 | #(go (let [resp (parse-response %)] 44 | #_(println "Request" method path query body resp) 45 | (>! c resp)))) 46 | c)) 47 | -------------------------------------------------------------------------------- /src/kubernetes/api/v1.clj: -------------------------------------------------------------------------------- 1 | (ns kubernetes.api.v1 2 | "Kubernetes v1 API. Auto-generated via macros from swagger documentation. 3 | Swagger doc is available from kubernetes.io/swagger-spec/api/v1" 4 | (:require [kubernetes.api.swagger :as swagger] 5 | [kubernetes.api.util :as util])) 6 | 7 | (def make-context util/make-context) 8 | 9 | (swagger/render-full-api "v1") 10 | -------------------------------------------------------------------------------- /test/kubernetes/api/v1_test.clj: -------------------------------------------------------------------------------- 1 | (ns kubernetes.api.v1-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.string :as str] 4 | [clojure.core.async :refer (> (repeatedly 10 #(rand-int 26)) 9 | (map #(nth (char-array "abcdefghijklmnopqrstuvwxyz") %)) 10 | (str/join ""))) 11 | (def nsopt {:namespace tns}) 12 | (def pod {:kind "Pod" 13 | :metadata {:name "test" :labels {:test "yes"}} 14 | :spec {:containers [{:name "nginx" 15 | :image "nginx"}]}}) 16 | 17 | (use-fixtures :once 18 | (fn [f] 19 | (