Skip to content
Merged
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
18 changes: 18 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.git
.github
.env
.DS_Store
/dist
/specsdeployd
taskfiles
.dockerignore
.editorconfig
.gitignore
.golangci.yml
.goreleaser.yml
.markdownlint-cli2.yaml
compose.yml
Dockerfile
LICENSE
README.md
Taskfile.dist.yml
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: CI

on:
push:
branches: [main]
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
unit-tests:
name: Unit tests
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod

- name: Cache Go modules
uses: actions/cache@v6
with:
path: |
~/.cache/go/pkg/mod
~/.cache/go/build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Check go.mod is tidy
run: go mod tidy -diff

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.12

- name: Test
run: go test -race -count=1 ./...

- name: Build (smoke)
run: go build -o /dev/null .
14 changes: 14 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: "2"
linters:
default: standard
enable:
- wsl_v5
- whitespace
- modernize
settings:
errcheck:
# Reporting a failed write would itself need a working stream.
exclude-functions:
- fmt.Fprint
- fmt.Fprintln
- fmt.Fprintf
31 changes: 31 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
version: 2

builds:
- id: specsdeployd
main: .
binary: specsdeployd
flags:
- -trimpath
- -tags=netgo
ldflags:
- -s -w
- -X github.com/specsnl/specsdeployd/internal/cmd.Version={{ .Version }}
env:
- CGO_ENABLED=0
# Linux only: the Ansible role that installs this never targets anything else.
goos: [linux]
goarch: [amd64, arm64]

archives:
- formats: [tar.gz]
name_template: "specsdeployd_{{ .Os }}_{{ .Arch }}"

checksum:
name_template: "checksums.txt"

release:
github:
owner: specsnl
name: specsdeployd
draft: false
prerelease: auto
23 changes: 23 additions & 0 deletions .markdownlint-cli2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# CLI config: https://github.com/DavidAnson/markdownlint-cli2?tab=readme-ov-file#markdownlint-cli2jsonc
# Lint rules: https://github.com/DavidAnson/markdownlint?tab=readme-ov-file#rules--aliases

config:
line-length:
line_length: 1000
tables: false
no-inline-html:
allowed_elements:
- dl
- dt
- dd
- details
- summary
- code
ul-style:
style: dash

globs:
- '**/*.md'

gitignore: true
showFound: true
46 changes: 46 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# syntax=docker/dockerfile:1
# check=error=true

# Latest version: https://hub.docker.com/_/golang/tags
FROM --platform=$BUILDPLATFORM golang:1.27.1-trixie AS base

WORKDIR /src

RUN apt-get update \
&& apt-get install --assume-yes --no-install-recommends \
ca-certificates \
tree \
git \
openssh-client

FROM base AS builder-download

COPY go.mod .
COPY go.sum .

RUN --mount=type=cache,target=/go/pkg/mod \
go mod download

FROM builder-download AS build

COPY . .

ARG TARGETOS
ARG TARGETARCH
ARG GOOS
ARG GOARCH
ARG GO_MODULE=github.com/specsnl/specsdeployd
ARG SPECSDEPLOYD_VERSION=dev

RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=${GOOS:-$TARGETOS} GOARCH=${GOARCH:-$TARGETARCH} go build \
-trimpath \
-tags netgo \
-ldflags "-s -w -X ${GO_MODULE}/internal/cmd.Version=${SPECSDEPLOYD_VERSION}" -o ./specsdeployd

# No runtime image: specsdeployd is a host systemd service driving podman and
# systemctl, so this Dockerfile only ever produces the binary.
FROM scratch AS export

COPY --from=build /src/specsdeployd /specsdeployd
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,28 @@ GitHub release. This repository ships **only the binary**.

## Status

Early — nothing is implemented yet. The repository is being scaffolded around a binary that answers
`specsdeployd version` and nothing else, which is enough for the Ansible role to install and assert
against. The webhook receiver lands on top of that.
Early. The binary answers its version and nothing else:

```sh
specsdeployd version # specsdeployd version 1.2.3
specsdeployd --version # 1.2.3
```

That is enough for the Ansible role to install it, verify its checksum and assert that it is on the
host. The webhook receiver lands on top of it — no config file is read, no privileged command is
run, and nothing listens on a port yet.

## Contributing

Every command runs through [Task](https://taskfile.dev), which wraps the Docker Compose services
that pin the Go, golangci-lint and Node versions — so a check runs the same way locally as it does
in CI. Building from a checkout needs nothing but Docker and Task. Run `task --list` for the full
set.

```sh
task build # the binary, into the working directory
task checkall # tidy:check, lint, test, md:check — run this before opening a pull request
```

## License

Expand Down
131 changes: 131 additions & 0 deletions Taskfile.dist.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# https://taskfile.dev
version: "3"

includes:
md: ./taskfiles/Taskfile.md.yml
lint:
taskfile: ./taskfiles/Taskfile.lint.yml
flatten: true

silent: true

vars:
# The leading v is stripped to match goreleaser's {{ .Version }}. The fallback
# is captured before the strip, so an undescribable tree yields "dev" rather
# than an empty string.
SPECSDEPLOYD_VERSION:
sh: described=$(git describe --tags --always --dirty="-dev" 2>/dev/null || echo "dev"); echo "${described#v}"

tasks:

dc:
desc: Run a docker compose command
internal: true
vars:
FIXUID:
sh: echo ${FIXUID:-$(id -u)}
FIXGID:
sh: echo ${FIXGID:-$(id -g)}
cmds:
- FIXUID={{ .FIXUID }} FIXGID={{ .FIXGID }} docker compose {{ .SUB_CMD }} {{ .CLI_ARGS }}

dc:build:
desc: Run docker compose build
cmds:
- task: dc
vars:
SUB_CMD: --profile=* build

dc:run:*:
desc: Run a one-off docker compose command
vars:
SERVICE: '{{ index .MATCH 0 }}'
RUN_FLAGS: '{{ .RUN_FLAGS | default "" }}'
USER_FLAG: '{{ (ne (.NON_ROOT | default "true") "false") | ternary " --user $(id -u):$(id -g)" "" }}'
COMPOSE_SERVICES:
sh: COMPOSE_PROFILES="*" docker compose config --services
cmds:
- task: dc
vars:
SUB_CMD: 'run --rm {{ .RUN_FLAGS }} {{ .USER_FLAG }} {{ .SERVICE }} {{ .SUB_CMD }}'
CLI_ARGS: '{{ .CLI_ARGS }}'
requires:
vars:
- name: SERVICE
enum:
ref: .COMPOSE_SERVICES | splitLines | compact

dc:shell:
desc: Shell into a docker compose service
cmds:
- task: dc
vars:
SUB_CMD: "run --tty --rm go-builder /bin/bash"
CLI_ARGS: "{{.CLI_ARGS}}"

build:
desc: Build specsdeployd into the working directory
cmds:
- task: lint
- >-
docker buildx bake --file compose.yml
--set "go-binary.output=type=local,dest=."
--set "go-binary.args.GOARCH={{ARCH}}"
--set "go-binary.args.GOOS={{OS}}"
--set "go-binary.args.SPECSDEPLOYD_VERSION={{.SPECSDEPLOYD_VERSION}}"
go-binary

tidy:check:
desc: Check that go.mod and go.sum are tidy
summary: |
Fails, without writing anything, when `go mod tidy` would change go.mod or go.sum.

Run `task tidy` to apply what it reports.
cmds:
- task: dc:run:go-builder
vars:
SUB_CMD: "go mod tidy -diff"

tidy:
desc: Run go mod tidy
cmds:
- task: dc:run:go-builder
vars:
SUB_CMD: "go mod tidy"

test:
desc: Run go tests
cmds:
- task: dc:run:go-builder
vars:
SUB_CMD: "go test -race -tags=integration ./..."

checkall:
desc: Run every check — the tidy check, golangci-lint, the Go tests, and markdownlint
summary: |
The full local check sequence: tidy:check, lint, test, md:check.

Every step reports; none of them writes. Run this before opening a pull request.
cmds:
- task: tidy:check
- task: lint
- task: test
- task: md:check

release:dry-run:
desc: Build a snapshot release locally without publishing
cmds:
- task: dc:run:goreleaser
vars:
SUB_CMD: release --snapshot --clean

cleanup:
desc: Cleanup workspace
summary: |
Cleanup of almost all gitignored files, untracked files and development containers.

The following files/dirs are excluded: none
cmds:
- git clean
-xd
--force
Loading