Skip to content

Commit eab11a0

Browse files
authored
Merge pull request #11 from gensyn/copilot/run-github-actions-locally
Add script to run CI workflows (tests and linting) locally
2 parents 1475fd2 + 9fc522c commit eab11a0

1 file changed

Lines changed: 182 additions & 0 deletions

File tree

run_workflows_locally.sh

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#!/usr/bin/env bash
2+
# run_workflows_locally.sh
3+
#
4+
# Runs the CI workflows (tests and linting) in this repository locally using
5+
# Docker and act (https://github.com/nektos/act).
6+
#
7+
# Both tools are installed automatically if they are not already present.
8+
#
9+
# Workflows that depend on GitHub infrastructure (hassfest, HACS validation,
10+
# release) are silently skipped as they cannot run meaningfully offline.
11+
#
12+
# Usage:
13+
# ./run_workflows_locally.sh
14+
15+
set -euo pipefail
16+
17+
# ── Colour helpers ────────────────────────────────────────────────────────────
18+
RED='\033[0;31m'
19+
GREEN='\033[0;32m'
20+
YELLOW='\033[1;33m'
21+
BLUE='\033[0;34m'
22+
BOLD='\033[1m'
23+
NC='\033[0m'
24+
25+
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
26+
success() { echo -e "${GREEN}[PASS]${NC} $*"; }
27+
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
28+
error() { echo -e "${RED}[FAIL]${NC} $*"; }
29+
header() { echo -e "\n${BOLD}$*${NC}"; }
30+
31+
command_exists() { command -v "$1" &>/dev/null; }
32+
33+
# ── Docker installation ───────────────────────────────────────────────────────
34+
install_docker() {
35+
if command_exists docker; then
36+
info "Docker is already installed: $(sudo docker --version)"
37+
return 0
38+
fi
39+
40+
header "Installing Docker…"
41+
curl -fsSL https://get.docker.com | sudo sh
42+
sudo usermod -aG docker "$USER" || true
43+
warn "Docker installed. You may need to run 'newgrp docker' or re-login for group membership to take effect."
44+
}
45+
46+
# ── act installation ──────────────────────────────────────────────────────────
47+
install_act() {
48+
if command_exists act; then
49+
info "act is already installed: $(act --version)"
50+
return 0
51+
fi
52+
53+
header "Installing act…"
54+
curl -fsSL https://raw.githubusercontent.com/nektos/act/master/install.sh \
55+
| sudo bash -s -- -b /usr/local/bin
56+
}
57+
58+
# ── Docker daemon check ───────────────────────────────────────────────────────
59+
ensure_docker_running() {
60+
if sudo docker info &>/dev/null; then
61+
return 0
62+
fi
63+
64+
warn "Docker daemon is not running – attempting to start it…"
65+
if command_exists systemctl; then
66+
sudo systemctl start docker
67+
else
68+
sudo service docker start
69+
fi
70+
sleep 3
71+
72+
if ! sudo docker info &>/dev/null; then
73+
error "Docker daemon is still not running. Please start Docker manually and re-run this script."
74+
exit 1
75+
fi
76+
}
77+
78+
# ── Workflow runner ───────────────────────────────────────────────────────────
79+
80+
# Ubuntu runner image used by act. The "act-latest" tag is a medium-sized
81+
# image that supports most common Actions without requiring the 20 GB+ full
82+
# image.
83+
ACT_IMAGE="catthehacker/ubuntu:act-latest"
84+
85+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
86+
WORKFLOWS_DIR="$SCRIPT_DIR/.github/workflows"
87+
88+
# run_workflow <workflow-file> <event>
89+
# Returns 0 on success, 1 on failure.
90+
run_workflow() {
91+
local workflow_file="$1"
92+
local event="$2"
93+
local name
94+
name="$(basename "$workflow_file")"
95+
96+
info "Running [$name] with event '$event'…"
97+
98+
if sudo act "$event" \
99+
-W "$workflow_file" \
100+
-P "ubuntu-latest=$ACT_IMAGE" \
101+
--rm \
102+
2>&1; then
103+
success "$name passed"
104+
return 0
105+
else
106+
error "$name failed"
107+
return 1
108+
fi
109+
}
110+
111+
run_all_workflows() {
112+
# Only workflows that run entirely locally (tests and linting).
113+
# Workflows that depend on GitHub infrastructure (hassfest, HACS validation,
114+
# release) are silently omitted.
115+
local workflow_files=(
116+
"test.yml"
117+
"pylint.yml"
118+
"integration-tests.yml"
119+
)
120+
local workflow_events=(
121+
"push"
122+
"push"
123+
"push"
124+
)
125+
126+
local passed=()
127+
local failed=()
128+
129+
local i
130+
for i in "${!workflow_files[@]}"; do
131+
local workflow="${workflow_files[$i]}"
132+
local event="${workflow_events[$i]}"
133+
local workflow_path="$WORKFLOWS_DIR/$workflow"
134+
135+
if [[ ! -f "$workflow_path" ]]; then
136+
warn "Workflow file not found, skipping: $workflow"
137+
continue
138+
fi
139+
140+
if run_workflow "$workflow_path" "$event"; then
141+
passed+=("$workflow")
142+
else
143+
failed+=("$workflow")
144+
fi
145+
done
146+
147+
# ── Summary ───────────────────────────────────────────────────────────────
148+
header "══════════════════════════════════════════════"
149+
header " Results"
150+
header "══════════════════════════════════════════════"
151+
152+
if [[ ${#passed[@]} -gt 0 ]]; then
153+
success "Passed (${#passed[@]}): ${passed[*]}"
154+
fi
155+
if [[ ${#failed[@]} -gt 0 ]]; then
156+
error "Failed (${#failed[@]}): ${failed[*]}"
157+
return 1
158+
fi
159+
160+
echo ""
161+
success "All workflows completed successfully."
162+
}
163+
164+
# ── Main ──────────────────────────────────────────────────────────────────────
165+
main() {
166+
if [[ $# -gt 0 ]]; then
167+
error "This script takes no arguments."
168+
echo "Usage: $0"
169+
exit 1
170+
fi
171+
172+
header "════════════════════════════════════════════════════"
173+
header " Running GitHub Actions workflows locally with act"
174+
header "════════════════════════════════════════════════════"
175+
176+
install_docker
177+
install_act
178+
ensure_docker_running
179+
run_all_workflows
180+
}
181+
182+
main "$@"

0 commit comments

Comments
 (0)