Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
115
tools/citrineos-core-main/.github/workflows/license-check.yml
vendored
Normal file
115
tools/citrineos-core-main/.github/workflows/license-check.yml
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
name: REUSE License Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
reuse-check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install REUSE tool
|
||||
run: pip install reuse
|
||||
|
||||
- name: Run REUSE lint with filtering
|
||||
run: |
|
||||
# Run reuse lint and capture output
|
||||
if ! reuse lint > reuse_output.txt 2>&1; then
|
||||
echo "REUSE lint found issues. Checking against ignore list..."
|
||||
|
||||
# Check if .reuse/ignore-files.txt exists
|
||||
if [ -f ".reuse/ignore-files.txt" ]; then
|
||||
echo "Found .reuse/ignore-files.txt file. Filtering results..."
|
||||
# Extract failing files from the REUSE output
|
||||
sed '/SUMMARY/q' reuse_output.txt | grep "^\* " | sed 's/^\* //' > failing_files.txt
|
||||
|
||||
# Function to check if a file should be ignored
|
||||
should_ignore_file() {
|
||||
local file="$1"
|
||||
local ignore_file="$2"
|
||||
|
||||
# Skip the reuse output file itself
|
||||
if [[ "$file" == "reuse_output.txt" ]]; then
|
||||
return 0 # ignore (return true)
|
||||
fi
|
||||
|
||||
while IFS= read -r ignore_pattern; do
|
||||
# Skip empty lines and comments
|
||||
[[ -z "$ignore_pattern" || "$ignore_pattern" =~ ^[[:space:]]*# ]] && continue
|
||||
|
||||
# Remove trailing whitespace
|
||||
ignore_pattern=$(echo "$ignore_pattern" | sed 's/[[:space:]]*$//')
|
||||
|
||||
# Check for exact match first
|
||||
if [[ "$file" == "$ignore_pattern" ]]; then
|
||||
return 0 # ignore
|
||||
fi
|
||||
|
||||
# Check if it's a directory pattern (ends with /)
|
||||
if [[ "$ignore_pattern" == */ ]]; then
|
||||
# Check if file is in this directory
|
||||
if [[ "$file" == "$ignore_pattern"* ]]; then
|
||||
return 0 # ignore
|
||||
fi
|
||||
else
|
||||
# Use shell pattern matching for globs
|
||||
# Enable extended globbing for more complex patterns
|
||||
shopt -s extglob nullglob
|
||||
if [[ "$file" == $ignore_pattern ]]; then
|
||||
return 0 # ignore
|
||||
fi
|
||||
shopt -u extglob nullglob
|
||||
fi
|
||||
done < "$ignore_file"
|
||||
|
||||
return 1 # don't ignore
|
||||
}
|
||||
|
||||
# Find files that are failing but NOT in the ignore list
|
||||
> unignored_failures.txt
|
||||
while IFS= read -r failing_file; do
|
||||
if ! should_ignore_file "$failing_file" ".reuse/ignore-files.txt"; then
|
||||
echo "$failing_file" >> unignored_failures.txt
|
||||
fi
|
||||
done < failing_files.txt
|
||||
|
||||
# Check if there are any unignored failures
|
||||
if [ -s unignored_failures.txt ]; then
|
||||
echo "❌ REUSE compliance failed for the following files:"
|
||||
cat unignored_failures.txt
|
||||
echo ""
|
||||
echo "These files need proper license headers or should be added to .reuse/ignore-files.txt"
|
||||
echo ""
|
||||
exit 1
|
||||
else
|
||||
echo "✅ All REUSE lint failures are accounted for in .reuse/ignore-files.txt"
|
||||
ignored_count=$(wc -l < failing_files.txt)
|
||||
echo "Ignored $ignored_count file(s) as specified in .reuse/ignore-files.txt"
|
||||
fi
|
||||
else
|
||||
echo "❌ REUSE lint failed and no .reuse/ignore-files.txt file found"
|
||||
echo ""
|
||||
echo "Full REUSE lint output:"
|
||||
cat reuse_output.txt
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "✅ REUSE lint passed - all files have proper license compliance"
|
||||
fi
|
||||
|
||||
- name: Clean up temporary files
|
||||
if: always()
|
||||
run: |
|
||||
rm -f reuse_output.txt failing_files.txt unignored_failures.txt
|
||||
37
tools/citrineos-core-main/.github/workflows/lint.yml
vendored
Normal file
37
tools/citrineos-core-main/.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
name: Linter
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
# Rerun check when the PR is updated, https://stackoverflow.com/a/72408109
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
linter:
|
||||
name: Linter
|
||||
runs-on: ubuntu-22.04 # todo: to prevent needing to run npm i we can have our own image where everything is installed
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.19.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24.16.0'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
80
tools/citrineos-core-main/.github/workflows/publish-swagger.yml
vendored
Normal file
80
tools/citrineos-core-main/.github/workflows/publish-swagger.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
name: Publish Swagger
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
swagger_valid: ${{ steps.validate-swagger.outputs.swagger_valid }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Build and Run Docker Compose
|
||||
run: |
|
||||
docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml up -d
|
||||
|
||||
- name: Generate Swagger JSON
|
||||
run: |
|
||||
echo "Fetching Swagger JSON..."
|
||||
curl --retry 3 --retry-delay 3 --retry-all-errors -o swagger.json http://localhost:8080/docs/json
|
||||
|
||||
- name: Validate Swagger JSON
|
||||
id: validate-swagger
|
||||
run: |
|
||||
if jq empty swagger.json; then
|
||||
echo "swagger_valid=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "swagger_valid=false" >> $GITHUB_OUTPUT && exit 1
|
||||
fi
|
||||
|
||||
- name: Upload Swagger JSON as Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: swagger-json
|
||||
path: swagger.json
|
||||
|
||||
- name: Clean up Docker
|
||||
run: docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml down
|
||||
|
||||
publish-swagger:
|
||||
needs: build
|
||||
if: needs.build.outputs.swagger_valid == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Download Swagger Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: swagger-json
|
||||
|
||||
- name: Checkout citrineos.github.io
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: citrineos/citrineos.github.io
|
||||
ssh-key: ${{ secrets.CITRINEOS_GITHUB_IO_DEPLOY_KEY }}
|
||||
path: citrineos.github.io
|
||||
|
||||
- name: Push Swagger JSON
|
||||
run: |
|
||||
cd citrineos.github.io
|
||||
git checkout -b swagger-updates || git checkout swagger-updates
|
||||
cp ../swagger.json ./docs/assets
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "actions@github.com"
|
||||
git add ./docs/assets/swagger.json
|
||||
git commit -m "Update Swagger JSON from PR"
|
||||
git push -f origin swagger-updates --verbose
|
||||
75
tools/citrineos-core-main/.github/workflows/push-release-tagged-operator-ui.yml
vendored
Normal file
75
tools/citrineos-core-main/.github/workflows/push-release-tagged-operator-ui.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
name: Push Release Tagged Operator UI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
image:
|
||||
- name: citrineos-operator-ui
|
||||
context: ./
|
||||
dockerfile: ./apps/operator-ui/Dockerfile
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24.16.0'
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ matrix.image.name }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-${{ matrix.image.name }}-
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository_owner }}/${{ matrix.image.name }}
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Build and push operator-ui image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ${{ matrix.image.context }}
|
||||
file: ${{ matrix.image.dockerfile }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
||||
|
||||
# Move cache to avoid ever-growing cache
|
||||
- name: Move cache
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||
77
tools/citrineos-core-main/.github/workflows/push-release-tagged-server.yml
vendored
Normal file
77
tools/citrineos-core-main/.github/workflows/push-release-tagged-server.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
name: Push Release Tagged Server
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
image:
|
||||
- name: citrineos-server
|
||||
context: ./
|
||||
dockerfile: ./apps/Server/deploy.Dockerfile
|
||||
config: ''
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24.16.0'
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ matrix.image.name }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-${{ matrix.image.name }}-
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository_owner }}/${{ matrix.image.name }}
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Build and push server image
|
||||
if: ${{ matrix.image.name == 'citrineos-server' }}
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ${{ matrix.image.context }}
|
||||
file: ${{ matrix.image.dockerfile }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
||||
|
||||
# Move cache to avoid ever-growing cache
|
||||
- name: Move cache
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||
111
tools/citrineos-core-main/.github/workflows/renew-certs.yml
vendored
Normal file
111
tools/citrineos-core-main/.github/workflows/renew-certs.yml
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
name: Renew Certificates
|
||||
# This workflow is to renew the certificates which are used for security profile 2&3
|
||||
# It can be triggered manually or automatically every month to check if the certificates are still valid
|
||||
# and renew them if needed on main branch
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 1 * *' # Runs on the 1st of every month
|
||||
|
||||
env:
|
||||
TARGET_BRANCH: main
|
||||
|
||||
jobs:
|
||||
renew-certs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.TARGET_BRANCH }}
|
||||
|
||||
- name: Set up OpenSSL + Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24.16.0'
|
||||
|
||||
- name: Check certificate validity
|
||||
id: check_cert
|
||||
run: |
|
||||
CERT_FILE="apps/Server/src/assets/certificates/certChain.pem"
|
||||
# extract the expiry date of the leaf certificate (first cert in the chain)
|
||||
EXPIRY_DATE=$(awk 'BEGIN{found=0} /BEGIN CERTIFICATE/{if (++found==1) print > "/tmp/leaf.pem"; next} found==1 {print > "/tmp/leaf.pem"} /END CERTIFICATE/{if (found==1) exit}' "$CERT_FILE")
|
||||
END_DATE=$(openssl x509 -enddate -noout -in /tmp/leaf.pem | cut -d= -f2)
|
||||
END_TIMESTAMP=$(date -d "$END_DATE" +%s)
|
||||
NOW_TIMESTAMP=$(date +%s)
|
||||
DIFF_DAYS=$(( (END_TIMESTAMP - NOW_TIMESTAMP) / 86400 ))
|
||||
echo "Leaf certificate expires in $DIFF_DAYS days."
|
||||
if [ "$DIFF_DAYS" -lt 31 ]; then
|
||||
echo "cert_needs_renewal=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "cert_needs_renewal=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Skip if certificate is valid
|
||||
if: steps.check_cert.outputs.cert_needs_renewal == 'false'
|
||||
run: echo "Certificate is still valid. Skipping renewal."
|
||||
|
||||
- name: Generate new certificates
|
||||
if: steps.check_cert.outputs.cert_needs_renewal == 'true'
|
||||
run: |
|
||||
CERT_DIR="apps/Server/src/assets/certificates"
|
||||
CONFIG_FILE=".github/workflows/renew-certs/openssl.config"
|
||||
|
||||
mkdir -p "$CERT_DIR"
|
||||
|
||||
# Create root key and self-signed root cert
|
||||
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out "$CERT_DIR/rootKey.pem"
|
||||
openssl req -x509 -new -nodes -key "$CERT_DIR/rootKey.pem" -sha256 -days 365 \
|
||||
-subj "/CN=host.docker.internal root/O=S44/C=US" \
|
||||
-out "$CERT_DIR/rootCertificate.pem" \
|
||||
-config "$CONFIG_FILE" -extensions v3_ca
|
||||
|
||||
# Create sub-CA key and CSR
|
||||
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out "$CERT_DIR/subCAKey.pem"
|
||||
openssl req -new -key "$CERT_DIR/subCAKey.pem" \
|
||||
-subj "/CN=host.docker.internal subCA/O=S44/C=US" \
|
||||
-out "$CERT_DIR/subCA.csr" \
|
||||
-config "$CONFIG_FILE"
|
||||
|
||||
# Sign sub-CA cert with root
|
||||
openssl x509 -req -in "$CERT_DIR/subCA.csr" -CA "$CERT_DIR/rootCertificate.pem" -CAkey "$CERT_DIR/rootKey.pem" -CAcreateserial \
|
||||
-out "$CERT_DIR/subCA.pem" -days 365 -sha256 \
|
||||
-extfile "$CONFIG_FILE" -extensions v3_subca
|
||||
|
||||
# Create leaf key and CSR
|
||||
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out "$CERT_DIR/leafKey.pem"
|
||||
openssl req -new -key "$CERT_DIR/leafKey.pem" \
|
||||
-subj "/CN=host.docker.internal/O=S44/C=US" \
|
||||
-out "$CERT_DIR/leaf.csr" \
|
||||
-config "$CONFIG_FILE"
|
||||
|
||||
# Sign leaf cert with sub-CA
|
||||
openssl x509 -req -in "$CERT_DIR/leaf.csr" -CA "$CERT_DIR/subCA.pem" -CAkey "$CERT_DIR/subCAKey.pem" -CAcreateserial \
|
||||
-out "$CERT_DIR/leaf.pem" -days 365 -sha256 \
|
||||
-extfile "$CONFIG_FILE" -extensions v3_leaf
|
||||
|
||||
# Combine into certChain.pem
|
||||
cat "$CERT_DIR/leaf.pem" "$CERT_DIR/subCA.pem" > "$CERT_DIR/certChain.pem"
|
||||
|
||||
# Clean up intermediate files
|
||||
rm -f "$CERT_DIR/"*.csr "$CERT_DIR/"*.srl "$CERT_DIR/leaf.pem" "$CERT_DIR/subCA.pem"
|
||||
|
||||
- name: Commit and push renewed certificates
|
||||
if: steps.check_cert.outputs.cert_needs_renewal == 'true'
|
||||
run: |
|
||||
git config user.name "github-actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
git add apps/Server/src/assets/certificates
|
||||
git commit -m "chore(cert): Renewed certificates [auto]"
|
||||
git push origin $TARGET_BRANCH
|
||||
17
tools/citrineos-core-main/.github/workflows/renew-certs/openssl.config
vendored
Normal file
17
tools/citrineos-core-main/.github/workflows/renew-certs/openssl.config
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
[ v3_ca ]
|
||||
basicConstraints = critical,CA:true,pathlen:1
|
||||
keyUsage = critical,digitalSignature, keyCertSign, cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always,issuer:always
|
||||
|
||||
[ v3_subca ]
|
||||
basicConstraints = critical,CA:true,pathlen:0
|
||||
keyUsage = critical,digitalSignature, keyCertSign, cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid,issuer
|
||||
|
||||
[ v3_leaf ]
|
||||
basicConstraints = CA:false
|
||||
keyUsage = critical,digitalSignature, keyEncipherment
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid,issuer
|
||||
86
tools/citrineos-core-main/.github/workflows/test-build-server.yml
vendored
Normal file
86
tools/citrineos-core-main/.github/workflows/test-build-server.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
name: Test Server on Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
server: ${{ steps.filter.outputs.server }}
|
||||
shared: ${{ steps.filter.outputs.shared }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
shared:
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'package.json'
|
||||
- 'tsconfig*.json'
|
||||
- '.github/workflows/**'
|
||||
server:
|
||||
- 'apps/Server/**'
|
||||
- 'packages/**'
|
||||
|
||||
build:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.server == 'true' || needs.changes.outputs.shared == 'true' }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24.16.0'
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Build and Run Docker Compose
|
||||
run: docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml up -d
|
||||
|
||||
- name: Install Newman
|
||||
run: |
|
||||
npm install -g newman
|
||||
sleep 10
|
||||
|
||||
- name: Integration test
|
||||
run: |
|
||||
RETRY_COUNT=0
|
||||
MAX_RETRIES=5
|
||||
RETRY_DELAY=10
|
||||
until [ $RETRY_COUNT -ge $MAX_RETRIES ]
|
||||
do
|
||||
newman run ${{ github.workspace }}/.github/workflows/tests/CI/collection.json -e ${{ github.workspace }}/.github/workflows/tests/CI/environment.json --reporters cli && break
|
||||
RETRY_COUNT=$((RETRY_COUNT+1))
|
||||
echo "Retry $RETRY_COUNT/$MAX_RETRIES in $RETRY_DELAY seconds..."
|
||||
sleep $RETRY_DELAY
|
||||
done
|
||||
shell: bash
|
||||
|
||||
- name: Get logs for all containers
|
||||
if: failure()
|
||||
run: |
|
||||
docker ps -a
|
||||
docker container ls -aq | xargs -I {} docker logs {}
|
||||
|
||||
- name: Clean up
|
||||
run: docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml down
|
||||
44
tools/citrineos-core-main/.github/workflows/tests/CI/collection.json
vendored
Normal file
44
tools/citrineos-core-main/.github/workflows/tests/CI/collection.json
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"info": {
|
||||
"name": "API Collection",
|
||||
"_postman_id": "a1234567-b89c-4d12-95a3-b4567e89c0d1",
|
||||
"description": "A collection contains CI tests.",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Check Health",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test('Status code is 200', function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "none"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{host}}:{{port}}/health",
|
||||
"protocol": "http",
|
||||
"host": ["{{host}}"],
|
||||
"port": "{{port}}",
|
||||
"path": ["health"]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
"type": "noauth"
|
||||
}
|
||||
}
|
||||
16
tools/citrineos-core-main/.github/workflows/tests/CI/environment.json
vendored
Normal file
16
tools/citrineos-core-main/.github/workflows/tests/CI/environment.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "4454509f-00c2-4b1b-837a-e45d9814dbf6",
|
||||
"name": "Local Environment",
|
||||
"values": [
|
||||
{
|
||||
"key": "host",
|
||||
"value": "localhost",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"key": "port",
|
||||
"value": "8080",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
181
tools/citrineos-core-main/.github/workflows/unit-tests.yml
vendored
Normal file
181
tools/citrineos-core-main/.github/workflows/unit-tests.yml
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
base: ${{ steps.filter.outputs.base }}
|
||||
core: ${{ steps.filter.outputs.core }}
|
||||
server: ${{ steps.filter.outputs.server }}
|
||||
operator-ui: ${{ steps.filter.outputs.operator-ui }}
|
||||
shared: ${{ steps.filter.outputs.shared }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
shared:
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'package.json'
|
||||
- 'tsconfig*.json'
|
||||
- 'vitest.config.ts'
|
||||
- 'eslint.config.base.js'
|
||||
- '.github/workflows/**'
|
||||
base:
|
||||
- 'packages/base/**'
|
||||
core:
|
||||
- 'packages/core/**'
|
||||
- 'packages/base/**'
|
||||
server:
|
||||
- 'apps/Server/**'
|
||||
- 'packages/**'
|
||||
operator-ui:
|
||||
- 'apps/operator-ui/**'
|
||||
- 'packages/base/**'
|
||||
|
||||
tests:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.base == 'true' || needs.changes.outputs.core == 'true' || needs.changes.outputs.server == 'true' || needs.changes.outputs.shared == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.19.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24.16.0"
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
run: pnpm test
|
||||
|
||||
operator-ui-e2e:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.operator-ui == 'true' || needs.changes.outputs.shared == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.19.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24.16.0"
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build @citrineos/operator-ui and its workspace deps
|
||||
run: pnpm --filter "@citrineos/operator-ui..." run build
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
id: playwright-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('apps/operator-ui/package.json') }}
|
||||
|
||||
- name: Install Playwright browsers
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
working-directory: apps/operator-ui
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- name: Install Playwright system deps
|
||||
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
||||
working-directory: apps/operator-ui
|
||||
run: pnpm exec playwright install-deps chromium
|
||||
|
||||
- name: Start backend stack (Hasura + CitrineOS Core)
|
||||
# Build with the default docker driver (builds straight into the daemon's
|
||||
# image store) — no tarball export/import or cache round-trip, which is
|
||||
# what made the build-push-action approach ~3min slower than the build itself.
|
||||
run: docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml up -d --build
|
||||
|
||||
- name: Wait for backend healthy
|
||||
run: |
|
||||
set -e
|
||||
for i in $(seq 1 60); do
|
||||
HASURA_OK=$(curl -fs http://localhost:8090/healthz > /dev/null && echo yes || echo no)
|
||||
CORE_OK=$(curl -fs http://localhost:8080/health > /dev/null && echo yes || echo no)
|
||||
if [ "$HASURA_OK" = "yes" ] && [ "$CORE_OK" = "yes" ]; then
|
||||
echo "Backend healthy after ${i} attempts."
|
||||
exit 0
|
||||
fi
|
||||
echo "Waiting for backend (attempt ${i}/60): hasura=${HASURA_OK} core=${CORE_OK}"
|
||||
sleep 5
|
||||
done
|
||||
echo "Backend did not become healthy in time."
|
||||
exit 1
|
||||
|
||||
- name: Run operator-ui Playwright tests
|
||||
env:
|
||||
CI: "true"
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
NEXT_PUBLIC_ADMIN_EMAIL: admin@citrineos.com
|
||||
ADMIN_PASSWORD: CitrineOS!
|
||||
NEXTAUTH_URL: http://localhost:3000
|
||||
NEXTAUTH_SECRET: test-secret-for-e2e-only
|
||||
HASURA_ADMIN_SECRET: CitrineOS!
|
||||
NEXT_PUBLIC_API_URL: http://localhost:8090/v1/graphql
|
||||
NEXT_PUBLIC_WS_URL: ws://localhost:8090/v1/graphql
|
||||
NEXT_PUBLIC_CITRINE_CORE_URL: http://localhost:8080
|
||||
NEXT_PUBLIC_FILE_SERVER_URL: http://localhost:8050
|
||||
ALLOW_IMAGE_UPLOAD: "false"
|
||||
working-directory: apps/operator-ui
|
||||
run: pnpm exec playwright test --project=chromium-desktop
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: operator-ui-playwright-report
|
||||
path: |
|
||||
apps/operator-ui/playwright-report
|
||||
apps/operator-ui/test-results
|
||||
apps/operator-ui/reports
|
||||
retention-days: 7
|
||||
|
||||
- name: Dump backend logs on failure
|
||||
if: failure()
|
||||
run: |
|
||||
docker ps -a
|
||||
docker container ls -aq | xargs -I {} docker logs {} || true
|
||||
|
||||
- name: Tear down backend stack
|
||||
if: always()
|
||||
run: docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml down -v
|
||||
Reference in New Issue
Block a user