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: 2 additions & 2 deletions .asf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ github:
features:
issues: true
projects: false

ghp_branch: gh-pages
ghp_path: /
enabled_merge_buttons:
Expand All @@ -40,7 +40,7 @@ github:
required_approving_review_count: 1
collaborators:
- daniel-hutao

notifications:
commits: commits@devlake.apache.org
issues: commits@devlake.apache.org
Expand Down
26 changes: 26 additions & 0 deletions .github/.licenserc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
header:
license:
spdx-id: Apache-2.0
copyright-owner: Apache Software Foundation

paths-ignore:
- '**/*.md'
- '**/*.json'
- '**/*.txt'
- '**/LICENSE'
- '**/NOTICE'
- '.gitignore'
- '.gitmodules'
- 'charts/**/charts/**'
- 'charts/**/Chart.lock'
- '.github/.licenserc.yaml'
- '.github/dependabot.yml'
- '.github/labeler.yml'
- '.github/actions/**/action.yml'
- '.licenserc.yaml'
- '.mise.toml'
- '.pre-commit-config.yaml'
- '.yamllint'
- '**/*.gotmpl'

comment: on-failure
1 change: 0 additions & 1 deletion .github/actions/chart-releaser-action
Submodule chart-releaser-action deleted from be1625
35 changes: 35 additions & 0 deletions .github/actions/kind-cluster/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Create kind Cluster
description: Creates a kind Kubernetes cluster with diagnostics on failure

inputs:
cluster_name:
description: 'Name of the kind cluster to create'
required: true
namespace:
description: 'Kubernetes namespace to create (optional)'
required: false
default: ''

runs:
using: composite
steps:
- name: Create kind cluster
run: |
kind create cluster --name ${{ inputs.cluster_name }}
shell: bash

- name: Cluster information
run: |
kubectl cluster-info
kubectl get nodes
kubectl get pods -n kube-system
helm version
kubectl version
kubectl get storageclasses
shell: bash

- name: Create namespace
if: inputs.namespace != ''
run: |
kubectl create namespace ${{ inputs.namespace }} || true
shell: bash
39 changes: 39 additions & 0 deletions .github/actions/setup-environment/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Setup DevLake Environment
description: Sets up mise, Helm, and chart dependencies for DevLake workflows

inputs:
charts_dir:
description: 'Path to charts directory'
required: false
default: 'charts/devlake'

runs:
using: composite
steps:
- name: Install mise
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4

- name: Install tools via mise
run: mise install
shell: bash

- name: Cache Helm dependencies
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4
with:
path: |
~/.cache/helm
${{ inputs.charts_dir }}/charts/
key: helm-deps-${{ hashFiles(format('{0}/Chart.lock', inputs.charts_dir)) }}
restore-keys: |
helm-deps-

- name: Add Helm repositories
run: |
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
shell: bash

- name: Build chart dependencies
run: |
helm dependency build ${{ inputs.charts_dir }}
shell: bash
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns: ["*"]
15 changes: 15 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'area/charts':
- changed-files:
- any-glob-to-any-file: 'charts/**'

'area/workflows':
- changed-files:
- any-glob-to-any-file: '.github/workflows/**'

'area/actions':
- changed-files:
- any-glob-to-any-file: '.github/actions/**'

'area/docs':
- changed-files:
- any-glob-to-any-file: '**/*.md'
88 changes: 88 additions & 0 deletions .github/scripts/e2e-smoke-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# E2E smoke test - verify DevLake API and UI endpoints are accessible
set -euo pipefail

NAMESPACE="${NAMESPACE:-default}"
SERVICE_PORT="${SERVICE_PORT:-30000}"
TIMEOUT="${TIMEOUT:-300}"
MAX_RETRIES="${MAX_RETRIES:-10}"
RETRY_INTERVAL="${RETRY_INTERVAL:-3}"

echo "=== DevLake E2E Smoke Test ==="
echo "Namespace: $NAMESPACE"
echo "Service Port: $SERVICE_PORT"
echo "Timeout: ${TIMEOUT}s"
echo "Max Retries: $MAX_RETRIES"
echo ""

# Get node IP
echo "🔍 Getting node IP address..."
NODE_IP=$(kubectl get nodes --namespace "$NAMESPACE" -o jsonpath="{.items[0].status.addresses[0].address}")
echo "✅ Node IP: $NODE_IP"
echo ""

BASE_URL="http://${NODE_IP}:${SERVICE_PORT}"

# Test function with retry logic
test_endpoint() {
local name="$1"
local url="$2"
local check_pattern="${3:-}"

echo "📡 Testing: $name"
echo " URL: $url"

for attempt in $(seq 1 "$MAX_RETRIES"); do
if [[ $attempt -gt 1 ]]; then
echo " Retry $((attempt-1))/$((MAX_RETRIES-1)) after ${RETRY_INTERVAL}s..."
sleep "$RETRY_INTERVAL"
fi

if response=$(curl --silent --show-error --fail --max-time 10 "$url" 2>&1); then
# If check_pattern provided, verify response contains it
if [[ -n "$check_pattern" ]]; then
if echo "$response" | grep -q "$check_pattern"; then
echo "✅ $name: OK (pattern matched)"
return 0
else
echo " ⚠️ Response received but pattern not found"
continue
fi
else
echo "✅ $name: OK"
return 0
fi
else
echo " ⚠️ Attempt $attempt failed"
fi
done

echo "❌ $name: FAILED after $MAX_RETRIES attempts"
return 1
}

# Test 1: Home page
echo "Test 1: DevLake Home Page"
test_endpoint "Home Page" "$BASE_URL"
echo ""

# Test 2: DevLake API health
echo "Test 2: DevLake API - Blueprints Endpoint"
test_endpoint "Blueprints API" "$BASE_URL/api/blueprints"
echo ""

# Test 3: Grafana API health
echo "Test 3: Grafana API Health"
test_endpoint "Grafana Health" "$BASE_URL/grafana/api/health" "database"
echo ""

# Test 4: Additional DevLake API endpoints
echo "Test 4: DevLake Version Info"
test_endpoint "Version Info" "$BASE_URL/api/version"
echo ""

echo "Test 5: DevLake Plugins Endpoint"
test_endpoint "Plugins API" "$BASE_URL/api/plugins"
echo ""

echo "🎉 All E2E smoke tests passed!"
106 changes: 106 additions & 0 deletions .github/scripts/mysql-smoke-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
# MySQL smoke test - verify basic MySQL connectivity and operations
set -euo pipefail

RELEASE_NAME="${RELEASE_NAME:-devlake-mysql}"
NAMESPACE="${NAMESPACE:-devlake-mysql}"
TIMEOUT="${TIMEOUT:-300}"

echo "=== MySQL Smoke Test ==="
echo "Release: $RELEASE_NAME"
echo "Namespace: $NAMESPACE"
echo "Timeout: ${TIMEOUT}s"
echo ""

# Wait for MySQL pod to be ready
echo "⏳ Waiting for MySQL pod to be ready..."
kubectl wait --for=condition=ready pod \
-l "app.kubernetes.io/instance=$RELEASE_NAME,devlakeComponent=mysql" \
-n "$NAMESPACE" \
--timeout="${TIMEOUT}s"

POD_NAME=$(kubectl get pod -l "app.kubernetes.io/instance=$RELEASE_NAME,devlakeComponent=mysql" \
-n "$NAMESPACE" -o jsonpath='{.items[0].metadata.name}')

echo "✅ Pod ready: $POD_NAME"
echo ""

# Get MySQL root password from secret
echo "🔐 Retrieving MySQL credentials..."
ROOT_PASSWORD=$(kubectl get secret "${RELEASE_NAME}-db-auth" \
-n "$NAMESPACE" \
-o jsonpath='{.data.MYSQL_ROOT_PASSWORD}' | base64 -d)

# Test 1: Basic connectivity
echo "📡 Test 1: Basic MySQL connectivity..."
kubectl exec -n "$NAMESPACE" "$POD_NAME" -- \
mysqladmin ping -uroot -p"$ROOT_PASSWORD" 2>&1 | grep -q "mysqld is alive"
echo "✅ MySQL is alive"
echo ""

# Test 2: Query system info
echo "🔍 Test 2: Query MySQL version..."
VERSION=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -- \
mysql -uroot -p"$ROOT_PASSWORD" -e "SELECT VERSION();" -sN)
echo "✅ MySQL version: $VERSION"
echo ""

# Test 3: Check character set
echo "🔤 Test 3: Verify character set configuration..."
CHARSET=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -- \
mysql -uroot -p"$ROOT_PASSWORD" -e "SHOW VARIABLES LIKE 'character_set_server';" -sN | awk '{print $2}')
COLLATION=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -- \
mysql -uroot -p"$ROOT_PASSWORD" -e "SHOW VARIABLES LIKE 'collation_server';" -sN | awk '{print $2}')

if [[ "$CHARSET" != "utf8mb4" ]]; then
echo "❌ Character set is $CHARSET, expected utf8mb4"
exit 1
fi

if [[ "$COLLATION" != "utf8mb4_bin" ]]; then
echo "❌ Collation is $COLLATION, expected utf8mb4_bin"
exit 1
fi

echo "✅ Character set: $CHARSET, Collation: $COLLATION"
echo ""

# Test 4: Create test database
echo "🗄️ Test 4: Create and drop test database..."
kubectl exec -n "$NAMESPACE" "$POD_NAME" -- \
mysql -uroot -p"$ROOT_PASSWORD" -e "CREATE DATABASE IF NOT EXISTS smoke_test;"

kubectl exec -n "$NAMESPACE" "$POD_NAME" -- \
mysql -uroot -p"$ROOT_PASSWORD" -e "SHOW DATABASES;" | grep -q "smoke_test"

kubectl exec -n "$NAMESPACE" "$POD_NAME" -- \
mysql -uroot -p"$ROOT_PASSWORD" -e "DROP DATABASE smoke_test;"

echo "✅ Database operations successful"
echo ""

# Test 5: Check devlake database exists
echo "🔍 Test 5: Check devlake database..."
DB_USER=$(kubectl get secret "${RELEASE_NAME}-db-auth" \
-n "$NAMESPACE" \
-o jsonpath='{.data.MYSQL_USER}' | base64 -d)

kubectl exec -n "$NAMESPACE" "$POD_NAME" -- \
mysql -uroot -p"$ROOT_PASSWORD" -e "SHOW DATABASES;" | grep -q "lake"

echo "✅ Devlake database 'lake' exists"
echo ""

# Test 6: Verify user can connect
echo "👤 Test 6: Verify devlake user connectivity..."
DB_PASSWORD=$(kubectl get secret "${RELEASE_NAME}-db-auth" \
-n "$NAMESPACE" \
-o jsonpath='{.data.MYSQL_PASSWORD}' | base64 -d)

kubectl exec -n "$NAMESPACE" "$POD_NAME" -- \
mysql -u"$DB_USER" -p"$DB_PASSWORD" -D lake -e "SELECT 1;" -sN | grep -q "1"

echo "✅ User $DB_USER can connect to database"
echo ""

echo "🎉 All MySQL smoke tests passed!"
Loading