-
Notifications
You must be signed in to change notification settings - Fork 0
/
core.clj
184 lines (165 loc) · 7.15 KB
/
core.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
(ns io.axrs.cli.circle-ci.core
(:require
[cli-matic.core :as cli]
[com.rpl.specter :as sp]
[io.axrs.cli-tools.ansi :as ansi]
[io.axrs.cli-tools.notifications :as notification]
[io.axrs.cli-tools.env :as env]
[io.axrs.cli-tools.http :as http]
[io.axrs.cli-tools.print :as print]
[io.axrs.cli-tools.time :as time]
[io.jesi.backpack :as bp]
[slingshot.slingshot :refer [try+ throw+]]
[taoensso.encore :refer [assoc-some]]))
(defonce ^:private project-urls (atom {}))
(defn- token []
(env/get "CIRCLECI_TOKEN"))
(defn- get-projects []
(try+
(:body (http/get-json "https://circleci.com/api/v1.1/projects"
{:circle-token (token)}))
(catch http/client-error? {:as response}
(http/print-error response "Please ensure your CIRCLE_CI token is correct")
(throw+))))
(defn- get-project-url [project]
(if-let [url (get @project-urls project)]
url
(let [urls (reduce
(fn [m {:keys [username vcs-type reponame]}]
(assoc m reponame (str "https://circleci.com/api/v1.1/project/" vcs-type \/ username \/ reponame)))
{}
(get-projects))]
(reset! project-urls urls)
(get @project-urls project))))
(defn- get-recent [{:keys [limit project]}]
(try+
(when-let [url (if project
(get-project-url project)
"https://circleci.com/api/v1.1/recent-builds")]
(:body (http/get-json url
(assoc-some {:circle-token (token)}
:limit (some-> limit (min 100))))))
(catch http/client-error? {:as response}
(http/print-error response "Please ensure your CIRCLE_CI token is correct")
(throw+))))
(defn- utc-dates->local [{:keys [start-time stop-time queued-at build-url workflow-url] :as result} now]
(let [[start-time stop-time queued-at] (map time/->date-time [start-time stop-time queued-at])]
(assoc result
:build-link (ansi/hyperlink build-url "Build")
:workflow-link (ansi/hyperlink workflow-url "Workflow")
:run-time (some-> start-time (time/humanized-interval (or stop-time now)))
:start-time (time/->wall-str start-time)
:stop-time (time/->wall-str stop-time)
:queued-at (time/->wall-str queued-at))))
(defn- clean-result [now {{:keys [workflow-id] :as workflows} :workflows
:as result}]
(-> result
(dissoc :workflows)
(merge workflows)
(assoc :workflow-url (format "https://circleci.com/workflow-run/%s" workflow-id))
(utc-dates->local now)))
(defn- clean-results [results]
(let [now (time/now)]
(->> results
(map (partial clean-result now))
(map (bp/partial-right dissoc :circle-yml)))))
(defn- colorize-status [status]
(let [color-fn (cond
(= "failed" status) ansi/red
(= "success" status) ansi/green
(= "running" status) ansi/blue
:else identity)]
(color-fn status)))
(defn- colorize [{:as row}]
(sp/transform [:status some?] colorize-status row))
(defn- key-match? [k v]
(if v
(comp (partial = v) k)
any?))
(defn- filter-by-params [{:keys [branch job-name]} results]
(let [job-name? (key-match? :job-name job-name)
branch? (key-match? :branch branch)]
(filter #(and (branch? %)
(job-name? %))
results)))
(defonce ^:private default-cols
[:status :queued-at :run-time :reponame :branch :job-name :subject :build-link :workflow-link])
(defonce ^:private notified (atom #{}))
(defn- recent [{:keys [project cols extra-cols watch notify]
:or {cols default-cols}
:as params}]
(let [watch? (some-> watch pos?)
notify? (and watch? (some-> notify pos?))
cols (if project (remove (bp/p= :reponame) cols) cols)]
(when watch?
(print/clear-screen))
(let [results (->> (get-recent params)
clean-results
(filter-by-params params))]
(when notify?
(doseq [{:keys [reponame branch job-name run-time build-url subject]} (filter (comp (bp/p= "failed") :status) results)]
(when-not (contains? @notified build-url)
(notification/send {:message subject
:subtitle (str job-name " failed (" run-time ")")
:url build-url
:title (str reponame " | " branch)})
(swap! notified conj build-url))))
(print/table colorize (concat cols extra-cols) results))
(when watch?
(Thread/sleep (* 1000 watch))
(recur params))))
(defn- projects [{:as params}]
(->> (get-projects)
(sort-by :reponame)
(print/table colorize [:username :reponame :vcs-type :vcs-url])))
(defn- cols [{:as params}]
(->> (get-recent {:limit 1})
first
keys
(concat [:build-link :workflow-link])
sort
clojure.pprint/pprint))
(defonce ^:private cli-config
{:app {:command "circle-ci"
:description "A CircleCI CLI"
:version "0.3.1"}
:commands [
{:command "cols"
:description ["Prints a list of columns available for use in tabular outputs"]
:runs cols}
{:command "projects"
:description ["Prints a list of projects followed by the current CircleCI User"]
:runs projects}
{:command "recent"
:short "r"
:description ["Prints a tabular list of recent jobs"]
:opts [{:option "limit"
:as "The total number of jobs to fetch before filtering (max is 100)."
:type :int
:default 25}
{:option "project"
:as "Filters the results to a specific reponame"
:type :string}
{:option "job-name"
:as "Filters the results to a specific CircleCI Job name"
:type :string}
{:option "branch"
:as "Filters the results to a specific branch"
:type :string}
{:option "cols"
:as "Columns to print"
:type :edn}
{:option "extra-cols"
:as "Extra columns to print (appended to the end of cols)"
:type :edn}
{:option "notify"
:as "Display system notification for failed builds"
:type :int}
{:option "watch"
:as "The number of seconds to wait before refresh"
:type :int}]
:runs recent}]})
(defn -main [& args]
(if-not (token)
(print/redln "No CIRCLECI_TOKEN defined.")
(cli/run-cmd args cli-config)))