Codebase list kitchensink-clojure / 3050a4e
Merge tag '3.1.0' into debian/latest Version 3.1.0 Thomas Goirand 3 years ago
13 changed file(s) with 345 addition(s) and 92 deletion(s). Raw diff Collapse all Expand all
00 language: clojure
1 lein: 2.7.1
21 jdk:
3 - oraclejdk7
4 - openjdk7
5 - openjdk6
6 script: ./ext/travisci/test.sh
2 - openjdk8
3 - openjdk11
74 notifications:
85 email: false
96 hipchat:
0 ## 3.1.0
1
2 This is a minor feature release.
3
4 * Add a function for atomic file writes
5
6 ## 3.0.0
7
8 Maintenance:
9 * Update dependencies to take up clj-parent 4
10
11 ## 2.5.2
12
13 This is a minor maintenance release.
14
15 Maintenance:
16 * Fix adding URLs to classpath under Java 9.
17
18 ## 2.5.1
19
20 This is a minor maintenance release.
21
22 Maintenance:
23 * Fix symbol redef warnings under Clojure 1.9
24
25 ## 2.5.0
26
27 This is a minor feature release.
28
29 Features:
30 * add a `stream->sha256` function for hashing the contents of an InputStream
31
32 ## 2.4.0
33
34 This is a minor feature and improvement release.
35
36 Features:
37 * add a `utf8-string->sha256` function, directly analogous to `utf8-string->sha1`
38 * add a `file->sha256` function, equivalent to reading a file's contents as a
39 UTF-8 string and hashing the result. Uses an InputStream internally to avoid
40 reading the entire file into memory at once.
41
42 Improvement:
43 * the `open-port-num` function should now return a random port number from the
44 entire traditional ephemeral port range of 49152 through 65535.
45
046 ## 2.3.0
147
248 This is a minor feature release.
2066
2167 Maintenance:
2268
23 * Update to dynapath 0.2.5, to address some compatability issues with Java 9.
69 * Update to dynapath 0.2.5, to address some compatibility issues with Java 9.
2470
2571 ## 2.1.1
2672
0 # This will cause the puppetserver-maintainers group to be assigned
1 # review of any opened PRs against the branches containing this file.
2
3 * @puppetlabs/puppetserver-maintainers
+0
-23
MAINTAINERS less more
0 {
1 "version": 1,
2 "file_format": "This MAINTAINERS file format is described at http://pup.pt/maintainers",
3 "issues": "https://tickets.puppetlabs.com/browse/TK",
4 "internal_list": "https://groups.google.com/a/puppet.com/forum/?hl=en#!forum/discuss-trapperkeeper-maintainers",
5 "people": [
6 {
7 "github": "senior",
8 "email": "ryan.senior@puppet.com",
9 "name": "Ryan Senior"
10 },
11 {
12 "github": "pcarlisle",
13 "email": "pcarlisle@puppet.com",
14 "name": "Patrick Carlisle"
15 },
16 {
17 "github": "camlow325",
18 "email": "jeremy.barlow@puppet.com",
19 "name": "Jeremy Barlow"
20 }
21 ]
22 }
+0
-3
ext/travisci/test.sh less more
0 #!/bin/bash
1
2 lein2 test
0 (defproject puppetlabs/kitchensink "2.3.0"
0 (defproject puppetlabs/kitchensink "3.1.0"
11 :description "Clojure utility functions"
22 :license {:name "Apache License, Version 2.0"
33 :url "http://www.apache.org/licenses/LICENSE-2.0.html"}
44
5 :min-lein-version "2.7.1"
5 :min-lein-version "2.9.1"
66
7 :parent-project {:coords [puppetlabs/clj-parent "0.1.3"]
7 :parent-project {:coords [puppetlabs/clj-parent "4.0.0"]
88 :inherit [:managed-dependencies]}
99
1010 ;; Abort when version ranges or version conflicts are detected in
1717 [org.clojure/tools.cli]
1818
1919 [clj-time]
20 [me.raynes/fs]
20 [clj-commons/fs]
2121 [slingshot]
2222 [cheshire]
2323
2424 [org.ini4j/ini4j "0.5.2"]
25 [org.tcrawley/dynapath "0.2.5"]
25 [org.tcrawley/dynapath]
2626 [digest "1.4.3"]
27
2827 ]
2928
3029 ;; By declaring a classifier here and a corresponding profile below we'll get an additional jar
3736
3837 ;; this plugin is used by jenkins jobs to interrogate the project version
3938 :plugins [[lein-project-version "0.1.0"]
40 [lein-parent "0.3.1"]]
39 [lein-parent "0.3.7"]]
40
41 :test-selectors {:default (complement :slow)
42 :slow :slow}
4143
4244 :deploy-repositories [["releases" {:url "https://clojars.org/repo"
4345 :username :env/clojars_jenkins_username
3434 [cl]
3535 (dp/addable-classpath? cl))
3636
37 (defn- ensure-modifiable-classloader
38 "Check if there is a modifiable classloader in the current hierarchy, and add
39 one if not."
40 []
41 (let [classloader (.. Thread currentThread getContextClassLoader)]
42 (when (not-any? modifiable-classloader? (classloader-hierarchy classloader))
43 (let [new-cl (clojure.lang.DynamicClassLoader. classloader)]
44 (.. Thread currentThread (setContextClassLoader new-cl))))))
45
3746 (defn add-classpath
3847 "A corollary to the (deprecated) `add-classpath` in clojure.core. This implementation
3948 requires a java.io.File or String path to a jar file or directory, and will attempt
5665 (if-not (dp/add-classpath-url classloader (.toURL (file jar-or-dir)))
5766 (throw (IllegalStateException. (str classloader " is not a modifiable classloader")))))
5867 ([jar-or-dir]
68 (ensure-modifiable-classloader)
5969 (let [classloaders (classloader-hierarchy)]
6070 (if-let [cl (last (filter modifiable-classloader? classloaders))]
6171 (add-classpath jar-or-dir cl)
44 ;; altogether. But who has time for that?
55
66 (ns puppetlabs.kitchensink.core
7 (:import [org.ini4j Ini Config BasicProfileSection]
8 [javax.naming.ldap LdapName]
9 [java.io StringWriter Reader File])
10 (:require [clojure.tools.logging :as log]
7 (:refer-clojure :exclude [boolean? uuid?])
8 (:require [clj-time.coerce :as time-coerce]
9 [clj-time.core :as time]
10 [clj-time.format :as time-format]
11 [clojure.java.io :as io]
12 [clojure.pprint :as pprint]
13 [clojure.set :as set]
1114 [clojure.string :as string]
1215 [clojure.tools.cli :as cli]
13 [clojure.java.io :as io]
14 [digest]
15 [slingshot.slingshot :refer [throw+]]
16 [me.raynes.fs :as fs])
17 (:use [clojure.java.io :only (reader)]
18 [clojure.set :only (difference union)]
19 [clojure.string :only (split)]
20 [clojure.stacktrace :only (print-cause-trace)]
21 [clojure.pprint :only [pprint]]
22 [clj-time.core :only [now seconds minutes hours days years]]
23 [clj-time.coerce :only [ICoerce to-date-time]]
24 [clj-time.format :only [formatters unparse]]))
25
16 [clojure.tools.logging :as log]
17 digest
18 [me.raynes.fs :as fs]
19 [slingshot.slingshot :refer [throw+]])
20 (:import [java.io File Reader StringWriter]
21 javax.naming.ldap.LdapName
22 [org.ini4j BasicProfileSection Config Ini]))
2623
2724 (defn error-map
2825 [kind message]
4340 convertible to a Joda DateTime"
4441 [x]
4542 (and
46 (satisfies? ICoerce x)
47 (to-date-time x)))
43 (satisfies? time-coerce/ICoerce x)
44 (time-coerce/to-date-time x)))
4845
4946 (defn boolean?
5047 "Returns true if the value is a boolean"
114111
115112 (defn pprint-to-string [x]
116113 (let [w (StringWriter.)]
117 (pprint x w)
114 (pprint/pprint x w)
118115 (.toString w)))
119116
120117 (defn to-sentence
149146 (defn lines
150147 "Returns a sequence of lines from the given filename"
151148 [filename]
152 (with-open [file-reader (reader (fs/file filename))]
149 (with-open [file-reader (io/reader (fs/file filename))]
153150 ;; line seq is lazy and file-reader gets closed
154151 (doall (line-seq file-reader))))
155152
302299 ;; ## Collection operations
303300
304301 (defn symmetric-difference
305 "Computes the symmetric difference between 2 sets"
302 "Computes the symmetric set/difference between 2 sets"
306303 [s1 s2]
307 (union (difference s1 s2) (difference s2 s1)))
304 (set/union (set/difference s1 s2) (set/difference s2 s1)))
308305
309306 (defn as-collection
310307 "Returns the item wrapped in a collection, if it's not one
602599 "Returns a timestamp string for the given `time`, or the current time if none
603600 is provided. The format of the timestamp is eg. 2012-02-23T22:01:39.539Z."
604601 ([]
605 (timestamp (now)))
602 (timestamp (time/now)))
606603 ([time]
607 (unparse (formatters :date-time) time)))
604 (time-format/unparse (time-format/formatters :date-time) time)))
608605
609606 ;; ## Exception handling
610607
739736 {} section))
740737
741738 (defn parse-ini
742 "Takes a reader that contains an ini file, and returns an Ini object
739 "Takes a io/reader that contains an ini file, and returns an Ini object
743740 containing the parsed results"
744741 [^Reader ini-reader]
745742 {:pre [(instance? Reader ini-reader)]
763760 (every? keyword? (keys %))
764761 (every? map? (vals %))]}
765762
766 (with-open [ini (reader filename)]
763 (with-open [ini (io/reader filename)]
767764 (reduce (fn [acc [name section]]
768765 (assoc acc
769766 (keywordize name)
964961 (let [bytes (.getBytes s "UTF-8")]
965962 (digest/sha-1 [bytes])))
966963
964 (defn utf8-string->sha256
965 "Compute a SHA-256 hash for the UTF-8 encoded version of the supplied
966 string"
967 [^String s]
968 {:pre [(string? s)]
969 :post [(string? %)]}
970 (let [bytes (.getBytes s "UTF-8")]
971 (digest/sha-256 [bytes])))
972
973 (defn stream->sha256
974 "Compute a SHA-256 hash for the given java.io.InputStream object."
975 [^java.io.InputStream stream]
976 {:pre [(instance? java.io.InputStream stream)]
977 :post [(string? %)]}
978 (digest/sha-256 stream))
979
980 (defn file->sha256
981 "Compute a SHA-256 hash for the given java.io.File object.
982 Uses an InputStream to read the file, so it doesn't load all the contents into
983 memory at once."
984 [^java.io.File file]
985 {:pre [(instance? java.io.File file)]
986 :post [(string? %)]}
987 (digest/sha-256 file))
988
967989 (defn bounded-memoize
968990 "Similar to memoize, but the cache will be reset if the number of entries
969991 exceeds the specified `bound`."
10171039 (string? b)]
10181040 :post [(number? %)]}
10191041 (let [parse #(mapv parse-int (-> %
1020 (split #"-")
1042 (string/split #"-")
10211043 (first)
1022 (split #"[\\._]")))]
1044 (string/split #"[\\._]")))]
10231045 (compare (parse a) (parse b))))
10241046
10251047 (def java-version
10661088 ~@(interleave (repeat g) (map pstep forms))]
10671089 ~g)))
10681090
1091 (defn- port-open?
1092 [port]
1093 (try
1094 (with-open [_ (java.net.ServerSocket. port)]
1095 port)
1096 (catch java.net.BindException e
1097 (when-not (re-find #"already in use" (.getMessage e))
1098 (throw e)))))
1099
10691100 (defn open-port-num
1070 "Returns a currently open port number"
1101 "Returns a currently open port number in the traditional ephemeral port range
1102 of 49152 through 65535."
10711103 []
1072 (with-open [s (java.net.ServerSocket. 0)]
1073 (.getLocalPort s)))
1104 (let [lo 49152
1105 hi 65536] ; one higher because the upper limit is exclusive
1106 (if-let [open-port (some port-open? (shuffle (range lo hi)))]
1107 open-port
1108 (throw (java.net.BindException.
1109 "All ephemeral ports are already in use (Bind failed)")))))
10741110
10751111 (defmacro assoc-if-new
10761112 "Assocs the provided values with the corresponding keys if and only
10981134 (defn parse-interval
10991135 "Given a time string of the form \"<number>\", or \"<number><unit>\", this
11001136 function parses this time amount and returns a joda time Period instance.
1101 If the unit is left off, the units are assumed to be seconds
1137 If the unit is left off, the units are assumed to be time/seconds
11021138
11031139 Example: \"12h\" -> (clj-time.core/hours 12)
11041140 Example: \"12\" -> (clj-time.core/seconds 12)
11111147 (when-let [[_ num unit] (re-matches #"^(\d+)([smhdy]?)$" time-str)]
11121148 (let [num (parse-int num)
11131149 time-fn (case unit
1114 "s" seconds
1115 "m" minutes
1116 "h" hours
1117 "d" days
1118 "y" years
1119 "" seconds)]
1150 "s" time/seconds
1151 "m" time/minutes
1152 "h" time/hours
1153 "d" time/days
1154 "y" time/years
1155 "" time/seconds)]
11201156 (time-fn num)))))
0 (ns puppetlabs.kitchensink.file
1 (:import [java.io BufferedWriter FileOutputStream OutputStreamWriter]
2 [java.nio.file CopyOption Files LinkOption Paths StandardCopyOption]
3 [java.nio.file.attribute FileAttribute PosixFilePermissions]))
4
5 (defn str->path
6 [path]
7 (Paths/get path (into-array String [])))
8
9 (defn get-perms
10 "Returns the currently set permissions of the given file path."
11 [path]
12 (-> (str->path path)
13 (Files/getPosixFilePermissions (into-array LinkOption []))
14 PosixFilePermissions/toString))
15
16 (defn set-perms
17 "Set the provided permissions on the given path. The permissions string is in
18 the form of the standard 9 character posix format, e.g. \"rwxr-xr-x\"."
19 [path permissions]
20 (-> (str->path path)
21 (Files/setPosixFilePermissions
22 (PosixFilePermissions/fromString permissions))
23 (.toFile)))
24
25 (defn perms->attribute
26 [permissions]
27 (PosixFilePermissions/asFileAttribute (PosixFilePermissions/fromString permissions)))
28
29 (def nofollow-links
30 (into-array LinkOption [LinkOption/NOFOLLOW_LINKS]))
31
32 (def ^:private default-permissions
33 (PosixFilePermissions/fromString "rw-r-----"))
34
35 (defn atomic-write
36 "Write to a file atomically. This takes a function that should operate on a
37 Writer as its first argument. If permissions are specified (as a string of the
38 form \"rwxrwxrwx\") then the file will be created with those permissions. If
39 not specified and the file already exists then existing permissions will be
40 preserved. If no file exist a default is set."
41 ([path write-function]
42 (atomic-write path write-function nil))
43 ([path write-function permissions]
44 (let [target (str->path path)
45 dir (.getParent target)
46 file-exists? (Files/exists target nofollow-links)
47 owner (if file-exists?
48 (Files/getOwner target nofollow-links))
49 group (if file-exists?
50 (Files/getAttribute target "posix:group" nofollow-links))
51 permissions (if permissions
52 (PosixFilePermissions/fromString permissions)
53 (if file-exists?
54 (Files/getPosixFilePermissions target nofollow-links)
55 default-permissions))
56 temp-attributes (into-array FileAttribute [(perms->attribute "rw-------")])
57 temp-file (Files/createTempFile dir (.toString (.getFileName target)) "tmp" temp-attributes)
58 stream (proxy [FileOutputStream] [(.toString temp-file)]
59 (close []
60 (.sync (.getFD this))
61 (proxy-super close)))
62 writer (BufferedWriter. (OutputStreamWriter. stream))]
63
64 (write-function writer)
65
66 (.close writer)
67
68 (when owner
69 (Files/setOwner temp-file owner))
70 (when group
71 (Files/setAttribute temp-file "posix:group" group nofollow-links))
72
73 ;; We set these here instead of at file creation to avoid problems with
74 ;; read-only files and umask
75 (Files/setPosixFilePermissions temp-file permissions)
76 (Files/move temp-file target (into-array CopyOption [StandardCopyOption/ATOMIC_MOVE])))))
77
78 (defn atomic-write-string
79 "Write a string to a file atomically. See atomic-write for more details."
80 ([path string]
81 (atomic-write-string path string nil))
82 ([path string permissions]
83 (atomic-write path #(.write % string) permissions)))
33 (:import (java.net URL)))
44
55 (deftest with-additional-classpath-entries-test
6 (let [paths ["/foo" "/bar"]
7 get-urls #(into #{}
8 (.getURLs (.getContextClassLoader (Thread/currentThread))))]
6 (let [paths ["classpath-test"]
7 get-resource #(-> (Thread/currentThread)
8 .getContextClassLoader
9 (.getResource "does-not-exist-anywhere-else"))]
910 (with-additional-classpath-entries
1011 paths
11 (testing "classloader now includes the new paths"
12 (let [urls (get-urls)]
13 (is (contains? urls (URL. "file:/foo")))
14 (is (contains? urls (URL. "file:/bar"))))))
15 (testing "classloader has been restored to its previous state"
16 (let [urls (get-urls)]
17 (is (not (contains? urls (URL. "file:/foo"))))
18 (is (not (contains? urls (URL. "file:/bar"))))))))
12 (testing "classloader now includes the new path"
13 (is (get-resource))))
14
15 (testing "classloader no longer includes the new path"
16 (is (not (get-resource))))))
66 [clj-time.core :as t]
77 [puppetlabs.kitchensink.testutils :as testutils]
88 [clojure.zip :as zip])
9 (:import (java.util ArrayList)))
9 (:import (java.util ArrayList)
10 (java.io ByteArrayInputStream)))
1011
1112 (deftest array?-test
1213 (testing "array?"
415416
416417 (testing "should produce the correct hash"
417418 (is (= "8843d7f92416211de9ebb963ff4ce28125932878"
418 (utf8-string->sha1 "foobar"))))))
419 (utf8-string->sha1 "foobar")))))
420
421 (testing "Computing a SHA-256 for a UTF-8 string"
422 (testing "should fail if not passed a string"
423 (is (thrown? AssertionError (utf8-string->sha256 1234))))
424
425 (testing "should produce a stable hash"
426 (is (= (utf8-string->sha256 "foobar")
427 (utf8-string->sha256 "foobar"))))
428
429 (testing "should produce the correct hash"
430 (is (= "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"
431 (utf8-string->sha256 "foobar"))))))
432
433 (deftest stream-hashing
434 (testing "Computing a SHA-256 hash for an input stream"
435 (testing "should fail if not passed an input stream"
436 (is (thrown? AssertionError (stream->sha256 "what"))))
437
438 (let [stream-fn #(ByteArrayInputStream. (.getBytes "foobar" "UTF-8"))]
439 (testing "should produce a stable hash"
440 (is (= (stream->sha256 (stream-fn))
441 (stream->sha256 (stream-fn)))))
442
443 (testing "should produce the correct hash"
444 (is (= "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"
445 (stream->sha256 (stream-fn))))))))
446
447 (deftest file-hashing
448 (testing "Computing a SHA-256 hash for a file"
449 (testing "should fail if not passed a file"
450 (is (thrown? AssertionError (file->sha256 "what"))))
451
452 (let [f (temp-file "sha256" ".txt")]
453 (spit f "foobar")
454
455 (testing "should produce a stable hash"
456 (is (= (file->sha256 f)
457 (file->sha256 f))))
458
459 (testing "should produce the correct hash"
460 (is (= "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"
461 (file->sha256 f)))))))
419462
420463 (deftest temp-file-name-test
421464 (testing "The file should not exist."
748791 (with-timeout 1 false
749792 (wait-return 1005 true))))))))
750793
751 (deftest open-port-num-test
794 (deftest ^:slow open-port-num-test
752795 (let [port-in-use (open-port-num)]
753796 (with-open [s (java.net.ServerSocket. port-in-use)]
754797 (let [open-ports (set (take 60000 (repeatedly open-port-num)))]
0 (ns puppetlabs.kitchensink.file-test
1 (:require [clojure.test :refer :all]
2 [me.raynes.fs :as fs]
3 [puppetlabs.kitchensink.file :refer :all]))
4
5 (deftest atomic-write-test
6 (testing "when the file doesn't exist"
7 (let [tmp-file (.toString (fs/temp-file "atomic-writes-test"))
8 content "Something said, not good"]
9 (fs/delete tmp-file)
10 (atomic-write tmp-file #(.write % content))
11
12 (testing "it writes the data"
13 (is (= content (slurp tmp-file))))
14
15 (testing "it applies a default mode of 0640"
16 (is (= "rw-r-----" (get-perms tmp-file))))
17
18 (testing "it applies the specified mode"
19 (fs/delete tmp-file)
20 (atomic-write tmp-file #(.write % content) "rwxrwxrwx")
21 (is (= "rwxrwxrwx" (get-perms tmp-file))))
22
23 (testing "it can create a read-only file"
24 (fs/delete tmp-file)
25 (atomic-write tmp-file #(.write % content) "r--------")
26 (is (= "r--------" (get-perms tmp-file))))
27
28 (fs/delete tmp-file)))
29
30 (testing "when overwriting"
31 (let [tmp-file (.toString (fs/temp-file "atomic-writes-test"))
32 content "Something said, not good"]
33 (set-perms tmp-file "rwxr-x--x")
34
35 (testing "it preserves existing permissions"
36 (atomic-write tmp-file #(.write % content))
37 (is (= "rwxr-x--x" (get-perms tmp-file))))
38
39 (testing "it overwrites existing permissions if specified"
40 (atomic-write tmp-file #(.write % content) "rw-rw----")
41 (is (= "rw-rw----" (get-perms tmp-file))))
42
43 (testing "it updates a read-only file"
44 (set-perms tmp-file "r--------")
45 (atomic-write tmp-file #(.write % content))
46 (is (= content (slurp tmp-file))))
47
48 (fs/delete tmp-file)))
49
50 (testing "the supplied function is allowed to close the writer"
51 (let [tmp-file (.toString (fs/temp-file "atomic-writes-test"))
52 write-fn (fn [writer]
53 (.write writer "They called me slow")
54 (.close writer))]
55
56 (atomic-write tmp-file write-fn)
57
58 (fs/delete tmp-file))))