Our great sponsors
-
WorkOS
The modern identity platform for B2B SaaS. The APIs are flexible and easy-to-use, supporting authentication, user identity, and complex enterprise features like SSO and SCIM provisioning.
-
Puts Debuggerer
Ruby library for improved puts debugging, automatically displaying bonus useful information such as source line number and source code.
I started a new job in July 2022. While the developer experience is great, one of the things that I missed at my new job was a CLI to rule them all (like I had at my old job). I thought of building an easily extensible CLI tool which I did with go and cobra. To handle distribution to the engineers, we decided to use both go install and homebrew.
The first thing to do is create a Github release, if you use go, I highly recommend using goreleaser. I wrote a tutorial on how to do that here. It truly makes releases in go easy. If you're using another language, you should check out how to manage releases in GitHub here.
We're almost done. To install our tool via Homebrew, we need to export a Github token that provides access to our private repos. A new token can be created here. The token needs to have repo permissions. We need to export the token as HOMEBREW_GITHUB_API_TOKEN. To export the token, we run:
Create a GitHub repo called homebrew-tap. An example is here. Create a Formula folder. In the root of the repo, create a file called custom_download_strategy.rb. The contents are shown above In the Formula folder, create a ruby file with the name of your tool like mytool.rb
require "download_strategy" class GitHubPrivateRepositoryDownloadStrategy < CurlDownloadStrategy require "utils/formatter" require "utils/github" def initialize(url, name, version, **meta) super parse_url_pattern set_github_token end def parse_url_pattern unless match = url.match(%r{https://github.com/([^/]+)/([^/]+)/(\S+)}) raise CurlDownloadStrategyError, "Invalid url pattern for GitHub Repository." end _, @owner, @repo, @filepath = *match end def download_url "https://#{@github_token}@github.com/#{@owner}/#{@repo}/#{@filepath}" end private def _fetch(url:, resolved_url:, timeout:) curl_download download_url, to: temporary_path, timeout: timeout end def set_github_token @github_token = ENV["HOMEBREW_GITHUB_API_TOKEN"] unless @github_token raise CurlDownloadStrategyError, "Environmental variable HOMEBREW_GITHUB_API_TOKEN is required." end validate_github_repository_access! end def validate_github_repository_access! # Test access to the repository GitHub.repository(@owner, @repo) rescue GitHub::HTTPNotFoundError # We only handle HTTPNotFoundError here, # becase AuthenticationFailedError is handled within util/github. message = <<~EOS HOMEBREW_GITHUB_API_TOKEN can not access the repository: #{@owner}/#{@repo} This token may not have permission to access the repository or the url of formula may be incorrect. EOS raise CurlDownloadStrategyError, message end end # GitHubPrivateRepositoryReleaseDownloadStrategy downloads tarballs from GitHub # Release assets. To use it, add # `:using => GitHubPrivateRepositoryReleaseDownloadStrategy` to the URL section of # your formula. This download strategy uses GitHub access tokens (in the # environment variables HOMEBREW_GITHUB_API_TOKEN) to sign the request. class GitHubPrivateRepositoryReleaseDownloadStrategy < GitHubPrivateRepositoryDownloadStrategy def initialize(url, name, version, **meta) super end def parse_url_pattern url_pattern = %r{https://github.com/([^/]+)/([^/]+)/releases/download/([^/]+)/(\S+)} unless @url =~ url_pattern raise CurlDownloadStrategyError, "Invalid url pattern for GitHub Release." end _, @owner, @repo, @tag, @filename = *@url.match(url_pattern) end def download_url "https://#{@github_token}@api.github.com/repos/#{@owner}/#{@repo}/releases/assets/#{asset_id}" end private def _fetch(url:, resolved_url:, timeout:) # HTTP request header `Accept: application/octet-stream` is required. # Without this, the GitHub API will respond with metadata, not binary. curl_download download_url, "--header", "Accept: application/octet-stream", to: temporary_path, timeout: timeout end def asset_id @asset_id ||= resolve_asset_id end def resolve_asset_id release_metadata = fetch_release_metadata assets = release_metadata["assets"].select { |a| a["name"] == @filename } raise CurlDownloadStrategyError, "Asset file not found." if assets.empty? assets.first["id"] end def fetch_release_metadata GitHub.get_release(@owner, @repo, @tag) end end
I started a new job in July 2022. While the developer experience is great, one of the things that I missed at my new job was a CLI to rule them all (like I had at my old job). I thought of building an easily extensible CLI tool which I did with go and cobra. To handle distribution to the engineers, we decided to use both go install and homebrew.