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
4 changes: 3 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
* @wippyai/framework

# Specific code owners for certain files or directories.
.github/* @wippyai/github
.github/* @wippyai/github
.github/actions/ @wippyai/github
.github/workflows/ @wippyai/github
45 changes: 45 additions & 0 deletions .github/actions/setup-wippy/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Setup verified latest Wippy runtime
description: Installs the latest stable Wippy runtime after signature verification.

outputs:
path:
description: Absolute path to the verified Wippy executable.
value: ${{ steps.install.outputs.path }}
version:
description: Installed Wippy release tag.
value: ${{ steps.install.outputs.version }}

runs:
using: composite
steps:
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: 1.26.5
check-latest: false
cache: false
- name: Install and verify Wippy runtime
id: install
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
wippy_version="$(gh api repos/wippyai/runtime/releases/latest --jq .tag_name)"
test -n "$wippy_version"

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
gh release download "$wippy_version" --repo wippyai/runtime --dir "$tmp" \
--pattern wippy-linux-amd64 --pattern wippy-linux-amd64.sig
go run "$GITHUB_ACTION_PATH/verify-release.go" \
"$GITHUB_ACTION_PATH/wippy-runtime-release.pub" \
"$tmp/wippy-linux-amd64" \
"$tmp/wippy-linux-amd64.sig"

wippy_path="$HOME/.wippy/bin/wippy"
install -Dm0755 "$tmp/wippy-linux-amd64" "$wippy_path"
"$wippy_path" version
{
echo "path=$wippy_path"
echo "version=$wippy_version"
} >> "$GITHUB_OUTPUT"
3 changes: 3 additions & 0 deletions .github/actions/setup-wippy/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/wippyai/dataflow/setup-wippy

go 1.26.0
69 changes: 69 additions & 0 deletions .github/actions/setup-wippy/verify-release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: Apache-2.0

package main

import (
"crypto/ed25519"
"encoding/base64"
"fmt"
"os"
"strings"
)

func readBase64(path string) ([]byte, error) {
encoded, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read %q: %w", path, err)
}

decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(encoded)))
if err != nil {
return nil, fmt.Errorf("decode base64 in %q: %w", path, err)
}

return decoded, nil
}

func verify(publicKey, artifact, signature []byte) error {
if len(publicKey) != ed25519.PublicKeySize {
return fmt.Errorf("public key is %d bytes, want %d", len(publicKey), ed25519.PublicKeySize)
}
if len(signature) != ed25519.SignatureSize {
return fmt.Errorf("signature is %d bytes, want %d", len(signature), ed25519.SignatureSize)
}
if !ed25519.Verify(ed25519.PublicKey(publicKey), artifact, signature) {
return fmt.Errorf("runtime signature verification failed")
}

return nil
}

func main() {
if len(os.Args) != 4 {
fmt.Fprintln(os.Stderr, "usage: verify-release <public-key> <artifact> <signature>")
os.Exit(2)
}

publicKey, err := readBase64(os.Args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "read public key: %v\n", err)
os.Exit(1)
}

artifact, err := os.ReadFile(os.Args[2])
if err != nil {
fmt.Fprintf(os.Stderr, "read artifact: %v\n", err)
os.Exit(1)
}

signature, err := readBase64(os.Args[3])
if err != nil {
fmt.Fprintf(os.Stderr, "read signature: %v\n", err)
os.Exit(1)
}

if err := verify(publicKey, artifact, signature); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
53 changes: 53 additions & 0 deletions .github/actions/setup-wippy/verify-release_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0

package main

import (
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"os"
"path/filepath"
"testing"
)

func TestVerify(t *testing.T) {
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}

artifact := []byte("verified runtime artifact")
signature := ed25519.Sign(privateKey, artifact)
if err := verify(publicKey, artifact, signature); err != nil {
t.Fatalf("verify valid signature: %v", err)
}

signature[0] ^= 1
if err := verify(publicKey, artifact, signature); err == nil {
t.Fatal("verify accepted a tampered signature")
}
}

func TestReadBase64(t *testing.T) {
path := filepath.Join(t.TempDir(), "key")
want := []byte("test value")
if err := os.WriteFile(path, []byte(base64.StdEncoding.EncodeToString(want)), 0o600); err != nil {
t.Fatal(err)
}

got, err := readBase64(path)
if err != nil {
t.Fatalf("read base64: %v", err)
}
if string(got) != string(want) {
t.Fatalf("read base64 = %q, want %q", got, want)
}

if err := os.WriteFile(path, []byte("not base64!"), 0o600); err != nil {
t.Fatal(err)
}
if _, err := readBase64(path); err == nil {
t.Fatal("readBase64 accepted invalid input")
}
}
1 change: 1 addition & 0 deletions .github/actions/setup-wippy/wippy-runtime-release.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xcLLvYAfv/BCYborznp5nrm6qdIU/o9c2xJi0tr0fhw=
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
github-actions:
patterns: ['*']
75 changes: 57 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,45 @@ on:
permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lint:
name: Wippy lint
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- name: Install Wippy
run: |
curl -fsSL https://hub.wippy.ai/install.sh -o /tmp/wippy-install.sh
. /tmp/wippy-install.sh
echo "$HOME/.wippy/bin" >> "$GITHUB_PATH"
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- id: wippy
uses: ./.github/actions/setup-wippy
- name: Install module dependencies
run: make install
env:
WIPPY: ${{ steps.wippy.outputs.path }}
run: |
make install
make verify-lock
- name: Lint
env:
WIPPY: ${{ steps.wippy.outputs.path }}
run: make lint

test:
name: Tests (${{ matrix.database }})
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 20
env:
MAKE_TARGET: test-${{ matrix.database }}
strategy:
fail-fast: false
matrix:
database: [sqlite, postgres]
services:
postgres:
image: postgres:17-alpine
image: postgres@sha256:af194ccf3e2d7fe367012c7b88ce8b816c5c889b18a5b316799a1f0d7eac746a # 17-alpine, linux/amd64
env:
POSTGRES_DB: dataflow_test
POSTGRES_USER: dataflow
Expand All @@ -46,15 +59,41 @@ jobs:
--health-timeout 5s
--health-retries 12
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install static-check dependency
run: sudo apt-get update && sudo apt-get install -y ripgrep
- name: Install Wippy
run: |
curl -fsSL https://hub.wippy.ai/install.sh -o /tmp/wippy-install.sh
. /tmp/wippy-install.sh
echo "$HOME/.wippy/bin" >> "$GITHUB_PATH"
- id: wippy
uses: ./.github/actions/setup-wippy
- name: Install module dependencies
run: make install
env:
WIPPY: ${{ steps.wippy.outputs.path }}
run: |
make install
make verify-lock
- name: Run ${{ matrix.database }} tests
run: make test-${{ matrix.database }}
env:
WIPPY: ${{ steps.wippy.outputs.path }}
run: make "$MAKE_TARGET"

package:
name: Package dry run
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- id: wippy
uses: ./.github/actions/setup-wippy
- name: Install module dependencies
env:
WIPPY: ${{ steps.wippy.outputs.path }}
run: |
make install
make verify-lock
- name: Validate package
env:
WIPPY: ${{ steps.wippy.outputs.path }}
run: make verify-package
56 changes: 48 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,64 @@
name: Release
name: Release Please

on:
push:
branches: [master]

permissions:
contents: write
pull-requests: write
permissions: {}

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
checks:
name: Checks
permissions:
contents: read
uses: ./.github/workflows/ci.yml

release:
name: Release Please
needs: checks
uses: wippyai/template-module/.github/workflows/reusable-release.yml@v1
with:
publish: true
secrets: inherit
runs-on: ubuntu-24.04
permissions:
contents: write # Required for Release Please to create tags and GitHub releases.
pull-requests: write # Required for Release Please to create and update release pull requests.
outputs:
created: ${{ steps.release.outputs.release_created }}
version: ${{ steps.release.outputs.version }}
steps:
- name: Run Release Please
id: release
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
with:
token: ${{ github.token }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

deploy-dry-run:
name: Deploy dry run
needs: release
if: ${{ needs.release.outputs.created == 'true' }}
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: v${{ needs.release.outputs.version }}
persist-credentials: false

- id: wippy
uses: ./.github/actions/setup-wippy

- name: Deploy dry run
env:
RELEASE_VERSION: ${{ needs.release.outputs.version }}
WIPPY: ${{ steps.wippy.outputs.path }}
WIPPY_TOKEN: ${{ secrets.WIPPY_TOKEN }}
run: |
set -euo pipefail
manifest_version="$(awk '/^version:/ { print $2; exit }' wippy.yaml)"
test "$manifest_version" = "$RELEASE_VERSION"
"$WIPPY" publish --version "$RELEASE_VERSION" --dry-run
Loading