Fork me on GitHub
#babashka
<
2023-06-13
>
escherize04:06:43

A couple days ago I asked about using lambdaisland/hiccup in babashka, since it has some nice things that hiccup/hiccup doesn’t have. Well it was not really possible since it uses some java libraries. So, announcing another hiccup clone 🎉 https://github.com/escherize/huff It has all the nice quality of life improvments from lambdaisland/hiccup but also can run on babashka.

👍 10
🎉 10
💯 1
leifericf09:06:58

I have finished https://github.com/leifericf/leifs-utils/blob/main/src/leifs_utils/git.clj, which also happens to be the first "real" Clojure program I've written. It can be used to clone all Git repositories from an Azure DevOps organization and a GitHub organization in one fell swoop. I work in a company with 1000+ Git repos ("microservices" and "micro frontends") spread across Azure DevOps and GitHub, so I need some utils to make that easier. This is only the beginning. If someone has the time and energy to look at my script and offer improvement suggestions, I would greatly appreciate it.

🎉 18
borkdude09:06:26

Well done

❤️ 2
joakimen09:06:54

Most of it is just a matter of preference imo, but something you might consider changing is get-setting . It reads the entire config-file from disk every time a key is extracted, so given that e.g. :local/repo-root-dir is read on every repo you clone, that's gonna do a lot of IO. You might consider reading the settings into a hashmap once with def , then look up keys in it when needed Other than that, process/sh supports varargs, so if you change the signature of sh-out->json to take & args then you won't have to wrap your shell commands in lots of string stuff

💡 2
2
leifericf10:06:25

Thank you, @U0422G22932! Those are some good improvement suggestions. I actually tried to make the function sh-out->json take & args, but I struggled to get it working (got some confusing errors), then abandoned that idea to finish the script and forgot to come back to it. I'll see if I can get that working now. Also, that's a very good point about excessive IO when reading settings! I totally missed that. Thank you for the suggestion.

borkdude10:06:53

Don't know if it helps, but the process functions now also take an explicit {:cmd [...]} option in which you can provide the strings

borkdude10:06:07

so (sh {:cmd ["foo" "bar"]})

leifericf10:06:55

Oh, yes, that might also make it easier to tidy things up a bit, @U04V15CAJ! Thanks for the tip.

leifericf12:06:07

Hmmm. I'm struggling with using & args here.

(defn sh-out->json
  [& args]
  (-> (process/sh {:cmd (vec args)})
      :out
      (json/parse-string true)))

(defn get-github-repo-data
  []
  (sh-out->json "gh repo list" (:github/org-name settings)
                 "--language" (:github/repo-language-filter settings)
                 "--source --no-archived --limit 1000 --json sshUrl"))

(get-github-repo-data)
(vec args) in this example will produce:
["gh repo list" "myorg" "--language" "C#" "--source --no-archived --limit 1000 --json sshUrl"]
But I get a java.lang.NullPointerException I suspect I'm doing something dumb 😅

borkdude12:06:44

@U01PE7630AC what bb version are you using

borkdude12:06:36

you might have to upgrade if it's older than a few weeks

leifericf12:06:58

Ahaaa, that might explain a few things. I'm using this version:

bb --version
babashka v1.3.179

borkdude12:06:12

the :cmd thing was introduced in 180 I think

👍 2
joakimen12:06:05

You can still do it the "old" way like this

(defn sh-out->json
  [& args]
  (-> (apply process/sh args)
      :out
      (json/parse-string true)))

👍 2
joakimen12:06:54

You have a mixed arg list, by the way, which might cause problems

(sh-out->json "gh repo list" (:github/org-name settings)
                 "--language" (:github/repo-language-filter settings)
                 "--source --no-archived --limit 1000 --json sshUrl")
Only the first arg (`gh repo list` will be tokenized (split) I think, so the last line might blow up. • This will work: (process/sh "gh repo list" "joakimen" "--limit" "1") • This won't work: (process/sh "gh repo list" "joakimen" "--limit 1")

leifericf12:06:09

Ooooh! I thought each parameter and its value had to be together as one string, as in the example with "--limit 1", that won't work. Let's see…

joakimen12:06:04

Easier to either tokenize everything, or pass the entire thing as a single string

borkdude12:06:37

I don't recommend stringifying arguments yourself, since those arguments might contain spaces

borkdude12:06:25

e.g. if you want to pass "--title" "foo bar" and you pass a single concatenated string this becomes "--title" "foo" and "bar" while you want to pass "foo bar" as a single string

borkdude12:06:39

so just stick to separate strings as much as possible

👍 2
leifericf13:06:42

I got rid of the exception like this:

(defn sh-out->json
  [& args]
  (-> (apply process/sh args)
      :out
      (json/parse-string true)))

(defn get-github-repo-data
  []
  (sh-out->json "gh"
                "repo"
                "list"
                (:github/org-name settings)
                "--language"
                (:github/repo-language-filter settings)
                "--source"
                "--no-archived"
                "--limit"
                "1000"
                "--json"
                "sshUrl"))
All parameters are now separate strings. But :out on the process is now nil, so there is still something not working:
{:proc #object[java.lang.ProcessImpl 0x65fde901 "Process[pid=99528, exitValue=137]"],
 :exit 137,
 :in
 #object[java.lang.ProcessImpl$ProcessPipeOutputStream 0x38b10a43 "java.lang.ProcessImpl$ProcessPipeOutputStream@38b10a43"],
 :out "",
 :err "",
 :prev nil,
 :cmd
 ["gh" "repo" "list" "myorg" "--language" "C#" "--source" "--no-archived" "--limit" "1000" "--json" "sshUrl"]}
Also, using the latest Babashka now 🙂
bb --version
babashka v1.3.181

joakimen13:06:51

Works on my machine, with my username (and no language-filter since i dont have your config)

(defn sh-out->json
  [& args]
  (-> (apply process/sh args)
      :out
      (json/parse-string true)))

(defn get-github-repo-data
  []
  (sh-out->json "gh" "repo" "list" "joakimen" "--source" "--no-archived"
                "--limit" "1000" "--json" "sshUrl"))

(get-github-repo-data)

;; => ({:sshUrl "[email protected]:joakimen/dotfiles.git"}
;;     {:sshUrl "git@github..
You sure all your keys (language-filter etc) is being read properly?

😮 2
borkdude13:06:05

note: :exit 137

👀 2
borkdude13:06:28

this usually means "out of memory"

borkdude13:06:50

you can insert a process/check in between if you want to crash on non-zero exit codes

💡 4
leifericf13:06:57

Ah! What in the world… I restarted VS Code and the REPL (via Calva), and now it works.

😁 2
leifericf13:06:58

Thank you so much for the help, guys! Sorry to waste your time on a "fluke."

joakimen13:06:54

It's all good 😄

💜 2
leifericf13:06:43

I've pushed a new version of my script now, with your suggested changes 🙂 Much better! https://github.com/leifericf/leifs-utils/blob/main/src/leifs_utils/git.clj

🙌 4
leifericf13:06:04

For comparison, https://martdegraaf.github.io/posts/consulting/git-clone-all-repos-azure-devops/#clone-all-repositories, which only gets repos from Azure DevOps (not GitHub), and presumably only works on Windows 🙂 Babashka is sooo nice!

joakimen13:06:20

I tried to adopt Power(S)hell a while back, I could never get into it. Absolutely has its uses when it comes to managing services, servers, working with Active Directory etc, but for a general purpose script language.. 🙅.

leifericf13:06:35

Indeed! Also, compared to the PowerShell script, my Babashka version is parallelized, and each function may also be used independently. And it can be made into a Task (my next to-do), etc. And it's cross-platform.

leifericf13:06:33

But I guess I should also make it do git pull instead of git clone if the repo already exists on disk. And maybe also add some output to the terminal to show the user what's going on whey they run it.

joakimen13:06:46

Yeah, but then you're suddenly dealing with a slightly different can of worms (dirty repos, missing upstreams, ..) Sounds useful though

👍 2
leifericf13:06:02

Yeah, that's true. There are many other issues. I also want to write some functions to find all Git repos from a root directory, then search through the code and commits (code diffs) to find certain classes, method usages, etc., across all projects and their history. This is useful because we often must apply similar fixes to different microservices. We often need to find a different microservice where a fix has already been applied, etc.

dabrazhe08:06:06

Is there a similar version for GitLab? I'd like to be able to checkout multiple Groups. @U01PE7630AC

leifericf09:06:24

My script currently does not work with GitLab, but I see https://docs.gitlab.com/ee/integration/glab/also have a CLI. It should be relatively straightforward to adapt my script to use glab repo list (https://gitlab.com/gitlab-org/cli/-/blob/main/docs/source/repo/list.md), and Git commands to mass-clone repos from there. But I don't use GitLab, so I can't say for sure.

leifericf09:06:56

You should only have to change this one function:

(defn get-github-repo-data
  []
  (sh-out->json "gh" "repo" "list" (:github/org-name settings)
                "--language" (:github/repo-language-filter settings)
                "--source"
                "--no-archived"
                "--limit" "1000"
                "--json" "sshUrl"))

leifericf09:06:17

And maybe you need to change this function if GitLab's CLI doesn't return a JSON-response with the key sshUrl:

(defn clone-all-repos
  [repos]
  (->> repos
       (extract-key :sshUrl)
       (pmap clone-repo)))

borkdude10:06:30

https://github.com/babashka/babashka: Native, fast starting Clojure interpreter for scripting Minor release: 1.3.181 (2023-06-13) • https://github.com/babashka/babashka/issues/1575: fix command line parsing problem with -e + *command-line-args*https://github.com/babashka/babashka/issues/1576: make downloading/unzipping of deps.clj tools .zip file more robust

babashka 12
🧡 2
🎉 4
Patrick Brown13:06:25

Is this a bug or expected behavior? When I call all-ns I get all the babashka built-in namespaces (it at least looks like all of them), I also get the namespace that I am currently in. However, if I've got :paths ["scripts"] as an entry in my bb.edn map none of the namespaces inside "scripts" are returned by all-ns. I tried adding them to a deps.edn and using it as a local-root dep, but no dice. Even when I eval a namespace in the repl it doesn't show up in all-ns. Am I doing something wrong or have I hit a wall in graal or something else.?

borkdude13:06:07

all-ns only returns the namespaces that are already loaded (exist in memory), not those on disk

Patrick Brown13:06:53

I see. Cheers on the speedy reply. I'm trying to grab vars with specific metadata. Can you think of an simple alternative route to do this programmatically?

borkdude13:06:28

you can use tools.namespace to scan existing files on the classpath to get their namespaces

👍 2
Patrick Brown13:06:59

There it is! That's perfect and well within grasp. CHEERS again!

borkdude13:06:24

$ bb -Sdeps '{:deps {org.clojure/tools.namespace {:mvn/version "RELEASE"}}}' -e "(require '[clojure.tools.namespace.find :as f]) (f/find-ns-decls-in-dir (io/file \"src\"))"
((ns cherry.compiler (:require [cherry.resource :as resource] [cherry.internal.deftype :as deftype] [cherry.internal.destructure :refer [core-let]] [cherry.internal.fn :refer [core-defmacro core-defn core-fn]] [cherry.internal.loop :as loop] [cherry.internal.macros :as macros]

borkdude13:06:35

(replace RELEASE with a proper version)

Patrick Brown13:06:55

What's with the fork you've got of tools.namespace?

borkdude13:06:03

no longer needed

borkdude13:06:36

where did you find this fork? I should archive this if it's not already

borkdude13:06:25

ok, I'll remove that, thanks

Patrick Brown13:06:12

While I've got you, do you know of anyone who's does their CI/CD pipeline with bb fns? Particularly in GH actions?

Patrick Brown13:06:11

I had the bright idea to just do it as babashka fns in an interceptor chain. It sounds simple enough, but I would listen to wisdom before jumping in.

borkdude13:06:17

Yes, I do this all the time, but mostly using babashka tasks: https://book.babashka.org/#tasks

borkdude13:06:16

you can look at the bb.edn of this project for a good example: https://github.com/clj-kondo/clj-kondo.lsp

borkdude13:06:37

I just type bb publish on the command line and then everything is kicked off

borkdude13:06:41

or you can run it from CI

Patrick Brown13:06:03

Yeah, I'm cool with the tasks idea. I was more leaning towards defining simple fns and then building chains based on plain data which would lead nicely into a build pipeline that is stored in a plain vector in an edn file and checked into git for the whole gitops thing....

Patrick Brown13:06:27

Yeah, that task route does give some cool cli integration thought. I like those doc strings

borkdude13:06:49

That's another way to do it. You can hook up tasks to any function you define elsewhere in your bb code

Patrick Brown13:06:42

Hmmm many thoughts, thanks for the help. I'm too amped up about today's work to keep chatting. You're the best. Much love.

👍 2
pesterhazy13:06:49

Fwiw we're moving our CI to bb as well. I think it's a match made in heaven

wilkerlucio18:06:04

hello, is there a lib for babashka to implement rich terminal UI's?

borkdude20:06:32

nbb + ink is also a good option

wilkerlucio20:06:03

thanks folks, playing with ink here cause what I need is a terminal UI that gets updated upon events, I couldn't find how to do that with gum

pesterhazy19:06:50

Introducing https://github.com/pesterhazy/bash2bb, a converter of bash scripts into babashka programs. 0.1.119 - Initial Release Many features work: • Variables (including environment variables) • Redirection • Conditional • Pipes • Heredoc Other language features, like loops, are not implemented yet

🎉 22
babashka 4