Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ jobs:
- name: Vulnerability scan
run: go run golang.org/x/vuln/cmd/[email protected] ./...

# The release pipeline only runs on a tag push, so a broken .goreleaser.yaml
# would otherwise surface at the worst moment. Same version as `task release:check`.
- name: GoReleaser config
run: go run github.com/goreleaser/goreleaser/[email protected] check

# Both installers are fetched raw from this branch by users, so a syntax
# error ships the moment it merges.
- name: Install scripts
run: |
shellcheck --shell=sh install/install.sh
pwsh -NoProfile -Command '
$tokens = $null; $errors = $null
[void][System.Management.Automation.Language.Parser]::ParseFile("install/install.ps1", [ref]$tokens, [ref]$errors)
if ($errors) { $errors | ForEach-Object { Write-Error $_.ToString() }; exit 1 }
'

build:
name: build
runs-on: ubuntu-latest
Expand Down
17 changes: 16 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:

permissions:
contents: write
packages: write

jobs:
goreleaser:
Expand All @@ -28,11 +29,25 @@ jobs:
go-version: "1.25.x"
cache-dependency-path: go.sum

# The arm64 image is built on an amd64 runner.
- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: latest
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ test-report.json
# Build and release output
/bin/
/dist/
/completions/

# Dependency directories
vendor/
Expand Down
112 changes: 109 additions & 3 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
version: 2

project_name: kaiten

before:
hooks:
- go mod tidy
# Completions ship in every archive and Linux package, so they are
# generated once here from the binary that is about to be released.
- sh -c 'mkdir -p completions && for shell in bash zsh fish; do go run ./cmd/kaiten completion "$shell" > "completions/kaiten.$shell"; done'

builds:
- id: kaiten
Expand All @@ -25,10 +29,12 @@ builds:
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}

# install/install.sh and install/install.ps1 download these by name, so the
# template is part of the install contract: kaiten_<os>_<arch>.tar.gz|zip.
archives:
- formats: [tar.gz]
name_template: >-
{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}
- id: archives
formats: [tar.gz]
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
format_overrides:
- goos: windows
formats: [zip]
Expand All @@ -39,10 +45,91 @@ archives:
- LICENSE
- NOTICE
- README.md
- completions/*

checksum:
name_template: "checksums.txt"

# .deb, .rpm and .apk, attached to the release next to the archives. Named
# like the archives, without the version, so that
# releases/latest/download/kaiten_linux_amd64.deb is a stable URL; the version
# lives in the package metadata, where dpkg, rpm and apk read it.
nfpms:
- id: packages
package_name: kaiten
file_name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
vendor: Kaiten
homepage: https://kaiten.sh
maintainer: Kaiten <[email protected]>
description: Command-line interface for the Kaiten API.
license: Apache-2.0
formats:
- deb
- rpm
- apk
bindir: /usr/bin
section: utils
contents:
- src: completions/kaiten.bash
dst: /usr/share/bash-completion/completions/kaiten
- src: completions/kaiten.zsh
dst: /usr/share/zsh/site-functions/_kaiten
- src: completions/kaiten.fish
dst: /usr/share/fish/vendor_completions.d/kaiten.fish
# The doc/license content types are rpm-only; deb and apk silently skip
# them, so each packager gets the license file in its own convention.
- src: LICENSE
dst: /usr/share/licenses/kaiten/LICENSE
type: license
packager: rpm
- src: NOTICE
dst: /usr/share/licenses/kaiten/NOTICE
type: license
packager: rpm
- src: LICENSE
dst: /usr/share/doc/kaiten/copyright
packager: deb
- src: NOTICE
dst: /usr/share/doc/kaiten/NOTICE
packager: deb
- src: LICENSE
dst: /usr/share/licenses/kaiten/LICENSE
packager: apk
- src: NOTICE
dst: /usr/share/licenses/kaiten/NOTICE
packager: apk

# ghcr.io/kaitencloud/cli:<version> and :latest, one multi-platform image. The
# workflow logs in to GHCR with the job's GITHUB_TOKEN. Built in the publish
# phase, so `--snapshot` produces per-platform local images and `--skip=docker`
# none at all.
dockers_v2:
- id: kaiten
ids:
- kaiten
images:
- ghcr.io/kaitencloud/cli
tags:
- "{{ .Version }}"
# A pre-release tag (v1.2.0-rc.1) does not move latest.
- "{{ if not .Prerelease }}latest{{ end }}"
platforms:
- linux/amd64
- linux/arm64
dockerfile: Dockerfile
extra_files:
- LICENSE
- NOTICE
labels:
org.opencontainers.image.title: "{{ .ProjectName }}"
org.opencontainers.image.description: Command-line interface for the Kaiten API
org.opencontainers.image.url: https://github.com/kaitencloud/cli
org.opencontainers.image.source: https://github.com/kaitencloud/cli
org.opencontainers.image.version: "{{ .Version }}"
org.opencontainers.image.revision: "{{ .FullCommit }}"
org.opencontainers.image.created: "{{ .Date }}"
org.opencontainers.image.licenses: Apache-2.0

changelog:
sort: asc
groups:
Expand All @@ -64,3 +151,22 @@ release:
github:
owner: kaitencloud
name: cli
# A pre-release tag (v1.2.0-rc.1) is published as a GitHub pre-release, which
# also keeps install.sh's "latest" pointing at the last stable one.
prerelease: auto
footer: |
## Install

```shell
# macOS and Linux
curl -fsSL https://raw.githubusercontent.com/kaitencloud/cli/main/install/install.sh | sh -s -- {{ .Version }}

# Windows
irm https://raw.githubusercontent.com/kaitencloud/cli/main/install/install.ps1 | iex

# Container
docker run --rm ghcr.io/kaitencloud/cli:{{ .Version }} version
```

Every archive and package above is listed in `checksums.txt`. See the
[README](https://github.com/kaitencloud/cli#installation) for the other options.
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Built by GoReleaser from the release binaries; see dockers_v2 in
# .goreleaser.yaml. The build context holds one binary per platform under
# <os>/<arch>/, and the static distroless base carries CA certificates and a
# non-root user and nothing else.
FROM gcr.io/distroless/static-debian12:nonroot

ARG TARGETPLATFORM
COPY $TARGETPLATFORM/kaiten /usr/local/bin/kaiten
COPY LICENSE NOTICE /usr/share/doc/kaiten/

ENTRYPOINT ["/usr/local/bin/kaiten"]
106 changes: 94 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,97 @@ output and exit codes a script can branch on.

## Installation

**Pre-built binaries** for Linux, macOS and Windows (amd64 and arm64) are attached
to every release on the [releases page](https://github.com/kaitencloud/cli/releases),
with a `checksums.txt`.
Every method below installs the same binary from the
[GitHub release](https://github.com/kaitencloud/cli/releases). Check what you got with
`kaiten version`.

**With Go 1.25 or newer:**
### macOS

```shell
curl -fsSL https://raw.githubusercontent.com/kaitencloud/cli/main/install/install.sh | sh
```

The script verifies the archive against the release's `checksums.txt` and installs into
`/usr/local/bin` (`KAITEN_INSTALL_DIR` changes that).

### Linux

```shell
curl -fsSL https://raw.githubusercontent.com/kaitencloud/cli/main/install/install.sh | sh
```

Or a package for your distribution (replace `amd64` with `arm64` as needed). Each installs
the binary and shell completions:

```shell
# Debian, Ubuntu
curl -fsSLO https://github.com/kaitencloud/cli/releases/latest/download/kaiten_linux_amd64.deb
sudo dpkg -i kaiten_linux_amd64.deb

# Fedora, RHEL, openSUSE
sudo rpm -i https://github.com/kaitencloud/cli/releases/latest/download/kaiten_linux_amd64.rpm

# Alpine
curl -fsSLO https://github.com/kaitencloud/cli/releases/latest/download/kaiten_linux_amd64.apk
sudo apk add --allow-untrusted kaiten_linux_amd64.apk
```

### Windows

```powershell
irm https://raw.githubusercontent.com/kaitencloud/cli/main/install/install.ps1 | iex
```

The script verifies the archive against `checksums.txt`, installs `kaiten.exe` into
`%LOCALAPPDATA%\Programs\kaiten` (no administrator rights needed) and adds it to your
user `PATH`.

### Container

```shell
docker run --rm -e KAITEN_BASE_URL -e KAITEN_AUTH_TOKEN ghcr.io/kaitencloud/cli:latest instances list
```

`ghcr.io/kaitencloud/cli` is tagged `latest` and with each version, built for
`linux/amd64` and `linux/arm64`, and runs as a non-root user. Pass the configuration
through the environment: the image keeps no config file between runs.

### Go

With Go 1.25 or newer:

```shell
go install github.com/kaitencloud/cli/cmd/kaiten@latest
```

**From source**, with [Task](https://taskfile.dev) installed:
### Binaries

Every release attaches `kaiten_<os>_<arch>.tar.gz` (`.zip` on Windows) for Linux, macOS and
Windows on amd64 and arm64, with a `checksums.txt` of SHA-256 sums. Unpack it and put
`kaiten` on your `PATH`.

### A specific version

```shell
git clone https://github.com/kaitencloud/cli.git && cd cli
task build # bin/kaiten
curl -fsSL https://raw.githubusercontent.com/kaitencloud/cli/main/install/install.sh | sh -s -- 1.2.3
```

```powershell
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/kaitencloud/cli/main/install/install.ps1))) -Version 1.2.3
```

**Shell completion** is available for bash, zsh, fish and PowerShell:
`go install [email protected]` and the container tags pin a version directly.

### Shell completion

The Linux packages and the archives ship completions for bash, zsh and fish. Any other
install can generate them from the binary:

```shell
kaiten completion zsh > "${fpath[1]}/_kaiten"
kaiten completion bash > /etc/bash_completion.d/kaiten
kaiten completion fish > ~/.config/fish/completions/kaiten.fish
kaiten completion powershell | Out-String | Invoke-Expression
```

## Quick start
Expand Down Expand Up @@ -210,17 +280,29 @@ task build # bin/kaiten
task test # go test -race -shuffle=on -cover ./...
task lint # golangci-lint, the version CI runs
task fmt # gofumpt + gci
task vuln # govulncheck over reachable code
task release:check # validate .goreleaser.yaml without building
task vuln # govulncheck over reachable code
task release:check # validate .goreleaser.yaml and the install scripts
task release:snapshot # build every release artifact into dist/, publish nothing
```

The API client is [`github.com/kaitencloud/sdk-go`](https://github.com/kaitencloud/sdk-go);
a change to a request or response shape belongs there. This repository holds the
command surface: flags, input sources, output formatting, confirmation prompts and
exit codes.

Releases are cut by pushing a `vX.Y.Z` tag: GoReleaser builds the six binaries,
writes the checksums and publishes the GitHub release.
### Releasing

Pushing a `vX.Y.Z` tag runs `.github/workflows/release.yml`, and GoReleaser
(`.goreleaser.yaml`) produces everything the Installation section points at: the six
binaries and their archives with completions, `checksums.txt`, the `.deb`/`.rpm`/`.apk`
packages and the `ghcr.io/kaitencloud/cli` image.
`task release:snapshot` builds all of it locally into `dist/` without a tag, and
`task release:check` validates the configuration and the install scripts; CI runs the
latter on every pull request.

A pre-release tag such as `v1.2.0-rc.1` is published as a GitHub pre-release: the
install scripts' "latest" and the `latest` image tag keep pointing at the last stable
version.

## Contributing

Expand Down
11 changes: 10 additions & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,20 @@ tasks:
- go run golang.org/x/vuln/cmd/govulncheck@{{.GOVULNCHECK_VERSION}} ./...

release:check:
desc: Validate .goreleaser.yaml without building anything
desc: Validate .goreleaser.yaml and the install scripts without building anything
cmds:
# The release pipeline only runs on a tag push, so a broken config is otherwise
# discovered at the worst possible moment. This is the same check, on demand.
- go run github.com/goreleaser/goreleaser/v2@{{.GORELEASER_VERSION}} check
- shellcheck --shell=sh install/install.sh

release:snapshot:
desc: Build every archive and package into dist/ without publishing
cmds:
# Everything a tag would release, from the working tree, without a tag or
# a registry: six binaries, the archives and .deb/.rpm/.apk. Docker is
# skipped because the arm64 image needs QEMU; CI has it, a laptop may not.
- go run github.com/goreleaser/goreleaser/v2@{{.GORELEASER_VERSION}} release --snapshot --clean --skip=publish,docker

coverage:
desc: Write a coverage profile to coverage.out
Expand Down
Loading
Loading