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
10 changes: 4 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,10 @@ jobs:
restore-keys: |
${{ runner.os }}-go-

- name: Vet
run: go vet ./...

- name: Fix (diff mode)
continue-on-error: true
run: go fix -diff ./...
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.12

- name: Test
run: go test -race -count=1 ./...
Expand Down
16 changes: 16 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: "2"
linters:
default: standard
enable:
- wsl_v5
- whitespace
- modernize
# - wrapcheck
settings:
errcheck:
# Output to stdout/stderr is best-effort: the output Writer interface has
# no error channel and reporting a write failure would itself need a
# working stream. See internal/util/output/writer.go.
exclude-functions:
- fmt.Fprintln
- charm.land/lipgloss/v2.Fprintln
105 changes: 26 additions & 79 deletions Taskfile.dist.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
# https://taskfile.dev
version: "3"

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

silent: true

# env:
# BUILDKIT_PROGRESS: plain

tasks:

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

dc:build:
desc: Run docker compose build
Expand All @@ -23,12 +36,21 @@ tasks:
dc:run:*:
desc: Run a one-off docker compose command
vars:
SERVICE: "{{index .MATCH 0}}"
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 {{.SERVICE}} {{.SUB_CMD}}"
CLI_ARGS: "{{.CLI_ARGS}}"
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
Expand All @@ -53,27 +75,6 @@ tasks:
--set "go-binary.args.SPECS_VERSION={{.SPECS_VERSION}}"
go-binary

vet:
desc: Run go vet
cmds:
- task: dc:run:go-builder
vars:
SUB_CMD: go vet

fix:
desc: Run go fix
cmds:
- task: dc:run:go-builder
vars:
SUB_CMD: "go fix ./..."

fix:diff:
desc: Run go fix in diff mode
cmds:
- task: dc:run:go-builder
vars:
SUB_CMD: "go fix -diff ./..."

test:
desc: Run go tests
cmds:
Expand All @@ -88,60 +89,6 @@ tasks:
vars:
SUB_CMD: release --snapshot --clean

docs:build:
desc: Build the static documentation site into docs/public/
cmds:
- task: dc:run:hugo
vars:
SUB_CMD: --minify

docs:serve:
desc: Start Hugo development server with live reload (http://localhost:1313)
cmds:
- task: dc
vars:
SUB_CMD: "run --rm --service-ports hugo server --bind 0.0.0.0"

docs:preview:
desc: Build and serve the static docs site locally (http://localhost:8080)
cmds:
- task: dc:run:hugo
vars:
SUB_CMD: --minify --baseURL http://localhost:8080
- task: dc
vars:
SUB_CMD: "run --rm --service-ports docs-preview"

docs:mod:tidy:
desc: Tidy Hugo module dependencies and regenerate go.sum
cmds:
- task: dc:run:hugo
vars:
SUB_CMD: mod tidy

md:check:
desc: Check the style of Markdown files with markdownlint
cmds:
- task: dc:run:node
vars:
SUB_CMD: "npx --yes markdownlint-cli2 {{.SUB_CMD}}"

md:fix:
desc: Fix Markdown style — align tables, then apply autofixable lint rules
cmds:
- task: md:fix-tables
- task: md:check
vars:
SUB_CMD: --fix

md:fix-tables:
desc: Align table spacing in Markdown files
cmds:
# dotglob so hidden directories (e.g. .github) are covered too.
- task: dc:run:node
vars:
SUB_CMD: bash -c "shopt -s globstar dotglob && npx --yes markdown-table-formatter **/*.md"

cleanup:
desc: Cleanup workspace
summary: |
Expand Down
74 changes: 66 additions & 8 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -1,50 +1,91 @@
volumes:
go-mod:
go-build-cache:
hugo-mod:
cache:

services:
volume-init:
image: busybox:1.37.0-musl
user: root
volumes:
- cache:/cache
command:
- sh
- -c
- |
mkdir -p /cache/go-build
mkdir -p /cache/go-mod
mkdir -p /cache/golangci-lint
chown -R ${FIXUID:-1000}:${FIXGID:-1000} /cache

go-builder:
profiles: ["build"]
user: ${FIXUID:-1000}:${FIXGID:-1000}
build:
context: .
dockerfile: Dockerfile
target: builder-download
environment:
GOCACHE: /cache/go-build
GOMODCACHE: /cache/go-mod
depends_on:
volume-init:
condition: service_completed_successfully
volumes:
- .:/src
- go-mod:/go/pkg/mod
- go-build-cache:/root/.cache/go-build
- cache:/cache

go-binary:
profiles: ["build"]
user: ${FIXUID:-1000}:${FIXGID:-1000}
build:
context: .
dockerfile: Dockerfile
target: export
environment:
GOCACHE: /cache/go-build
GOMODCACHE: /cache/go-mod
depends_on:
volume-init:
condition: service_completed_successfully
volumes:
- cache:/cache

goreleaser:
profiles: ["build"]
user: ${FIXUID:-1000}:${FIXGID:-1000}
# Latest version: https://hub.docker.com/r/goreleaser/goreleaser/tags
image: goreleaser/goreleaser:v2.15.1
working_dir: /src
environment:
GOCACHE: /cache/go-build
GOMODCACHE: /cache/go-mod
depends_on:
volume-init:
condition: service_completed_successfully
volumes:
- .:/src
- go-mod:/go/pkg/mod
- go-build-cache:/root/.cache/go-build
- cache:/cache

hugo:
profiles: ["docs"]
user: ${FIXUID:-1000}:${FIXGID:-1000}
# Latest version: https://hub.docker.com/r/hugomods/hugo/tags
image: hugomods/hugo:debian-go-git-non-root-0.161.1
working_dir: /src/docs
environment:
GOCACHE: /cache/go-build
GOMODCACHE: /cache/go-mod
volumes:
- .:/src
- hugo-mod:/root/go/pkg/mod
- cache:/cache
depends_on:
volume-init:
condition: service_completed_successfully
ports:
- "1313:1313"

docs-preview:
profiles: ["docs"]
user: ${FIXUID:-1000}:${FIXGID:-1000}
image: nginx:alpine
volumes:
- ./docs/public:/usr/share/nginx/html:ro
Expand All @@ -59,3 +100,20 @@ services:
working_dir: /src
volumes:
- .:/src

golangci-lint:
profiles: ["lint"]
user: ${FIXUID:-1000}:${FIXGID:-1000}
# Latest version: https://hub.docker.com/r/golangci/golangci-lint/tags
image: golangci/golangci-lint:v2.12.2-alpine
working_dir: /src
environment:
GOCACHE: /cache/go-build
GOMODCACHE: /cache/go-mod
GOLANGCI_LINT_CACHE: /cache/golangci-lint
depends_on:
volume-init:
condition: service_completed_successfully
volumes:
- .:/src
- cache:/cache
14 changes: 7 additions & 7 deletions docs/content/docs/architecture/template-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,13 +297,13 @@ All of Go's standard `text/template` built-ins are available, plus:

### Custom Functions (`internal/template/specsregistry.go`)

| Function | Signature | Description |
|------------------|-------------------------------------------------------------------|---------------------------------|
| `hostname` | `() string` | System hostname |
| `username` | `() string` | Current OS username |
| `toBinary` | `(n int) string` | Format integer as binary string |
| `formatFilesize` | `(bytes float64) string` | Human-readable size (KB/MB/GB…) |
| `password` | `(length, digits, symbols int, noUpper, allowRepeat bool) string` | Secure random password |
| Function | Signature | Description |
|------------------|-------------------------------------------------------------------|-----------------------------------------|
| `hostname` | `() string` | System hostname |
| `username` | `() string` | Current OS username (env/UID fallbacks) |
| `toBinary` | `(n int) string` | Format integer as binary string |
| `formatFilesize` | `(bytes float64) string` | Human-readable size (KB/MB/GB…) |
| `password` | `(length, digits, symbols int, noUpper, allowRepeat bool) string` | Secure random password |

### Sprout Functions

Expand Down
14 changes: 7 additions & 7 deletions docs/content/docs/template-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ Templates have access to 200+ functions provided by [Sprout](https://github.com/

## Specs functions

| Function | Signature | Description |
|------------------|---------------------------------------------------------------------------|--------------------------------------------|
| `hostname` | `hostname` → `string` | System hostname |
| `username` | `username` → `string` | Current OS username |
| `toBinary` | `toBinary <int>` → `string` | Integer to binary string |
| `formatFilesize` | `formatFilesize <bytes>` → `string` | Human-readable file size (e.g. `"1.0 MB"`) |
| `password` | `password <length> <digits> <symbols> <noUpper> <allowRepeat>` → `string` | Generate a secure random password |
| Function | Signature | Description |
|------------------|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------|
| `hostname` | `hostname` → `string` | System hostname |
| `username` | `username` → `string` | Current OS username (falls back to `$USER`/`$LOGNAME`/`$USERNAME`, then the numeric UID) |
| `toBinary` | `toBinary <int>` → `string` | Integer to binary string |
| `formatFilesize` | `formatFilesize <bytes>` → `string` | Human-readable file size (e.g. `"1.0 MB"`) |
| `password` | `password <length> <digits> <symbols> <noUpper> <allowRepeat>` → `string` | Generate a secure random password |

```text
Default registry: {{ hostname }}.azurecr.io
Expand Down
17 changes: 13 additions & 4 deletions internal/cmd/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ func TestWriteMetadata_PreservesSuppliedCreated(t *testing.T) {
if err != nil {
t.Fatalf("LoadMetadata: %v", err)
}

if got == nil {
t.Fatal("LoadMetadata returned nil metadata")
}
if !got.Created.Time.Equal(want) {

if !got.Created.Equal(want) {
t.Errorf("Created = %s, want %s", got.Created.Time, want)
}
if !got.Updated.Time.Equal(want) {

if !got.Updated.Equal(want) {
t.Errorf("Updated = %s, want %s", got.Updated.Time, want)
}
}
Expand All @@ -48,6 +51,7 @@ func TestWriteMetadata_UpgradeRoundTripPreservesCreated(t *testing.T) {
if err != nil {
t.Fatalf("LoadMetadata: %v", err)
}

if meta == nil {
t.Fatal("LoadMetadata returned nil metadata")
}
Expand All @@ -64,18 +68,23 @@ func TestWriteMetadata_UpgradeRoundTripPreservesCreated(t *testing.T) {
if err != nil {
t.Fatalf("LoadMetadata after upgrade: %v", err)
}

if upgraded == nil {
t.Fatal("LoadMetadata returned nil metadata after upgrade")
}
if !upgraded.Created.Time.Equal(original) {

if !upgraded.Created.Equal(original) {
t.Errorf("Created after upgrade = %s, want %s", upgraded.Created.Time, original)
}
if !upgraded.Updated.Time.Equal(upgradedAt) {

if !upgraded.Updated.Equal(upgradedAt) {
t.Errorf("Updated after upgrade = %s, want %s", upgraded.Updated.Time, upgradedAt)
}

if upgraded.Commit != "new-sha" {
t.Errorf("Commit after upgrade = %q, want %q", upgraded.Commit, "new-sha")
}

if upgraded.Version != "v1.1.0" {
t.Errorf("Version after upgrade = %q, want %q", upgraded.Version, "v1.1.0")
}
Expand Down
Loading