Skip to content

Commit c600e92

Browse files
Initial implementation of Scaleway Apple Silicon fleeting plugin
- Implements the fleeting InstanceGroup interface for Scaleway Mac mini servers - Uses the official scaleway-sdk-go Apple Silicon v1alpha1 API - Supports M1-M, M2-M, M2-L server types in fr-par-3 - Servers are identified by a name prefix (fleeting-<name>-<index>) - ConnectInfo defaults to darwin/arm64/ssh with the Scaleway-provided SSH username - GitHub Actions workflow publishes a multi-platform OCI artifact to GHCR via fleeting-artifact, enabling gitlab-runner fleeting install
0 parents  commit c600e92

9 files changed

Lines changed: 870 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v[0-9]+.[0-9]+.[0-9]+'
7+
8+
permissions:
9+
contents: write # create GitHub releases & upload assets
10+
packages: write # push to GHCR
11+
id-token: write # keyless cosign signing via OIDC
12+
13+
env:
14+
IMAGE: ghcr.io/${{ github.repository }}
15+
GO_VERSION: "1.24"
16+
17+
jobs:
18+
# ── 1. Build binaries for all platforms (parallelised) ───────────────────────
19+
build:
20+
name: Build (${{ matrix.os }}-${{ matrix.arch }}${{ matrix.variant && format('v{0}', matrix.variant) || '' }})
21+
runs-on: ubuntu-latest
22+
strategy:
23+
matrix:
24+
include:
25+
- { os: linux, arch: amd64 }
26+
- { os: linux, arch: arm64 }
27+
- { os: linux, arch: arm, variant: "7" }
28+
- { os: linux, arch: "386" }
29+
- { os: darwin, arch: amd64 }
30+
- { os: darwin, arch: arm64 }
31+
- { os: windows, arch: amd64 }
32+
- { os: windows, arch: "386" }
33+
34+
steps:
35+
- uses: actions/checkout@v4
36+
37+
- uses: actions/setup-go@v5
38+
with:
39+
go-version: ${{ env.GO_VERSION }}
40+
cache: true
41+
42+
- name: Build
43+
env:
44+
GOOS: ${{ matrix.os }}
45+
GOARCH: ${{ matrix.arch }}
46+
GOARM: ${{ matrix.variant }}
47+
CGO_ENABLED: "0"
48+
run: |
49+
VERSION="${{ github.ref_name }}"
50+
REVISION="$(git rev-parse --short=8 HEAD)"
51+
REFERENCE="${{ github.ref_name }}"
52+
BUILT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
53+
PKG="github.com/codecentric/fleeting-plugin-scaleway"
54+
55+
ARCH_DIR="${{ matrix.arch }}${{ matrix.variant && format('v{0}', matrix.variant) || '' }}"
56+
EXT="${{ matrix.os == 'windows' && '.exe' || '' }}"
57+
OUTDIR="dist/${{ matrix.os }}/${ARCH_DIR}"
58+
mkdir -p "${OUTDIR}"
59+
60+
go build -a \
61+
-ldflags "-w -s \
62+
-X ${PKG}.VERSION=${VERSION} \
63+
-X ${PKG}.REVISION=${REVISION} \
64+
-X ${PKG}.REFERENCE=${REFERENCE} \
65+
-X ${PKG}.BUILT=${BUILT}" \
66+
-o "${OUTDIR}/plugin${EXT}" \
67+
./cmd/fleeting-plugin-scaleway/...
68+
69+
echo "Built ${OUTDIR}/plugin${EXT}"
70+
71+
- name: Upload dist artifact
72+
uses: actions/upload-artifact@v4
73+
with:
74+
name: dist-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.variant && format('v{0}', matrix.variant) || '' }}
75+
path: dist/
76+
retention-days: 1
77+
78+
# ── 2. Publish OCI artifact to GHCR & create GitHub release ─────────────────
79+
release:
80+
name: Publish OCI & GitHub Release
81+
runs-on: ubuntu-latest
82+
needs: build
83+
84+
steps:
85+
- uses: actions/checkout@v4
86+
87+
- uses: actions/setup-go@v5
88+
with:
89+
go-version: ${{ env.GO_VERSION }}
90+
cache: true
91+
92+
# Merge all per-platform dist/ directories into one
93+
- name: Download all dist artifacts
94+
uses: actions/download-artifact@v4
95+
with:
96+
pattern: dist-*
97+
path: dist/
98+
merge-multiple: true
99+
100+
- name: Display dist layout
101+
run: find dist -type f | sort
102+
103+
# Install fleeting-artifact
104+
- name: Install fleeting-artifact
105+
run: go install gitlab.com/gitlab-org/fleeting/fleeting-artifact/cmd/fleeting-artifact@latest
106+
107+
# Install cosign for keyless signing
108+
- name: Install cosign
109+
uses: sigstore/cosign-installer@v3
110+
111+
# Log in to GHCR
112+
- name: Log in to GHCR
113+
uses: docker/login-action@v3
114+
with:
115+
registry: ghcr.io
116+
username: ${{ github.actor }}
117+
password: ${{ secrets.GITHUB_TOKEN }}
118+
119+
# Push the multi-platform OCI artifact and capture the digest
120+
- name: Release OCI artifact
121+
id: oci
122+
env:
123+
VERSION: ${{ github.ref_name }}
124+
run: |
125+
# Strip leading 'v' for the OCI tag (fleeting-artifact expects semver without 'v')
126+
SEMVER="${VERSION#v}"
127+
DIGEST="$(fleeting-artifact release -dir dist "${{ env.IMAGE }}:${SEMVER}")"
128+
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
129+
echo "semver=${SEMVER}" >> "$GITHUB_OUTPUT"
130+
131+
# Keyless sign the OCI artifact with cosign + OIDC
132+
- name: Sign OCI artifact
133+
env:
134+
COSIGN_YES: "true"
135+
run: cosign sign "${{ steps.oci.outputs.digest }}"
136+
137+
# Build checksums of all binaries for the GitHub release assets
138+
- name: Generate checksums
139+
run: |
140+
find dist -type f -name 'plugin*' | sort | xargs sha256sum > checksums.txt
141+
cat checksums.txt
142+
143+
# Create a GitHub release with the binary archives and checksums
144+
- name: Create GitHub Release
145+
uses: softprops/action-gh-release@v2
146+
with:
147+
name: Release ${{ github.ref_name }}
148+
generate_release_notes: true
149+
files: |
150+
checksums.txt

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dist/
2+
bin/
3+
*.exe
4+
.env

Makefile

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
.DEFAULT_GOAL := build
2+
3+
export NAME ?= fleeting-plugin-scaleway
4+
export VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
5+
export OUT_PATH ?= out
6+
export CGO_ENABLED := 0
7+
8+
REVISION := $(shell git rev-parse --short=8 HEAD 2>/dev/null || echo unknown)
9+
REFERENCE := $(shell git symbolic-ref --short HEAD 2>/dev/null || echo HEAD)
10+
BUILT := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
11+
PKG := github.com/codecentric/fleeting-plugin-scaleway
12+
13+
GO_LDFLAGS := \
14+
-X $(PKG).VERSION=$(VERSION) \
15+
-X $(PKG).REVISION=$(REVISION) \
16+
-X $(PKG).REFERENCE=$(REFERENCE) \
17+
-X $(PKG).BUILT=$(BUILT) \
18+
-w -s
19+
20+
# All OS/arch targets.
21+
# Format: <os>/<arch> or <os>/<arch>/<variant> (variant only for ARM).
22+
# Windows binaries get a .exe suffix via the build rule.
23+
OS_ARCHS ?= \
24+
linux/amd64 \
25+
linux/arm64 \
26+
linux/arm/7 \
27+
linux/386 \
28+
darwin/amd64 \
29+
darwin/arm64 \
30+
windows/amd64 \
31+
windows/386
32+
33+
# ── build (host only) ────────────────────────────────────────────────────────
34+
35+
.PHONY: build
36+
build:
37+
@mkdir -p $(OUT_PATH)
38+
go build -ldflags "$(GO_LDFLAGS)" -o $(OUT_PATH)/$(NAME) ./cmd/$(NAME)/...
39+
40+
# ── cross-compile all targets ─────────────────────────────────────────────────
41+
42+
# dist/<os>/<arch[variant]>/plugin[.exe]
43+
# This layout is exactly what fleeting-artifact expects.
44+
.PHONY: all
45+
all:
46+
@$(MAKE) $(foreach t,$(OS_ARCHS),_build_$(subst /,_,$(t)))
47+
48+
# Generic rule: _build_<os>_<arch> or _build_<os>_<arch>_<variant>
49+
_build_%:
50+
$(eval PARTS := $(subst _, ,$*))
51+
$(eval OS := $(word 1,$(PARTS)))
52+
$(eval ARCH := $(word 2,$(PARTS)))
53+
$(eval GOARM := $(word 3,$(PARTS)))
54+
$(eval OUTDIR := dist/$(OS)/$(if $(GOARM),$(ARCH)v$(GOARM),$(ARCH)))
55+
$(eval EXT := $(if $(filter windows,$(OS)),.exe,))
56+
@mkdir -p $(OUTDIR)
57+
GOOS=$(OS) GOARCH=$(ARCH) $(if $(GOARM),GOARM=$(GOARM),) \
58+
go build -a -ldflags "$(GO_LDFLAGS)" \
59+
-o $(OUTDIR)/plugin$(EXT) \
60+
./cmd/$(NAME)/...
61+
@echo "Built $(OUTDIR)/plugin$(EXT)"
62+
63+
# ── test ─────────────────────────────────────────────────────────────────────
64+
65+
.PHONY: test
66+
test:
67+
go test -v -timeout=30m ./...
68+
69+
# ── OCI release (requires fleeting-artifact in PATH) ─────────────────────────
70+
71+
# Usage: make release-oci IMAGE=ghcr.io/codecentric/fleeting-plugin-scaleway VERSION=1.2.3
72+
.PHONY: release-oci
73+
release-oci:
74+
@if [ -z "$(IMAGE)" ]; then echo "IMAGE is required, e.g. make release-oci IMAGE=ghcr.io/org/fleeting-plugin-scaleway VERSION=1.2.3"; exit 1; fi
75+
@if [ -z "$(VERSION)" ]; then echo "VERSION is required"; exit 1; fi
76+
fleeting-artifact release -dir dist $(IMAGE):$(VERSION)
77+
78+
# ── clean ─────────────────────────────────────────────────────────────────────
79+
80+
.PHONY: clean
81+
clean:
82+
rm -rf $(OUT_PATH) dist

README.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# fleeting-plugin-scaleway
2+
3+
GitLab Runner fleeting plugin for [Scaleway Apple Silicon](https://www.scaleway.com/en/developers/api/apple-silicon/) (Mac mini M1/M2/M2-Pro dedicated servers).
4+
5+
## Overview
6+
7+
This plugin implements the [GitLab fleeting](https://docs.gitlab.com/runner/executors/docker_autoscaler.html) `InstanceGroup` interface, allowing the GitLab Runner docker-autoscaler executor to dynamically provision and deprovision Scaleway Apple Silicon servers.
8+
9+
> **Note:** Scaleway Apple Silicon servers have a **minimum 24-hour lease**. The plugin will provision servers on demand, but billing starts immediately and the server cannot be deleted before the lease expires.
10+
11+
## Plugin Configuration
12+
13+
| Parameter | Type | Required | Description |
14+
|---|---|---|---|
15+
| `secret_key` | string | No* | Scaleway API secret key. Defaults to `SCW_SECRET_KEY` env var. |
16+
| `project_id` | string | No* | Scaleway project ID. Defaults to `SCW_DEFAULT_PROJECT_ID` env var. |
17+
| `zone` | string | No | Availability zone. Defaults to `fr-par-3` (the only Apple Silicon zone). |
18+
| `server_type` | string | **Yes** | Server type to provision, e.g. `M2-M`, `M2-L`, `M1-M`. |
19+
| `os_id` | string | No | OS UUID to install. When omitted the default OS for the server type is used. |
20+
| `name` | string | **Yes** | Logical name for this instance group. Used to tag/identify servers. |
21+
22+
\* Can be provided via environment variable instead.
23+
24+
### Default connector config
25+
26+
| Parameter | Default |
27+
|---|---|
28+
| `os` | `darwin` |
29+
| `protocol` | `ssh` |
30+
| `arch` | `arm64` |
31+
32+
## Authentication
33+
34+
Provide credentials in one of two ways:
35+
36+
1. **Plugin config** — set `secret_key` and `project_id` in `plugin_config`.
37+
2. **Environment variables** — set `SCW_SECRET_KEY` and `SCW_DEFAULT_PROJECT_ID` on the runner process.
38+
39+
## Example runner config
40+
41+
```toml
42+
concurrent = 4
43+
check_interval = 0
44+
shutdown_timeout = 0
45+
log_level = "info"
46+
47+
[[runners]]
48+
name = "scaleway-mac-runner"
49+
url = "https://gitlab.com"
50+
token = "<GITLAB_RUNNER_TOKEN>"
51+
executor = "docker-autoscaler"
52+
shell = "bash"
53+
54+
[runners.autoscaler]
55+
capacity_per_instance = 1
56+
max_use_count = 10
57+
max_instances = 4
58+
plugin = "fleeting-plugin-scaleway"
59+
60+
[runners.autoscaler.plugin_config]
61+
name = "mac-runners"
62+
server_type = "M2-M"
63+
zone = "fr-par-3"
64+
# secret_key and project_id can also be set via SCW_SECRET_KEY /
65+
# SCW_DEFAULT_PROJECT_ID environment variables.
66+
secret_key = "<SCW_SECRET_KEY>"
67+
project_id = "<SCW_PROJECT_ID>"
68+
69+
[runners.autoscaler.connector_config]
70+
username = "m1"
71+
key_path = "/etc/gitlab-runner/id_ed25519"
72+
use_static_credentials = true
73+
keepalive = "30s"
74+
timeout = "10m"
75+
76+
[[runners.autoscaler.policy]]
77+
idle_count = 1
78+
idle_time = "30m"
79+
```
80+
81+
## Installation
82+
83+
### OCI registry (recommended)
84+
85+
The plugin is distributed as an OCI artifact on GHCR. GitLab Runner can install it automatically via `gitlab-runner fleeting install`.
86+
87+
Set the `plugin` field in `config.toml`:
88+
89+
```toml
90+
[runners.autoscaler]
91+
plugin = "ghcr.io/codecentric/fleeting-plugin-scaleway:latest"
92+
```
93+
94+
Then run:
95+
96+
```bash
97+
gitlab-runner fleeting install
98+
```
99+
100+
The runner will download and install the correct binary for its OS and architecture.
101+
Use a version constraint instead of `latest` to pin a specific release:
102+
103+
```toml
104+
plugin = "ghcr.io/codecentric/fleeting-plugin-scaleway:1" # latest 1.x.x
105+
plugin = "ghcr.io/codecentric/fleeting-plugin-scaleway:1.2" # latest 1.2.x
106+
```
107+
108+
### Manual binary installation
109+
110+
Download a binary from the [Releases](https://github.com/codecentric/fleeting-plugin-scaleway/releases) page, place it somewhere on `$PATH`, and name it `fleeting-plugin-scaleway`. Then reference it by that name:
111+
112+
```toml
113+
[runners.autoscaler]
114+
plugin = "fleeting-plugin-scaleway"
115+
```
116+
117+
## Building from source
118+
119+
```bash
120+
go build ./cmd/fleeting-plugin-scaleway
121+
```
122+
123+
Cross-compile all platforms (output goes to `dist/`):
124+
125+
```bash
126+
make all
127+
```
128+
129+
## Caveats
130+
131+
- The **24-hour minimum lease** means autoscaling costs are dominated by the lease duration, not actual usage. Configure `idle_time` and `max_use_count` accordingly.
132+
- Only **`fr-par-3`** is currently available for Apple Silicon.
133+
- Server names are prefixed with `fleeting-<name>-` so the plugin can identify its own servers across API calls.
134+
- SSH credentials must be pre-configured on the base OS image or injected through the Scaleway runner configuration. The plugin itself does not inject SSH keys.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package main
2+
3+
import (
4+
scaleway "github.com/codecentric/fleeting-plugin-scaleway"
5+
"gitlab.com/gitlab-org/fleeting/fleeting/plugin"
6+
)
7+
8+
func main() {
9+
plugin.Main(&scaleway.InstanceGroup{}, scaleway.Version)
10+
}

0 commit comments

Comments
 (0)