-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdevctl
More file actions
executable file
·525 lines (441 loc) · 16.2 KB
/
devctl
File metadata and controls
executable file
·525 lines (441 loc) · 16.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
#!/usr/bin/env bash
# ==============================================================================
# Monitoring Hub - Docker-First Development CLI
# ==============================================================================
# This script provides a unified interface for all development tasks using Docker.
# No local Python installation required - everything runs in containers.
#
# Usage: ./devctl <command> [args...]
# ==============================================================================
set -euo pipefail
# --- Configuration ---
DEV_IMAGE="monitoring-hub-dev:latest"
WORKSPACE="/workspace"
DOCKER_RUN="docker run --rm -v $(pwd):${WORKSPACE} -w ${WORKSPACE}"
DOCKER_RUN_IT="docker run -it --rm -v $(pwd):${WORKSPACE} -w ${WORKSPACE}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# --- Helper Functions ---
info() {
echo -e "${BLUE}ℹ ${NC}$1"
}
success() {
echo -e "${GREEN}✓${NC} $1"
}
error() {
echo -e "${RED}✗${NC} $1" >&2
}
warning() {
echo -e "${YELLOW}⚠${NC} $1"
}
check_docker() {
if ! command -v docker &> /dev/null; then
error "Docker is not installed or not in PATH"
exit 1
fi
}
ensure_image() {
if ! docker image inspect "$DEV_IMAGE" &> /dev/null; then
warning "Development image not found. Building..."
docker build -t "$DEV_IMAGE" -f config/docker/Dockerfile.dev .
success "Development image built successfully"
fi
}
show_help() {
cat << 'EOF'
Monitoring Hub - Docker-First Development CLI
USAGE:
./devctl <command> [args...]
SETUP COMMANDS:
build Build the development Docker image
rebuild Rebuild the development image from scratch
shell Open an interactive bash shell in the container
DEVELOPMENT COMMANDS:
test Run all tests with pytest
test-cov Run tests with coverage report
lint Check code with ruff linter
lint-fix Auto-fix linting issues
lint-css Check CSS with stylelint
lint-yaml Check YAML with yamllint
format Format code with ruff
format-check Check code formatting without changes
type-check Run mypy type checking
ci Run all CI checks (lint, format, type-check, tests)
EXPORTER COMMANDS:
create-exporter [name] Create a new exporter interactively
build-exporter <name> Build a specific exporter locally
test-exporter <name> Test build an exporter (RPM + Docker)
test-rpm <name> [dist] Test RPM build in container (default: el9)
test-deb <name> [dist] Test DEB build in container (default: ubuntu-22.04)
test-rpm-sign <rpm_file> Test RPM signing in container (almalinux:9)
test-deb-sign <deb_file> Test DEB signing in container (debian:12)
list-exporters List all available exporters
NOTE: All tests run in isolated Docker containers - no local dependencies required
PORTAL & DOCS COMMANDS:
generate-portal Generate the web portal (index.html)
docs-build Build MkDocs documentation
docs-serve Serve docs with live reload at localhost:8000
validate-site Validate generated portal
URL VALIDATION COMMANDS:
validate-urls Validate all exporter artifact URLs
validate-url <name> Validate specific exporter URLs
STATE & VERSION COMMANDS:
state-manager Run state manager to detect changes
watcher Run version watcher to check for updates
UTILITY COMMANDS:
clean Clean build artifacts and caches (using Docker)
python <cmd> Run arbitrary Python command in container
bash <cmd> Run arbitrary bash command in container
help Show this help message
EXAMPLES:
./devctl build # Build dev image
./devctl test # Run tests
./devctl create-exporter # Create new exporter interactively
./devctl build-exporter node_exporter # Build specific exporter
./devctl test-rpm node_exporter # Test RPM build
./devctl test-deb node_exporter # Test DEB build
./devctl test-rpm-sign build/file.rpm # Test RPM signing
./devctl docs-serve # Serve docs at localhost:8000
./devctl shell # Open interactive shell
./devctl python -m core.engine.builder --help
EOF
}
# --- Command Handlers ---
cmd_build() {
info "Building development Docker image..."
docker build -t "$DEV_IMAGE" -f config/docker/Dockerfile.dev .
success "Development image built successfully"
}
cmd_rebuild() {
info "Rebuilding development Docker image from scratch..."
docker build --no-cache -t "$DEV_IMAGE" -f config/docker/Dockerfile.dev .
success "Development image rebuilt successfully"
}
cmd_shell() {
ensure_image
info "Opening interactive shell in development container..."
$DOCKER_RUN_IT "$DEV_IMAGE" /bin/bash
}
cmd_test() {
ensure_image
info "Running tests..."
$DOCKER_RUN "$DEV_IMAGE" pytest -v "$@"
}
cmd_test_cov() {
ensure_image
info "Running tests with coverage..."
$DOCKER_RUN "$DEV_IMAGE" pytest -v --cov=core --cov-report=term-missing --cov-report=html "$@"
}
cmd_lint() {
ensure_image
info "Running Python linter..."
$DOCKER_RUN "$DEV_IMAGE" ruff check .
}
cmd_lint_fix() {
ensure_image
info "Running linter with auto-fix..."
$DOCKER_RUN "$DEV_IMAGE" ruff check --fix .
success "Linting issues fixed"
}
cmd_lint_css() {
ensure_image
info "Running CSS linter..."
$DOCKER_RUN "$DEV_IMAGE" stylelint \
--config config/linting/.stylelintrc.json \
--ignore-path config/linting/.stylelintignore \
"**/*.css" "**/*.html.j2"
}
cmd_lint_yaml() {
ensure_image
info "Running YAML linter..."
$DOCKER_RUN "$DEV_IMAGE" yamllint exporters/ core/tests/fixtures/ config/
}
cmd_format() {
ensure_image
info "Formatting code..."
$DOCKER_RUN "$DEV_IMAGE" ruff format .
success "Code formatted"
}
cmd_format_check() {
ensure_image
info "Checking code formatting..."
$DOCKER_RUN "$DEV_IMAGE" ruff format --check .
}
cmd_type_check() {
ensure_image
info "Running type checker..."
$DOCKER_RUN "$DEV_IMAGE" mypy --explicit-package-bases core/
}
cmd_ci() {
ensure_image
info "Running all CI checks..."
$DOCKER_RUN "$DEV_IMAGE" /bin/bash -c "set -e && \
echo '==> Running linter...' && ruff check . && \
echo '==> Checking format...' && ruff format --check . && \
echo '==> Running type checker...' && mypy --explicit-package-bases core/ && \
echo '==> Running CSS linter...' && stylelint --config config/linting/.stylelintrc.json --ignore-path config/linting/.stylelintignore \"**/*.css\" \"**/*.html.j2\" && \
echo '==> Running YAML linter...' && yamllint exporters/ core/tests/fixtures/ config/ && \
echo '==> Running tests...' && pytest -v"
success "All CI checks passed"
}
cmd_create_exporter() {
ensure_image
info "Creating new exporter..."
$DOCKER_RUN_IT "$DEV_IMAGE" python3 ./core/scripts/create_exporter.py "$@"
}
cmd_build_exporter() {
ensure_image
if [ $# -eq 0 ]; then
error "Usage: ./devctl build-exporter <exporter_name>"
exit 1
fi
local exporter_name="$1"
shift
info "Building exporter: $exporter_name"
$DOCKER_RUN "$DEV_IMAGE" python3 -m core.engine.builder \
--manifest "exporters/${exporter_name}/manifest.yaml" \
--output-dir "build/${exporter_name}" \
--arch amd64 \
"$@"
success "Exporter built: build/${exporter_name}"
}
cmd_test_exporter() {
ensure_image
if [ $# -eq 0 ]; then
error "Usage: ./devctl test-exporter <exporter_name> [--arch <amd64|arm64>] [--el8|--el9|--el10]"
exit 1
fi
info "Testing exporter: $1"
$DOCKER_RUN_IT "$DEV_IMAGE" ./core/scripts/local_test.sh "$@"
}
cmd_test_rpm() {
if [ $# -eq 0 ]; then
error "Usage: ./devctl test-rpm <exporter_name> [--el8|--el9|--el10]"
echo ""
echo "Examples:"
echo " ./devctl test-rpm node_exporter"
echo " ./devctl test-rpm node_exporter --el10"
echo " ./devctl test-rpm node_exporter --el9 --smoke"
echo ""
echo "Options: --el8, --el9, --el10, --smoke, --docker"
exit 1
fi
local exporter_name="$1"
shift
info "Testing RPM build: $exporter_name"
# Pass all remaining arguments to local_test.sh
./core/scripts/local_test.sh "$exporter_name" "$@"
}
cmd_test_deb() {
if [ $# -eq 0 ]; then
error "Usage: ./devctl test-deb <exporter_name> [distribution] [architecture]"
echo ""
echo "Examples:"
echo " ./devctl test-deb node_exporter"
echo " ./devctl test-deb node_exporter ubuntu-24.04"
echo " ./devctl test-deb node_exporter debian-12 arm64"
echo ""
echo "Supported distributions: ubuntu-22.04, ubuntu-24.04, debian-12, debian-13"
echo "Supported architectures: amd64, arm64"
exit 1
fi
local exporter_name="$1"
local distribution="${2:-ubuntu-22.04}"
local architecture="${3:-amd64}"
info "Testing DEB build: $exporter_name ($distribution, $architecture)"
./core/scripts/test_deb_build.sh "$exporter_name" "$distribution" "$architecture"
}
cmd_test_rpm_sign() {
if [ $# -eq 0 ]; then
error "Usage: ./devctl test-rpm-sign <rpm_file>"
echo ""
echo "This tests RPM signing with the local GPG key from secrets/"
echo "Signing is performed inside a Docker container (almalinux:9)"
echo ""
echo "Example:"
echo " ./devctl test-rpm-sign build/node_exporter/rpms/node_exporter-1.10.2-1.el9.x86_64.rpm"
exit 1
fi
local rpm_file="$1"
if [ ! -f "$rpm_file" ]; then
error "RPM file not found: $rpm_file"
exit 1
fi
if [ ! -d "secrets" ] || [ ! -f "secrets/GPG_PRIVATE_KEY" ]; then
error "GPG secrets not found. Run: ./core/scripts/generate_gpg_key.sh"
exit 1
fi
info "Testing RPM signature in container..."
info "RPM file: $rpm_file"
# Read secrets
local gpg_key=$(cat secrets/GPG_PRIVATE_KEY)
local gpg_passphrase=$(cat secrets/GPG_PASSPHRASE)
local gpg_key_id=$(cat secrets/GPG_KEY_ID)
# Sign the RPM in container
./core/scripts/sign_rpm_container.sh "$rpm_file" "$gpg_key" "$gpg_passphrase" "$gpg_key_id"
success "RPM signed successfully in container!"
info "Verify with: docker run --rm -v \$(pwd):/work:ro almalinux:9 rpm -qpi /work/$rpm_file"
}
cmd_test_deb_sign() {
if [ $# -eq 0 ]; then
error "Usage: ./devctl test-deb-sign <deb_file>"
echo ""
echo "This tests DEB signing with the local GPG key from secrets/"
echo "Signing is performed inside a Docker container (debian:12)"
echo ""
echo "Example:"
echo " ./devctl test-deb-sign build/test_node_exporter/debs/node-exporter_1.10.2-1_amd64.deb"
exit 1
fi
local deb_file="$1"
if [ ! -f "$deb_file" ]; then
error "DEB file not found: $deb_file"
exit 1
fi
if [ ! -d "secrets" ] || [ ! -f "secrets/GPG_PRIVATE_KEY" ]; then
error "GPG secrets not found. Run: ./core/scripts/generate_gpg_key.sh"
exit 1
fi
info "Testing DEB signature in container..."
info "DEB file: $deb_file"
# Read secrets
local gpg_key=$(cat secrets/GPG_PRIVATE_KEY)
local gpg_key_id=$(cat secrets/GPG_KEY_ID)
local gpg_passphrase=$(cat secrets/GPG_PASSPHRASE)
# Sign the DEB in container
./core/scripts/sign_deb_container.sh "$deb_file" "$gpg_key" "$gpg_passphrase" "$gpg_key_id"
success "DEB signed successfully in container!"
success "Signature file: ${deb_file}.asc"
info "Verify with: docker run --rm -v \$(pwd):/work:ro debian:12 bash -c 'apt-get update -qq && apt-get install -y -qq gnupg && gpg --verify /work/${deb_file}.asc /work/$deb_file'"
}
cmd_list_exporters() {
ensure_image
info "Available exporters:"
$DOCKER_RUN "$DEV_IMAGE" /bin/bash -c "ls -1 exporters/ | grep -v '^\.' | sort"
}
cmd_generate_portal() {
ensure_image
info "Generating web portal..."
$DOCKER_RUN "$DEV_IMAGE" python3 -m core.engine.site_generator
success "Portal generated: index.html"
}
cmd_docs_build() {
ensure_image
info "Building MkDocs documentation..."
$DOCKER_RUN "$DEV_IMAGE" mkdocs build -f config/docs/mkdocs.yml
success "Documentation built: site/"
}
cmd_docs_serve() {
ensure_image
info "Serving documentation at http://localhost:8000"
info "Press Ctrl+C to stop..."
$DOCKER_RUN_IT -p 8000:8000 "$DEV_IMAGE" mkdocs serve -a 0.0.0.0:8000 -f config/docs/mkdocs.yml
}
cmd_validate_site() {
ensure_image
info "Validating generated portal..."
$DOCKER_RUN "$DEV_IMAGE" python3 ./core/scripts/validate_site.py
}
cmd_validate_urls() {
ensure_image
info "Validating artifact URLs for all exporters..."
$DOCKER_RUN "$DEV_IMAGE" python3 ./core/scripts/validate_urls.py "$@"
}
cmd_validate_url() {
ensure_image
if [ $# -eq 0 ]; then
error "Usage: ./devctl validate-url <exporter_name>"
exit 1
fi
local exporter_name="$1"
shift
info "Validating artifact URLs for: $exporter_name"
$DOCKER_RUN "$DEV_IMAGE" python3 ./core/scripts/validate_urls.py --exporter "$exporter_name" "$@"
}
cmd_state_manager() {
ensure_image
info "Running state manager..."
$DOCKER_RUN "$DEV_IMAGE" python3 -m core.engine.state_manager "$@"
}
cmd_watcher() {
ensure_image
info "Running version watcher..."
$DOCKER_RUN "$DEV_IMAGE" python3 -m core.engine.watcher "$@"
}
cmd_python() {
ensure_image
$DOCKER_RUN_IT "$DEV_IMAGE" python3 "$@"
}
cmd_bash() {
ensure_image
$DOCKER_RUN_IT "$DEV_IMAGE" /bin/bash -c "$*"
}
cmd_clean() {
ensure_image
info "Cleaning build artifacts and caches..."
$DOCKER_RUN "$DEV_IMAGE" /bin/bash -c "rm -rf .pytest_cache htmlcov .coverage coverage.xml .mypy_cache .ruff_cache catalog.json index.html build/ && find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true && find . -type f -name '*.pyc' -delete"
success "Cleanup completed"
}
# --- Main Command Router ---
main() {
check_docker
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command="$1"
shift
case "$command" in
# Setup
build) cmd_build "$@" ;;
rebuild) cmd_rebuild "$@" ;;
shell) cmd_shell "$@" ;;
# Development
test) cmd_test "$@" ;;
test-cov) cmd_test_cov "$@" ;;
lint) cmd_lint "$@" ;;
lint-fix) cmd_lint_fix "$@" ;;
lint-css) cmd_lint_css "$@" ;;
lint-yaml) cmd_lint_yaml "$@" ;;
format) cmd_format "$@" ;;
format-check) cmd_format_check "$@" ;;
type-check) cmd_type_check "$@" ;;
ci) cmd_ci "$@" ;;
# Exporters
create-exporter) cmd_create_exporter "$@" ;;
build-exporter) cmd_build_exporter "$@" ;;
test-exporter) cmd_test_exporter "$@" ;;
test-rpm) cmd_test_rpm "$@" ;;
test-deb) cmd_test_deb "$@" ;;
test-rpm-sign) cmd_test_rpm_sign "$@" ;;
test-deb-sign) cmd_test_deb_sign "$@" ;;
list-exporters) cmd_list_exporters "$@" ;;
# Portal & Docs
generate-portal) cmd_generate_portal "$@" ;;
docs-build) cmd_docs_build "$@" ;;
docs-serve) cmd_docs_serve "$@" ;;
validate-site) cmd_validate_site "$@" ;;
# URL Validation
validate-urls) cmd_validate_urls "$@" ;;
validate-url) cmd_validate_url "$@" ;;
# State & Version
state-manager) cmd_state_manager "$@" ;;
watcher) cmd_watcher "$@" ;;
# Utilities
clean) cmd_clean "$@" ;;
python) cmd_python "$@" ;;
bash) cmd_bash "$@" ;;
help|--help|-h) show_help ;;
*)
error "Unknown command: $command"
echo ""
show_help
exit 1
;;
esac
}
main "$@"