pingera-cli 0.1.5__tar.gz → 0.1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pingera_cli-0.1.5/pingera_cli.egg-info → pingera_cli-0.1.6}/PKG-INFO +1 -1
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/docs/cli-commands.md +93 -7
- pingera_cli-0.1.6/docs/example-check.json +23 -0
- pingera_cli-0.1.6/docs/example-check.yaml +21 -0
- pingera_cli-0.1.6/docs/example-synthetic-screenshot.json +16 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/__init__.py +1 -1
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/checks.py +132 -13
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/on_demand_checks.py +119 -9
- {pingera_cli-0.1.5 → pingera_cli-0.1.6/pingera_cli.egg-info}/PKG-INFO +1 -1
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli.egg-info/SOURCES.txt +3 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/.gitignore +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/.replit +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/LICENSE +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/README.md +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/assert +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/__main__.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/__init__.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/auth.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/base.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/check_groups.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/check_secrets.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/secrets.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/__init__.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/base_formatter.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/generic_formatter.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/multistep_formatter.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/registry.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/ssl_formatter.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/synthetic_formatter.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/web_formatter.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/main.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/utils/__init__.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/utils/config.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/utils/console.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli.egg-info/dependency_links.txt +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli.egg-info/entry_points.txt +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli.egg-info/requires.txt +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli.egg-info/top_level.txt +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pyproject.toml +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/replit.md +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/run_tests.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/setup.cfg +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/setup.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/__init__.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/conftest.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/requirements.txt +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/test_auth.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/test_checks.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/test_config.py +0 -0
- {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/test_on_demand_checks.py +0 -0
|
@@ -9,7 +9,7 @@ The `pngr` CLI provides a comprehensive interface to the Pingera monitoring plat
|
|
|
9
9
|
These options are available for all commands:
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
--api-key <key> API key for authentication (can also
|
|
12
|
+
--api-key <key> API key for authentication (can also be used PINGERA_API_KEY env var)
|
|
13
13
|
--base-url <url> API base URL (default: https://api.pingera.ru)
|
|
14
14
|
--output <format> Output format: table, json, yaml (default: table)
|
|
15
15
|
--page-id <id> Default page ID for status page operations (can also use PINGERA_PAGE_ID env var)
|
|
@@ -183,7 +183,7 @@ pngr heartbeats get <heartbeat-id>
|
|
|
183
183
|
pngr heartbeats create --name <name> --interval <seconds> [--grace-period <seconds>]
|
|
184
184
|
|
|
185
185
|
# Update heartbeat
|
|
186
|
-
pngr heartbeats update <heartbeat-id>
|
|
186
|
+
pngr heartbeats update <heartbeat-id> --name <name> --interval <seconds>
|
|
187
187
|
|
|
188
188
|
# Delete heartbeat
|
|
189
189
|
pngr heartbeats delete <heartbeat-id> [--confirm]
|
|
@@ -211,7 +211,7 @@ pngr pages get <page-id>
|
|
|
211
211
|
pngr pages create --name <name> [--description <desc>] [--subdomain <sub>] [--theme <theme>]
|
|
212
212
|
|
|
213
213
|
# Update status page
|
|
214
|
-
pngr pages update <page-id>
|
|
214
|
+
pngr pages update <page-id> --name <name> --description <desc> --theme <theme>
|
|
215
215
|
|
|
216
216
|
# Delete status page
|
|
217
217
|
pngr pages delete <page-id> [--confirm]
|
|
@@ -231,7 +231,7 @@ pngr components get <component-id> [--page-id <id>]
|
|
|
231
231
|
pngr components create --name <name> [--description <desc>] [--status <status>] [--page-id <id>]
|
|
232
232
|
|
|
233
233
|
# Update component
|
|
234
|
-
pngr components update <component-id>
|
|
234
|
+
pngr components update <component-id> --name <name> --description <desc> --page-id <id>
|
|
235
235
|
|
|
236
236
|
# Delete component
|
|
237
237
|
pngr components delete <component-id> [--page-id <id>] [--confirm]
|
|
@@ -260,7 +260,7 @@ pngr incidents get <incident-id> [--page-id <id>]
|
|
|
260
260
|
pngr incidents create --name <name> --body <text> --status <status> --impact <impact> [--page-id <id>]
|
|
261
261
|
|
|
262
262
|
# Update incident
|
|
263
|
-
pngr incidents update <incident-id>
|
|
263
|
+
pngr incidents update <incident-id> --name <name> --body <text> --status <status> --page-id <id>
|
|
264
264
|
|
|
265
265
|
# Delete incident
|
|
266
266
|
pngr incidents delete <incident-id> [--page-id <id>] [--confirm]
|
|
@@ -273,7 +273,93 @@ pngr incidents updates update <incident-id> <update-id> --body <text> [--page-id
|
|
|
273
273
|
pngr incidents updates delete <incident-id> <update-id> [--page-id <id>] [--confirm]
|
|
274
274
|
```
|
|
275
275
|
|
|
276
|
-
##
|
|
276
|
+
## 📄 Check Configuration Files
|
|
277
|
+
|
|
278
|
+
You can create checks from JSON or YAML configuration files using the `--from-file` option:
|
|
279
|
+
|
|
280
|
+
### JSON Format
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"name": "My API Check",
|
|
284
|
+
"type": "api",
|
|
285
|
+
"url": "https://api.example.com/health",
|
|
286
|
+
"interval": 300,
|
|
287
|
+
"timeout": 30,
|
|
288
|
+
"parameters": {
|
|
289
|
+
"regions": ["US", "EU"],
|
|
290
|
+
"http_request": {
|
|
291
|
+
"method": "POST",
|
|
292
|
+
"headers": {
|
|
293
|
+
"Authorization": "Bearer token123",
|
|
294
|
+
"Content-Type": "application/json"
|
|
295
|
+
},
|
|
296
|
+
"body": "{\"check\": \"health\"}"
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### YAML Format
|
|
303
|
+
```yaml
|
|
304
|
+
name: "My Synthetic Check"
|
|
305
|
+
type: "synthetic"
|
|
306
|
+
interval: 600
|
|
307
|
+
timeout: 60
|
|
308
|
+
parameters:
|
|
309
|
+
regions:
|
|
310
|
+
- "US"
|
|
311
|
+
- "EU"
|
|
312
|
+
pw_script: |
|
|
313
|
+
const { test, expect } = require('@playwright/test');
|
|
314
|
+
test('login test', async ({ page }) => {
|
|
315
|
+
await page.goto('https://example.com/login');
|
|
316
|
+
await page.fill('#username', 'testuser');
|
|
317
|
+
await page.fill('#password', 'password');
|
|
318
|
+
await page.click('button[type="submit"]');
|
|
319
|
+
await expect(page).toHaveURL(/dashboard/);
|
|
320
|
+
});
|
|
321
|
+
# Marketplace fields (ignored by CLI, used by web interface)
|
|
322
|
+
marketplace:
|
|
323
|
+
tags: ["authentication", "login"]
|
|
324
|
+
description: "Tests user login functionality"
|
|
325
|
+
category: "user-flows"
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Field Priority
|
|
329
|
+
When using `--from-file` with command line options:
|
|
330
|
+
- Command line options override file values
|
|
331
|
+
- File provides defaults for unspecified options
|
|
332
|
+
- Required validations still apply
|
|
333
|
+
- Non-SDK fields (like `marketplace`, custom fields) are ignored and not sent to the API
|
|
334
|
+
- CLI-specific fields like `pw_script_file` are processed but not sent to the SDK
|
|
335
|
+
|
|
336
|
+
### Create a new check
|
|
337
|
+
```bash
|
|
338
|
+
# From command line options
|
|
339
|
+
pngr checks create --name "My Website" --type web --url "https://example.com"
|
|
340
|
+
|
|
341
|
+
# From configuration file
|
|
342
|
+
pngr checks create --from-file check-config.json
|
|
343
|
+
pngr checks create --from-file check-config.yaml
|
|
344
|
+
|
|
345
|
+
# Combine file and command line (command line options override file values)
|
|
346
|
+
pngr checks create --from-file check-config.json --name "Override Name"
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Run on-demand checks
|
|
350
|
+
```bash
|
|
351
|
+
# From command line options
|
|
352
|
+
pngr checks run custom --url https://example.com --type web --wait-for-result
|
|
353
|
+
|
|
354
|
+
# From configuration file
|
|
355
|
+
pngr checks run custom --from-file check-config.json --wait-for-result
|
|
356
|
+
pngr checks run custom --from-file check-config.yaml --wait-for-result
|
|
357
|
+
|
|
358
|
+
# Combine file and command line (command line options override file values)
|
|
359
|
+
pngr checks run custom --from-file check-config.json --name "Override Name" --wait-for-result
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## 🔐 Secrets Management
|
|
277
363
|
|
|
278
364
|
Manage organization secrets for use in monitoring checks:
|
|
279
365
|
|
|
@@ -400,7 +486,7 @@ pngr config set defaults.page_id your-page-id
|
|
|
400
486
|
pngr checks list --status active
|
|
401
487
|
|
|
402
488
|
# Create a web check
|
|
403
|
-
pngr checks create --name "
|
|
489
|
+
pngr checks create --name "My Website" --type web --url "https://example.com"
|
|
404
490
|
|
|
405
491
|
# Create a TCP check
|
|
406
492
|
pngr checks create --name "Database Connection" --type tcp --host db.example.com --port 5432
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
{
|
|
3
|
+
"name": "Example Web Check",
|
|
4
|
+
"type": "web",
|
|
5
|
+
"url": "https://example.com",
|
|
6
|
+
"interval": 300,
|
|
7
|
+
"timeout": 30,
|
|
8
|
+
"parameters": {
|
|
9
|
+
"regions": ["RU, Moscow", "EU, West"],
|
|
10
|
+
"search_text": "Welcome",
|
|
11
|
+
"http_request": {
|
|
12
|
+
"method": "GET",
|
|
13
|
+
"headers": {
|
|
14
|
+
"User-Agent": "PingeraCLI/1.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"marketplace": {
|
|
19
|
+
"tags": ["web", "basic"],
|
|
20
|
+
"name": "Simple website check with string search",
|
|
21
|
+
"description": "Queries website URL and searches for a string in the response",
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
name: "Example Synthetic Check"
|
|
3
|
+
type: "synthetic"
|
|
4
|
+
interval: 600
|
|
5
|
+
timeout: 60
|
|
6
|
+
parameters:
|
|
7
|
+
regions:
|
|
8
|
+
- "US"
|
|
9
|
+
- "EU"
|
|
10
|
+
pw_script: |
|
|
11
|
+
const { test, expect } = require('@playwright/test');
|
|
12
|
+
|
|
13
|
+
test('example test', async ({ page }) => {
|
|
14
|
+
await page.goto('https://example.com');
|
|
15
|
+
await expect(page).toHaveTitle(/Example/);
|
|
16
|
+
await page.click('text=Login');
|
|
17
|
+
await page.fill('input[name="username"]', 'testuser');
|
|
18
|
+
await page.fill('input[name="password"]', 'testpass');
|
|
19
|
+
await page.click('button[type="submit"]');
|
|
20
|
+
await expect(page).toHaveURL(/dashboard/);
|
|
21
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
{
|
|
3
|
+
"name": "Website Screenshot Monitor",
|
|
4
|
+
"type": "synthetic",
|
|
5
|
+
"interval": 1800,
|
|
6
|
+
"timeout": 30,
|
|
7
|
+
"parameters": {
|
|
8
|
+
"regions": ["RU, Moscow"],
|
|
9
|
+
"pw_script": "const { test, expect } = require('@playwright/test');\n\ntest('website screenshot test', async ({ page }) => {\n // Navigate to the website\n await page.goto('https://pingera.ru');\n \n // Wait for the page to fully load\n await page.waitForLoadState('load');\n \n // Additional wait to ensure all dynamic content is loaded\n await page.waitForTimeout(2000);\n \n // Take a full page screenshot\n await page.screenshot({ \n path: 'website-screenshot.png', \n fullPage: true \n });\n \n // Verify the page loaded successfully\n await expect(page).toHaveTitle(/Example/);\n \n // Check for key elements on the page\n await expect(page.locator('h1')).toBeVisible();\n \n console.log('Screenshot captured and page verified successfully');\n});",
|
|
10
|
+
},
|
|
11
|
+
"marketplace": {
|
|
12
|
+
"tags": ["screenshot", "visual-monitoring", "website-monitoring"],
|
|
13
|
+
"name": "Website Screenshot Monitor",
|
|
14
|
+
"description": "Captures full-page screenshots of websites and verifies page loading",
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -389,20 +389,126 @@ class ChecksCommand(BaseCommand):
|
|
|
389
389
|
self.display_error(f"Failed to get check: {str(e)}")
|
|
390
390
|
raise typer.Exit(1)
|
|
391
391
|
|
|
392
|
-
def
|
|
392
|
+
def _parse_check_file(self, file_path: str) -> dict:
|
|
393
|
+
"""Parse check configuration from JSON or YAML file"""
|
|
394
|
+
import json
|
|
395
|
+
import os
|
|
396
|
+
|
|
397
|
+
if not os.path.exists(file_path):
|
|
398
|
+
self.display_error(f"Check file not found: {file_path}")
|
|
399
|
+
raise typer.Exit(1)
|
|
400
|
+
|
|
401
|
+
try:
|
|
402
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
403
|
+
content = f.read().strip()
|
|
404
|
+
|
|
405
|
+
if not content:
|
|
406
|
+
self.display_error(f"Check file is empty: {file_path}")
|
|
407
|
+
raise typer.Exit(1)
|
|
408
|
+
|
|
409
|
+
# Try to determine format from file extension or content
|
|
410
|
+
file_ext = os.path.splitext(file_path)[1].lower()
|
|
411
|
+
|
|
412
|
+
if file_ext in ['.yaml', '.yml']:
|
|
413
|
+
try:
|
|
414
|
+
import yaml
|
|
415
|
+
return yaml.safe_load(content)
|
|
416
|
+
except ImportError:
|
|
417
|
+
self.display_error("YAML support not available. Install with: pip install pyyaml")
|
|
418
|
+
raise typer.Exit(1)
|
|
419
|
+
except yaml.YAMLError as e:
|
|
420
|
+
self.display_error(f"Invalid YAML in file {file_path}: {str(e)}")
|
|
421
|
+
raise typer.Exit(1)
|
|
422
|
+
else:
|
|
423
|
+
# Default to JSON
|
|
424
|
+
try:
|
|
425
|
+
return json.loads(content)
|
|
426
|
+
except json.JSONDecodeError as e:
|
|
427
|
+
self.display_error(f"Invalid JSON in file {file_path}: {str(e)}")
|
|
428
|
+
raise typer.Exit(1)
|
|
429
|
+
|
|
430
|
+
except IOError as e:
|
|
431
|
+
self.display_error(f"Failed to read check file: {str(e)}")
|
|
432
|
+
raise typer.Exit(1)
|
|
433
|
+
|
|
434
|
+
def create_check(self, name: str, check_type: str, url: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None, interval: int = 300, timeout: int = 30, parameters: Optional[str] = None, pw_script_file: Optional[str] = None, from_file: Optional[str] = None):
|
|
393
435
|
"""Create a new monitoring check"""
|
|
394
436
|
try:
|
|
395
437
|
import json
|
|
396
438
|
import os
|
|
397
439
|
checks_api = self.get_client()
|
|
398
440
|
|
|
399
|
-
#
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
441
|
+
# If creating from file, parse file and merge with command line options
|
|
442
|
+
if from_file:
|
|
443
|
+
file_data = self._parse_check_file(from_file)
|
|
444
|
+
|
|
445
|
+
# Filter only SDK-recognized fields from file data
|
|
446
|
+
# This ignores marketplace-specific fields and other extensions
|
|
447
|
+
sdk_fields = ["name", "type", "url", "host", "port", "interval", "timeout", "parameters", "active"]
|
|
448
|
+
filtered_file_data = {k: v for k, v in file_data.items() if k in sdk_fields}
|
|
449
|
+
|
|
450
|
+
# Command line options take precedence over file data
|
|
451
|
+
check_data = {
|
|
452
|
+
"name": filtered_file_data.get("name", name),
|
|
453
|
+
"type": filtered_file_data.get("type", check_type),
|
|
454
|
+
"interval": filtered_file_data.get("interval", interval),
|
|
455
|
+
"timeout": filtered_file_data.get("timeout", timeout)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
# Override with command line values if provided
|
|
459
|
+
if name != "": # Only override if name was explicitly provided
|
|
460
|
+
check_data["name"] = name
|
|
461
|
+
if check_type != "web": # Only override if type was explicitly provided
|
|
462
|
+
check_data["type"] = check_type
|
|
463
|
+
if interval != 300: # Only override if interval was explicitly provided
|
|
464
|
+
check_data["interval"] = interval
|
|
465
|
+
if timeout != 30: # Only override if timeout was explicitly provided
|
|
466
|
+
check_data["timeout"] = timeout
|
|
467
|
+
|
|
468
|
+
# Handle URL from file
|
|
469
|
+
if url is None and filtered_file_data.get("url"):
|
|
470
|
+
url = filtered_file_data["url"]
|
|
471
|
+
elif url is not None:
|
|
472
|
+
# Command line URL takes precedence
|
|
473
|
+
pass
|
|
474
|
+
|
|
475
|
+
# Handle host/port from file
|
|
476
|
+
if host is None and filtered_file_data.get("host"):
|
|
477
|
+
host = filtered_file_data["host"]
|
|
478
|
+
elif host is not None:
|
|
479
|
+
# Command line host takes precedence
|
|
480
|
+
pass
|
|
481
|
+
|
|
482
|
+
if port is None and filtered_file_data.get("port"):
|
|
483
|
+
port = filtered_file_data["port"]
|
|
484
|
+
elif port is not None:
|
|
485
|
+
# Command line port takes precedence
|
|
486
|
+
pass
|
|
487
|
+
|
|
488
|
+
# Handle parameters from file
|
|
489
|
+
file_parameters = filtered_file_data.get("parameters")
|
|
490
|
+
if file_parameters and parameters is None:
|
|
491
|
+
# Use file parameters if no command line parameters
|
|
492
|
+
parameters = json.dumps(file_parameters) if isinstance(file_parameters, dict) else file_parameters
|
|
493
|
+
|
|
494
|
+
# Handle pw_script_file from file (this is CLI-specific, not sent to SDK)
|
|
495
|
+
if pw_script_file is None and file_data.get("pw_script_file"):
|
|
496
|
+
pw_script_file = file_data["pw_script_file"]
|
|
497
|
+
|
|
498
|
+
# Show info about ignored fields if any
|
|
499
|
+
ignored_fields = [k for k in file_data.keys() if k not in sdk_fields and k != "pw_script_file"]
|
|
500
|
+
if ignored_fields:
|
|
501
|
+
self.display_info(f"Ignoring non-SDK fields from file: {', '.join(ignored_fields)}")
|
|
502
|
+
|
|
503
|
+
self.display_info(f"Creating check from file: {from_file}")
|
|
504
|
+
else:
|
|
505
|
+
# Build check data normally
|
|
506
|
+
check_data = {
|
|
507
|
+
"name": name,
|
|
508
|
+
"type": check_type,
|
|
509
|
+
"interval": interval,
|
|
510
|
+
"timeout": timeout
|
|
511
|
+
}
|
|
406
512
|
|
|
407
513
|
# Add URL if provided (required for web, api, ssl checks)
|
|
408
514
|
if url is not None:
|
|
@@ -1075,23 +1181,36 @@ def get_check(
|
|
|
1075
1181
|
|
|
1076
1182
|
@app.command("create")
|
|
1077
1183
|
def create_check(
|
|
1078
|
-
name: str = typer.Option(
|
|
1079
|
-
check_type: str = typer.Option(
|
|
1184
|
+
name: str = typer.Option("", "--name", "-n", help="Check name (required if not using --from-file)"),
|
|
1185
|
+
check_type: str = typer.Option("web", "--type", "-t", help="Check type (web, api, tcp, ssl, synthetic, multistep)"),
|
|
1080
1186
|
url: Optional[str] = typer.Option(None, "--url", "-u", help="URL to monitor (required for web, api, ssl checks)"),
|
|
1081
1187
|
host: Optional[str] = typer.Option(None, "--host", help="Hostname/IP for TCP/SSL checks (max 255 characters)"),
|
|
1082
1188
|
port: Optional[int] = typer.Option(None, "--port", help="Port number for TCP checks (1-65535)"),
|
|
1083
1189
|
interval: int = typer.Option(300, "--interval", "-i", help="Check interval in seconds"),
|
|
1084
1190
|
timeout: int = typer.Option(30, "--timeout", help="Timeout in seconds"),
|
|
1085
1191
|
parameters: Optional[str] = typer.Option(None, "--parameters", help="JSON string with check parameters (e.g., '{\"regions\": [\"US\", \"EU\"]}')"),
|
|
1086
|
-
pw_script_file: Optional[str] = typer.Option(None, "--pw-script-file", help="Path to file containing Playwright script for synthetic/multistep checks")
|
|
1192
|
+
pw_script_file: Optional[str] = typer.Option(None, "--pw-script-file", help="Path to file containing Playwright script for synthetic/multistep checks"),
|
|
1193
|
+
from_file: Optional[str] = typer.Option(None, "--from-file", help="Path to JSON or YAML file containing check configuration")
|
|
1087
1194
|
):
|
|
1088
|
-
"""Create a new monitoring check.
|
|
1195
|
+
"""Create a new monitoring check. Can be created from command line options or from a JSON/YAML file.
|
|
1196
|
+
|
|
1197
|
+
When using --from-file:
|
|
1198
|
+
- Command line options override file values
|
|
1199
|
+
- File should contain check configuration in JSON or YAML format
|
|
1200
|
+
|
|
1201
|
+
Parameters vary by check type:
|
|
1089
1202
|
- web/api: --url required
|
|
1090
1203
|
- tcp: --host required, --port optional
|
|
1091
1204
|
- ssl: --url or --host required
|
|
1092
1205
|
- synthetic/multistep: --pw-script-file or --parameters with pw_script required"""
|
|
1206
|
+
|
|
1207
|
+
# Validate that we have either from_file or required parameters
|
|
1208
|
+
if not from_file and not name:
|
|
1209
|
+
typer.echo("Error: --name is required when not using --from-file", err=True)
|
|
1210
|
+
raise typer.Exit(1)
|
|
1211
|
+
|
|
1093
1212
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1094
|
-
checks_cmd.create_check(name, check_type, url, host, port, interval, timeout, parameters, pw_script_file)
|
|
1213
|
+
checks_cmd.create_check(name, check_type, url, host, port, interval, timeout, parameters, pw_script_file, from_file)
|
|
1095
1214
|
|
|
1096
1215
|
|
|
1097
1216
|
@app.command("update")
|
|
@@ -21,6 +21,48 @@ class OnDemandChecksCommand(BaseCommand):
|
|
|
21
21
|
|
|
22
22
|
def __init__(self, output_format: Optional[str] = None):
|
|
23
23
|
super().__init__(output_format)
|
|
24
|
+
|
|
25
|
+
def _parse_check_file(self, file_path: str) -> dict:
|
|
26
|
+
"""Parse check configuration from JSON or YAML file"""
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
|
|
30
|
+
if not os.path.exists(file_path):
|
|
31
|
+
self.display_error(f"Check file not found: {file_path}")
|
|
32
|
+
raise typer.Exit(1)
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
36
|
+
content = f.read().strip()
|
|
37
|
+
|
|
38
|
+
if not content:
|
|
39
|
+
self.display_error(f"Check file is empty: {file_path}")
|
|
40
|
+
raise typer.Exit(1)
|
|
41
|
+
|
|
42
|
+
# Try to determine format from file extension or content
|
|
43
|
+
file_ext = os.path.splitext(file_path)[1].lower()
|
|
44
|
+
|
|
45
|
+
if file_ext in ['.yaml', '.yml']:
|
|
46
|
+
try:
|
|
47
|
+
import yaml
|
|
48
|
+
return yaml.safe_load(content)
|
|
49
|
+
except ImportError:
|
|
50
|
+
self.display_error("YAML support not available. Install with: pip install pyyaml")
|
|
51
|
+
raise typer.Exit(1)
|
|
52
|
+
except yaml.YAMLError as e:
|
|
53
|
+
self.display_error(f"Invalid YAML in file {file_path}: {str(e)}")
|
|
54
|
+
raise typer.Exit(1)
|
|
55
|
+
else:
|
|
56
|
+
# Default to JSON
|
|
57
|
+
try:
|
|
58
|
+
return json.loads(content)
|
|
59
|
+
except json.JSONDecodeError as e:
|
|
60
|
+
self.display_error(f"Invalid JSON in file {file_path}: {str(e)}")
|
|
61
|
+
raise typer.Exit(1)
|
|
62
|
+
|
|
63
|
+
except IOError as e:
|
|
64
|
+
self.display_error(f"Failed to read check file: {str(e)}")
|
|
65
|
+
raise typer.Exit(1)
|
|
24
66
|
|
|
25
67
|
def get_client(self):
|
|
26
68
|
"""Get Pingera SDK client with authentication for on-demand checks"""
|
|
@@ -48,7 +90,7 @@ class OnDemandChecksCommand(BaseCommand):
|
|
|
48
90
|
self.display_error(f"Failed to initialize client: {str(e)}")
|
|
49
91
|
raise typer.Exit(1)
|
|
50
92
|
|
|
51
|
-
def execute_custom_check(self, url: Optional[str] = None, check_type: str = "web", host: Optional[str] = None, port: Optional[int] = None, timeout: int = 30, name: str = "On-demand check", parameters: Optional[str] = None, pw_script_file: Optional[str] = None, wait_for_result: bool = False):
|
|
93
|
+
def execute_custom_check(self, url: Optional[str] = None, check_type: str = "web", host: Optional[str] = None, port: Optional[int] = None, timeout: int = 30, name: str = "On-demand check", parameters: Optional[str] = None, pw_script_file: Optional[str] = None, from_file: Optional[str] = None, wait_for_result: bool = False):
|
|
52
94
|
"""Execute custom on-demand check"""
|
|
53
95
|
try:
|
|
54
96
|
import json
|
|
@@ -58,12 +100,73 @@ class OnDemandChecksCommand(BaseCommand):
|
|
|
58
100
|
# Import the SDK models
|
|
59
101
|
from pingera.models import ExecuteCustomCheckRequest
|
|
60
102
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
103
|
+
# If creating from file, parse file and merge with command line options
|
|
104
|
+
if from_file:
|
|
105
|
+
file_data = self._parse_check_file(from_file)
|
|
106
|
+
|
|
107
|
+
# Filter only SDK-recognized fields from file data
|
|
108
|
+
# This ignores marketplace-specific fields and other extensions
|
|
109
|
+
sdk_fields = ["name", "type", "url", "host", "port", "timeout", "parameters"]
|
|
110
|
+
filtered_file_data = {k: v for k, v in file_data.items() if k in sdk_fields}
|
|
111
|
+
|
|
112
|
+
# Command line options take precedence over file data
|
|
113
|
+
check_data = {
|
|
114
|
+
"name": filtered_file_data.get("name", name),
|
|
115
|
+
"type": filtered_file_data.get("type", check_type),
|
|
116
|
+
"timeout": filtered_file_data.get("timeout", timeout)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Override with command line values if provided
|
|
120
|
+
if name != "On-demand check": # Only override if name was explicitly provided
|
|
121
|
+
check_data["name"] = name
|
|
122
|
+
if check_type != "web": # Only override if type was explicitly provided
|
|
123
|
+
check_data["type"] = check_type
|
|
124
|
+
if timeout != 30: # Only override if timeout was explicitly provided
|
|
125
|
+
check_data["timeout"] = timeout
|
|
126
|
+
|
|
127
|
+
# Handle URL from file
|
|
128
|
+
if url is None and filtered_file_data.get("url"):
|
|
129
|
+
url = filtered_file_data["url"]
|
|
130
|
+
elif url is not None:
|
|
131
|
+
# Command line URL takes precedence
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
# Handle host/port from file
|
|
135
|
+
if host is None and filtered_file_data.get("host"):
|
|
136
|
+
host = filtered_file_data["host"]
|
|
137
|
+
elif host is not None:
|
|
138
|
+
# Command line host takes precedence
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
if port is None and filtered_file_data.get("port"):
|
|
142
|
+
port = filtered_file_data["port"]
|
|
143
|
+
elif port is not None:
|
|
144
|
+
# Command line port takes precedence
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
# Handle parameters from file
|
|
148
|
+
file_parameters = filtered_file_data.get("parameters")
|
|
149
|
+
if file_parameters and parameters is None:
|
|
150
|
+
# Use file parameters if no command line parameters
|
|
151
|
+
parameters = json.dumps(file_parameters) if isinstance(file_parameters, dict) else file_parameters
|
|
152
|
+
|
|
153
|
+
# Handle pw_script_file from file (this is CLI-specific, not sent to SDK)
|
|
154
|
+
if pw_script_file is None and file_data.get("pw_script_file"):
|
|
155
|
+
pw_script_file = file_data["pw_script_file"]
|
|
156
|
+
|
|
157
|
+
# Show info about ignored fields if any
|
|
158
|
+
ignored_fields = [k for k in file_data.keys() if k not in sdk_fields and k != "pw_script_file"]
|
|
159
|
+
if ignored_fields:
|
|
160
|
+
self.display_info(f"Ignoring non-SDK fields from file: {', '.join(ignored_fields)}")
|
|
161
|
+
|
|
162
|
+
self.display_info(f"Creating on-demand check from file: {from_file}")
|
|
163
|
+
else:
|
|
164
|
+
# Build check data normally
|
|
165
|
+
check_data = {
|
|
166
|
+
"name": name,
|
|
167
|
+
"type": check_type,
|
|
168
|
+
"timeout": timeout
|
|
169
|
+
}
|
|
67
170
|
|
|
68
171
|
# Add URL if provided (required for web, api, ssl checks)
|
|
69
172
|
if url is not None:
|
|
@@ -606,15 +709,22 @@ def run_custom_check(
|
|
|
606
709
|
name: str = typer.Option("On-demand check", "--name", "-n", help="Check name"),
|
|
607
710
|
parameters: Optional[str] = typer.Option(None, "--parameters", help="JSON string with check parameters (e.g., '{\"regions\": [\"US\", \"EU\"]}')"),
|
|
608
711
|
pw_script_file: Optional[str] = typer.Option(None, "--pw-script-file", help="Path to file containing Playwright script for synthetic/multistep checks"),
|
|
712
|
+
from_file: Optional[str] = typer.Option(None, "--from-file", help="Path to JSON or YAML file containing check configuration"),
|
|
609
713
|
wait_for_result: bool = typer.Option(False, "--wait-for-result", help="Wait for job completion and show result immediately (max 5 minutes)"),
|
|
610
714
|
):
|
|
611
|
-
"""Execute custom on-demand check.
|
|
715
|
+
"""Execute custom on-demand check. Can be executed from command line options or from a JSON/YAML file.
|
|
716
|
+
|
|
717
|
+
When using --from-file:
|
|
718
|
+
- Command line options override file values
|
|
719
|
+
- File should contain check configuration in JSON or YAML format
|
|
720
|
+
|
|
721
|
+
Parameters vary by check type:
|
|
612
722
|
- web/api: --url required
|
|
613
723
|
- tcp: --host required, --port optional
|
|
614
724
|
- ssl: --url or --host required
|
|
615
725
|
- synthetic/multistep: --pw-script-file or --parameters with pw_script required"""
|
|
616
726
|
on_demand_cmd = OnDemandChecksCommand(get_output_format())
|
|
617
|
-
on_demand_cmd.execute_custom_check(url, check_type, host, port, timeout, name, parameters, pw_script_file, wait_for_result)
|
|
727
|
+
on_demand_cmd.execute_custom_check(url, check_type, host, port, timeout, name, parameters, pw_script_file, from_file, wait_for_result)
|
|
618
728
|
|
|
619
729
|
|
|
620
730
|
@run_app.command("existing")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|