primenumsdev
Posted on February 6, 2022
Recently, I faced a problem, I was need to share a Clojure library internally, without exposing it to the public Clojars repo, and surprisingly I didn't find a good tutorial on how to do it. So I decided to describe my story here.
Since our company uses GitHub, I knew that GitHub provides a private package registry, but never used it before.
It appears that it is very simple solution to provide an internal maven package registry for sharing libraries within multiple projects, and accompanied with GitHub Actions you can easily setup an automated release pipeline.
Let's start.
Step 1 Create a new library
I use Leiningen to start a new project, so let's first create a test library:
$ lein new cljcloud/my-lib
It will create a new project based on default Leiningen template, with project.clj
file in the root.
We need to modify project.clj
and add these lines:
:repositories [["github" {:url "https://maven.pkg.github.com/your-org-name/your-repo-name"
:username "private-token"
:password :env/GITHUB_TOKEN}]]
Pls, update your-org-name
and your-repo-name
with real values accordingly.
It tells Leiningen to use GitHub private registry of your GitHub organization to push your package.
It's also possible to store packages at your personal account, you just need to use your GitHub username instead of your-org-name
.
The line :password :env/GITHUB_TOKEN
instructs Leiningen to read password from environment variable called GITHUB_TOKEN
, we will get back to this latter.
I set :username "private-token"
because we going to use GitHub private access token, and it doesn't matter what we specify for the username, it only needs to be a string.
Another thing that I changed is library version, I removed the SNAPSHOT
from it, so that final project.clj
looked like this:
(defproject cljcloud/my-lib "0.1.0"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:repositories [["github" {:url "https://maven.pkg.github.com/cljcloud/my-lib"
:username "private-token"
:password :env/GITHUB_TOKEN}]]
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.10.3"]])
Step 2 Set up GitHub Actions workflow to publish package
In order to create GitHub workflow, you need to make a new YAML file:
.github/workflows/publish-package.yml
with the following content:
name: publish package
# Manually trigger
on:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
# Add permissions for GITHUB_TOKEN
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
java-version: '17'
distribution: 'temurin'
- name: Install clojure tools
uses: DeLaGuardo/setup-clojure@3.5
with:
lein: 'latest'
- name: Configure GPG Key
run: echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --import
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
- name: Publish package
run: cd my-lib && lein deploy github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
A few more things, by default Leiningen, requires GPG key to be present when running lein deploy
, you can opt-in to not use it with the option: :sign-releases false
in project.clj
:repositories [["github" {:url "https://maven.pkg.github.com/cljcloud/my-lib"
:username "private-token"
:password :env/GITHUB_TOKEN
:sign-releases false}]]
But it's not recommended, so I have created a new GPG key via:
$ gpg --gen-key
It will prompt for name, email, and passphrase and print out a key ID and details.
Then you can run this command to list your local keys:
$ gpg --list-keys
In GitHub Actions workflow we use secrets.GPG_SIGNING_KEY
, which is our locally generated GPG signing key, exported as Base64 text.
To export your newly generated key, run:
$ gpg --export-secret-keys YOUR_ID_HERE | base64 > private.key
YOUR_ID_HERE
- is a public key, long string with unique characters it will be printed after the key is generated or when you run list keys command.
private.key
- is a file name that will be created in your current folder and will contain a Base64 representation of your private GPG key.
Next, you have to copy the private.key
content and create a new secret in your GitHub repository with the name: GPG_SIGNING_KEY
.
Note that we also pass environment variable: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
, here we use the secrets.GITHUB_TOKEN
which is automatically provided by GitHub Actions context, and for which we set the permissions at the top:
# Add permissions for GITHUB_TOKEN
permissions:
contents: read
packages: write
That's it. Now when you commit and push your code to the repo, you will be able to find and run a new GitHub Actions workflow that should build and release your library as a private package to GitHub private registry.
Step 3 Wait, so how do I use it now?
In order to use your package in your other projects, you have to add a similar line to project.clj
:
:repositories [["github" {:url "https://maven.pkg.github.com/cljcloud/my-lib"
:username "private-token"
:password :env/GITHUB_TOKEN}]]
and specify your library as usual Leiningen dependency, like this:
:dependencies [[org.clojure/clojure "1.10.3"]
[cljcloud/my-lib "0.1.20"]]
And when you run lein deps
locally, Leiningen may fail, with 401 Unauthorized error, that's because you don't have a GITHUB_TOKEN
environment variable, you can go to GitHub and generate one by following these simple instructions.
After that, you should set the environment variable via:
$ export GITHUB_TOKEN="your personal access token"
And retry with lein deps
should work now.
Related links
Posted on February 6, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.