pingera-cli 0.1.3__tar.gz → 0.1.4__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.3/pingera_cli.egg-info → pingera_cli-0.1.4}/PKG-INFO +21 -2
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/README.md +21 -2
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/docs/cli-commands.md +65 -15
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/__init__.py +1 -1
- pingera_cli-0.1.4/pingera_cli/commands/check_secrets.py +298 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/commands/checks.py +6 -2
- pingera_cli-0.1.4/pingera_cli/commands/secrets.py +301 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/main.py +2 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4/pingera_cli.egg-info}/PKG-INFO +21 -2
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli.egg-info/SOURCES.txt +2 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/.gitignore +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/.replit +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/LICENSE +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/assert +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/__main__.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/commands/__init__.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/commands/auth.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/commands/base.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/commands/check_groups.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/commands/on_demand_checks.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/formatters/__init__.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/formatters/base_formatter.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/formatters/generic_formatter.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/formatters/multistep_formatter.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/formatters/registry.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/formatters/ssl_formatter.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/formatters/synthetic_formatter.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/formatters/web_formatter.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/utils/__init__.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/utils/config.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli/utils/console.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli.egg-info/dependency_links.txt +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli.egg-info/entry_points.txt +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli.egg-info/requires.txt +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pingera_cli.egg-info/top_level.txt +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/pyproject.toml +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/replit.md +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/run_tests.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/setup.cfg +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/setup.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/tests/__init__.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/tests/conftest.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/tests/requirements.txt +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/tests/test_auth.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/tests/test_checks.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/tests/test_config.py +0 -0
- {pingera_cli-0.1.3 → pingera_cli-0.1.4}/tests/test_on_demand_checks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pingera-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A beautiful Python CLI tool built with typer and rich, distributed via pip and based on Pingera SDK
|
|
5
5
|
Home-page: https://github.com/pingera/pingera-cli
|
|
6
6
|
Author: PingeraCLI Team
|
|
@@ -44,7 +44,6 @@ Dynamic: home-page
|
|
|
44
44
|
Dynamic: license-file
|
|
45
45
|
Dynamic: requires-python
|
|
46
46
|
|
|
47
|
-
|
|
48
47
|
# Pingera Platform CLI 🚀
|
|
49
48
|
|
|
50
49
|
A beautiful Python CLI tool for the [Pingera Platform](https://pingera.ru) - built with typer and rich, distributed via pip and based on Pingera SDK.
|
|
@@ -61,6 +60,7 @@ A beautiful Python CLI tool for the [Pingera Platform](https://pingera.ru) - bui
|
|
|
61
60
|
- **Cross-Platform**: Works on Windows, macOS, and Linux
|
|
62
61
|
- **Easy Installation**: Simple pip install with all dependencies managed
|
|
63
62
|
- **Configuration Management**: Flexible configuration with environment variables and config files
|
|
63
|
+
- **Secrets Management**: Manage organization secrets for monitoring checks
|
|
64
64
|
|
|
65
65
|
## 🚀 Installation
|
|
66
66
|
|
|
@@ -136,6 +136,25 @@ pngr checks groups create \
|
|
|
136
136
|
|
|
137
137
|
# Assign a check to a group
|
|
138
138
|
pngr checks assign-group check_123 --group-id group_456
|
|
139
|
+
|
|
140
|
+
# Remove a check from its group
|
|
141
|
+
pngr checks assign-group check_123 --group-id null
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Secrets Management
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# List organization secrets
|
|
148
|
+
pngr secrets list
|
|
149
|
+
|
|
150
|
+
# Create a new secret
|
|
151
|
+
pngr secrets create "DATABASE_PASSWORD"
|
|
152
|
+
|
|
153
|
+
# Update a secret's value
|
|
154
|
+
pngr secrets update sec123abc456
|
|
155
|
+
|
|
156
|
+
# Delete a secret
|
|
157
|
+
pngr secrets delete sec123abc456
|
|
139
158
|
```
|
|
140
159
|
|
|
141
160
|
### List available regions
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
# Pingera Platform CLI 🚀
|
|
3
2
|
|
|
4
3
|
A beautiful Python CLI tool for the [Pingera Platform](https://pingera.ru) - built with typer and rich, distributed via pip and based on Pingera SDK.
|
|
@@ -15,6 +14,7 @@ A beautiful Python CLI tool for the [Pingera Platform](https://pingera.ru) - bui
|
|
|
15
14
|
- **Cross-Platform**: Works on Windows, macOS, and Linux
|
|
16
15
|
- **Easy Installation**: Simple pip install with all dependencies managed
|
|
17
16
|
- **Configuration Management**: Flexible configuration with environment variables and config files
|
|
17
|
+
- **Secrets Management**: Manage organization secrets for monitoring checks
|
|
18
18
|
|
|
19
19
|
## 🚀 Installation
|
|
20
20
|
|
|
@@ -90,6 +90,25 @@ pngr checks groups create \
|
|
|
90
90
|
|
|
91
91
|
# Assign a check to a group
|
|
92
92
|
pngr checks assign-group check_123 --group-id group_456
|
|
93
|
+
|
|
94
|
+
# Remove a check from its group
|
|
95
|
+
pngr checks assign-group check_123 --group-id null
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Secrets Management
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# List organization secrets
|
|
102
|
+
pngr secrets list
|
|
103
|
+
|
|
104
|
+
# Create a new secret
|
|
105
|
+
pngr secrets create "DATABASE_PASSWORD"
|
|
106
|
+
|
|
107
|
+
# Update a secret's value
|
|
108
|
+
pngr secrets update sec123abc456
|
|
109
|
+
|
|
110
|
+
# Delete a secret
|
|
111
|
+
pngr secrets delete sec123abc456
|
|
93
112
|
```
|
|
94
113
|
|
|
95
114
|
### List available regions
|
|
@@ -165,4 +184,4 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
165
184
|
|
|
166
185
|
- **Issues**: [GitHub Issues](https://github.com/pingera/pingera-cli/issues)
|
|
167
186
|
- **Documentation**: [Pingera CLI Docs](https://docs.pingera.ru/devs/cli)
|
|
168
|
-
- **Platform Support**: [app.pingera.ru](https://app.pingera.ru)
|
|
187
|
+
- **Platform Support**: [app.pingera.ru](https://app.pingera.ru)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
# Pingera CLI Commands Specification
|
|
3
2
|
|
|
4
3
|
## Overview
|
|
@@ -70,23 +69,40 @@ pngr checks jobs get <job-id>
|
|
|
70
69
|
Manage check groups for organizing checks.
|
|
71
70
|
|
|
72
71
|
```bash
|
|
73
|
-
# List check groups
|
|
74
|
-
pngr checks groups list
|
|
72
|
+
# List all check groups
|
|
73
|
+
pngr checks groups list
|
|
75
74
|
|
|
76
|
-
# Get specific group
|
|
77
|
-
pngr checks groups get <
|
|
75
|
+
# Get a specific check group
|
|
76
|
+
pngr checks groups get <group_id>
|
|
78
77
|
|
|
79
|
-
# Create group
|
|
80
|
-
pngr checks groups create
|
|
78
|
+
# Create a new check group
|
|
79
|
+
pngr checks groups create "API Endpoints" --description "All API endpoint checks" --color "#4F46E5"
|
|
81
80
|
|
|
82
|
-
# Update group
|
|
83
|
-
pngr checks groups update <
|
|
81
|
+
# Update a check group
|
|
82
|
+
pngr checks groups update <group_id> --name "Updated Name" --description "Updated description"
|
|
84
83
|
|
|
85
|
-
# Delete group
|
|
86
|
-
pngr checks groups delete <
|
|
84
|
+
# Delete a check group
|
|
85
|
+
pngr checks groups delete <group_id>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `pngr checks secrets`
|
|
89
|
+
Manage secret associations for checks to inject environment variables.
|
|
87
90
|
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
```bash
|
|
92
|
+
# List all secrets associated with a check
|
|
93
|
+
pngr checks secrets list <check_id>
|
|
94
|
+
|
|
95
|
+
# Add a secret to a check with environment variable name
|
|
96
|
+
pngr checks secrets add <check_id> <secret_id> DATABASE_PASSWORD
|
|
97
|
+
|
|
98
|
+
# Remove a secret association from a check
|
|
99
|
+
pngr checks secrets remove <check_id> <secret_id>
|
|
100
|
+
|
|
101
|
+
# Remove a secret without confirmation
|
|
102
|
+
pngr checks secrets remove <check_id> <secret_id> --force
|
|
103
|
+
|
|
104
|
+
# Replace all secret associations for a check
|
|
105
|
+
pngr checks secrets update-all <check_id> --associations '[{"secret_id": "sec123", "env_variable": "DB_PASS"}, {"secret_id": "sec456", "env_variable": "API_KEY"}]'
|
|
90
106
|
```
|
|
91
107
|
|
|
92
108
|
## On-Demand Checks Commands
|
|
@@ -257,6 +273,41 @@ pngr incidents updates update <incident-id> <update-id> --body <text> [--page-id
|
|
|
257
273
|
pngr incidents updates delete <incident-id> <update-id> [--page-id <id>] [--confirm]
|
|
258
274
|
```
|
|
259
275
|
|
|
276
|
+
## Secrets Management
|
|
277
|
+
|
|
278
|
+
Manage organization secrets for use in monitoring checks:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# List all secrets
|
|
282
|
+
pngr secrets list
|
|
283
|
+
|
|
284
|
+
# List secrets with pagination
|
|
285
|
+
pngr secrets list --page 2 --page-size 50
|
|
286
|
+
|
|
287
|
+
# Get a specific secret by ID
|
|
288
|
+
pngr secrets get sec123abc456
|
|
289
|
+
|
|
290
|
+
# Create a new secret (will prompt for value securely)
|
|
291
|
+
pngr secrets create "DATABASE_PASSWORD"
|
|
292
|
+
|
|
293
|
+
# Create a secret with value provided via option
|
|
294
|
+
pngr secrets create "API_KEY" --value "your-secret-value"
|
|
295
|
+
|
|
296
|
+
# Update a secret's value (will prompt for new value securely)
|
|
297
|
+
pngr secrets update sec123abc456
|
|
298
|
+
|
|
299
|
+
# Update a secret with value provided via option
|
|
300
|
+
pngr secrets update sec123abc456 --value "new-secret-value"
|
|
301
|
+
|
|
302
|
+
# Delete a secret (with confirmation)
|
|
303
|
+
pngr secrets delete sec123abc456
|
|
304
|
+
|
|
305
|
+
# Delete a secret without confirmation
|
|
306
|
+
pngr secrets delete sec123abc456 --force
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Note:** Secret values are hidden in list views for security. Only individual secret retrieval shows the actual value (when permissions allow).
|
|
310
|
+
|
|
260
311
|
## Configuration Commands
|
|
261
312
|
|
|
262
313
|
### `pngr config`
|
|
@@ -418,5 +469,4 @@ pngr checks assign-group check_123 --group-id group_456
|
|
|
418
469
|
pngr checks assign-group check_123 --group-id null
|
|
419
470
|
|
|
420
471
|
# Export incidents as JSON
|
|
421
|
-
pngr incidents list --output json > incidents.json
|
|
422
|
-
```
|
|
472
|
+
pngr incidents list --output json > incidents.json
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
Commands for managing check-secret associations
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from typing import Optional, List
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.prompt import Confirm
|
|
13
|
+
|
|
14
|
+
from .base import BaseCommand
|
|
15
|
+
from ..utils.config import get_api_key, get_output_format
|
|
16
|
+
from ..utils.console import console
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CheckSecretsCommand(BaseCommand):
|
|
20
|
+
"""
|
|
21
|
+
Commands for managing check-secret associations
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, output_format: Optional[str] = None):
|
|
25
|
+
super().__init__(output_format)
|
|
26
|
+
|
|
27
|
+
def get_client(self):
|
|
28
|
+
"""Get Pingera SDK client with authentication"""
|
|
29
|
+
api_key = get_api_key()
|
|
30
|
+
if not api_key:
|
|
31
|
+
self.display_error("API key not found. Use 'pngr auth login --api-key <key>' to set it.")
|
|
32
|
+
raise typer.Exit(1)
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
from pingera import ApiClient, Configuration
|
|
36
|
+
from pingera.api import CheckSecretsApi
|
|
37
|
+
|
|
38
|
+
# Configure the client
|
|
39
|
+
configuration = Configuration()
|
|
40
|
+
configuration.host = "https://api.pingera.ru"
|
|
41
|
+
configuration.api_key['apiKeyAuth'] = api_key
|
|
42
|
+
|
|
43
|
+
# Create API client
|
|
44
|
+
api_client = ApiClient(configuration)
|
|
45
|
+
return CheckSecretsApi(api_client)
|
|
46
|
+
except ImportError:
|
|
47
|
+
self.display_error("Pingera SDK not installed. Install with: pip install pingera-sdk")
|
|
48
|
+
raise typer.Exit(1)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
self.display_error(f"Failed to initialize client: {str(e)}")
|
|
51
|
+
raise typer.Exit(1)
|
|
52
|
+
|
|
53
|
+
def list_check_secrets(self, check_id: str):
|
|
54
|
+
"""List all secrets associated with a check"""
|
|
55
|
+
try:
|
|
56
|
+
check_secrets_api = self.get_client()
|
|
57
|
+
|
|
58
|
+
# Make API call
|
|
59
|
+
secrets = check_secrets_api.v1_checks_check_id_secrets_get(check_id)
|
|
60
|
+
|
|
61
|
+
# Handle different output formats
|
|
62
|
+
if self.output_format in ['json', 'yaml']:
|
|
63
|
+
# Convert to dict for JSON/YAML output
|
|
64
|
+
secrets_data = [secret.to_dict() for secret in secrets]
|
|
65
|
+
self.output_data({
|
|
66
|
+
'check_id': check_id,
|
|
67
|
+
'secrets': secrets_data,
|
|
68
|
+
'total': len(secrets_data)
|
|
69
|
+
})
|
|
70
|
+
else:
|
|
71
|
+
# Table format
|
|
72
|
+
if not secrets:
|
|
73
|
+
self.display_info(f"No secrets associated with check {check_id}")
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
table = Table(title=f"Secrets for Check {check_id}")
|
|
77
|
+
table.add_column("Association ID", style="cyan")
|
|
78
|
+
table.add_column("Secret ID", style="yellow")
|
|
79
|
+
table.add_column("Secret Name", style="white")
|
|
80
|
+
table.add_column("Environment Variable", style="green")
|
|
81
|
+
table.add_column("Created At", style="dim")
|
|
82
|
+
|
|
83
|
+
for secret in secrets:
|
|
84
|
+
secret_name = secret.secret.secret_name if hasattr(secret, 'secret') and secret.secret else 'Unknown'
|
|
85
|
+
|
|
86
|
+
table.add_row(
|
|
87
|
+
secret.id or '',
|
|
88
|
+
secret.secret_id or '',
|
|
89
|
+
secret_name,
|
|
90
|
+
secret.env_variable or '',
|
|
91
|
+
str(secret.created_at) if secret.created_at else ''
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
console.print(table)
|
|
95
|
+
console.print(f"\n[dim]Found {len(secrets)} secret associations[/dim]")
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
self.display_error(f"Failed to list check secrets: {str(e)}")
|
|
99
|
+
raise typer.Exit(1)
|
|
100
|
+
|
|
101
|
+
def add_secret_to_check(self, check_id: str, secret_id: str, env_variable: str):
|
|
102
|
+
"""Add a secret to a check with an environment variable name"""
|
|
103
|
+
try:
|
|
104
|
+
check_secrets_api = self.get_client()
|
|
105
|
+
|
|
106
|
+
# Import the CheckSecret model
|
|
107
|
+
from pingera.models import CheckSecret
|
|
108
|
+
|
|
109
|
+
# Create the association
|
|
110
|
+
check_secret = CheckSecret(
|
|
111
|
+
secret_id=secret_id,
|
|
112
|
+
env_variable=env_variable
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Make API call
|
|
116
|
+
created_association = check_secrets_api.v1_checks_check_id_secrets_post(check_id, check_secret)
|
|
117
|
+
|
|
118
|
+
# Handle different output formats
|
|
119
|
+
if self.output_format in ['json', 'yaml']:
|
|
120
|
+
self.output_data(created_association.to_dict())
|
|
121
|
+
else:
|
|
122
|
+
self.display_success(
|
|
123
|
+
f"Secret '{secret_id}' successfully associated with check '{check_id}' as environment variable '{env_variable}'"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Show details table
|
|
127
|
+
table = Table(title="Created Association")
|
|
128
|
+
table.add_column("Property", style="cyan")
|
|
129
|
+
table.add_column("Value", style="white")
|
|
130
|
+
|
|
131
|
+
table.add_row("Association ID", created_association.id or '')
|
|
132
|
+
table.add_row("Check ID", check_id)
|
|
133
|
+
table.add_row("Secret ID", created_association.secret_id or '')
|
|
134
|
+
table.add_row("Environment Variable", created_association.env_variable or '')
|
|
135
|
+
table.add_row("Created At", str(created_association.created_at) if created_association.created_at else '')
|
|
136
|
+
|
|
137
|
+
console.print(table)
|
|
138
|
+
|
|
139
|
+
except Exception as e:
|
|
140
|
+
self.display_error(f"Failed to add secret to check: {str(e)}")
|
|
141
|
+
raise typer.Exit(1)
|
|
142
|
+
|
|
143
|
+
def remove_secret_from_check(self, check_id: str, secret_id: str, force: bool = False):
|
|
144
|
+
"""Remove a secret association from a check"""
|
|
145
|
+
try:
|
|
146
|
+
check_secrets_api = self.get_client()
|
|
147
|
+
|
|
148
|
+
# Confirmation prompt unless force is specified
|
|
149
|
+
if not force:
|
|
150
|
+
if not self.prompt_confirmation(
|
|
151
|
+
f"Are you sure you want to remove secret '{secret_id}' from check '{check_id}'?",
|
|
152
|
+
default=False
|
|
153
|
+
):
|
|
154
|
+
self.display_info("Operation cancelled")
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
# Make API call
|
|
158
|
+
check_secrets_api.v1_checks_check_id_secrets_secret_id_delete(check_id, secret_id)
|
|
159
|
+
|
|
160
|
+
if self.output_format in ['json', 'yaml']:
|
|
161
|
+
self.output_data({
|
|
162
|
+
"message": f"Secret '{secret_id}' removed from check '{check_id}'",
|
|
163
|
+
"check_id": check_id,
|
|
164
|
+
"secret_id": secret_id
|
|
165
|
+
})
|
|
166
|
+
else:
|
|
167
|
+
self.display_success(f"Secret '{secret_id}' removed from check '{check_id}'")
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
self.display_error(f"Failed to remove secret from check: {str(e)}")
|
|
171
|
+
raise typer.Exit(1)
|
|
172
|
+
|
|
173
|
+
def update_all_secrets_for_check(self, check_id: str, associations: List[dict]):
|
|
174
|
+
"""Replace all secret associations for a check"""
|
|
175
|
+
try:
|
|
176
|
+
check_secrets_api = self.get_client()
|
|
177
|
+
|
|
178
|
+
# Import the CheckSecret model
|
|
179
|
+
from pingera.models import CheckSecret
|
|
180
|
+
|
|
181
|
+
# Create CheckSecret objects from the associations
|
|
182
|
+
check_secrets = []
|
|
183
|
+
for assoc in associations:
|
|
184
|
+
check_secret = CheckSecret(
|
|
185
|
+
secret_id=assoc['secret_id'],
|
|
186
|
+
env_variable=assoc['env_variable']
|
|
187
|
+
)
|
|
188
|
+
check_secrets.append(check_secret)
|
|
189
|
+
|
|
190
|
+
# Make API call
|
|
191
|
+
updated_associations = check_secrets_api.v1_checks_check_id_secrets_put(check_id, check_secrets)
|
|
192
|
+
|
|
193
|
+
# Handle different output formats
|
|
194
|
+
if self.output_format in ['json', 'yaml']:
|
|
195
|
+
associations_data = [assoc.to_dict() for assoc in updated_associations]
|
|
196
|
+
self.output_data({
|
|
197
|
+
'check_id': check_id,
|
|
198
|
+
'secrets': associations_data,
|
|
199
|
+
'total': len(associations_data)
|
|
200
|
+
})
|
|
201
|
+
else:
|
|
202
|
+
self.display_success(f"Updated all secret associations for check '{check_id}'")
|
|
203
|
+
|
|
204
|
+
if updated_associations:
|
|
205
|
+
table = Table(title=f"Updated Associations for Check {check_id}")
|
|
206
|
+
table.add_column("Association ID", style="cyan")
|
|
207
|
+
table.add_column("Secret ID", style="yellow")
|
|
208
|
+
table.add_column("Environment Variable", style="green")
|
|
209
|
+
|
|
210
|
+
for assoc in updated_associations:
|
|
211
|
+
table.add_row(
|
|
212
|
+
assoc.id or '',
|
|
213
|
+
assoc.secret_id or '',
|
|
214
|
+
assoc.env_variable or ''
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
console.print(table)
|
|
218
|
+
console.print(f"\n[dim]Total associations: {len(updated_associations)}[/dim]")
|
|
219
|
+
else:
|
|
220
|
+
console.print("[dim]No associations remaining[/dim]")
|
|
221
|
+
|
|
222
|
+
except Exception as e:
|
|
223
|
+
self.display_error(f"Failed to update check secrets: {str(e)}")
|
|
224
|
+
raise typer.Exit(1)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# Create Typer app for check secrets commands
|
|
228
|
+
app = typer.Typer(name="secrets", help="Manage secrets for checks")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@app.command("list")
|
|
232
|
+
def list_secrets(
|
|
233
|
+
check_id: str = typer.Argument(..., help="Check ID to list secrets for"),
|
|
234
|
+
):
|
|
235
|
+
"""List all secrets associated with a check"""
|
|
236
|
+
check_secrets_cmd = CheckSecretsCommand(get_output_format())
|
|
237
|
+
check_secrets_cmd.list_check_secrets(check_id)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@app.command("add")
|
|
241
|
+
def add_secret(
|
|
242
|
+
check_id: str = typer.Argument(..., help="Check ID to add secret to"),
|
|
243
|
+
secret_id: str = typer.Argument(..., help="Secret ID to associate"),
|
|
244
|
+
env_variable: str = typer.Argument(..., help="Environment variable name for the secret"),
|
|
245
|
+
):
|
|
246
|
+
"""Add a secret to a check with an environment variable name"""
|
|
247
|
+
if len(env_variable) > 100:
|
|
248
|
+
console.print("[red]Error:[/red] Environment variable name must be 100 characters or less")
|
|
249
|
+
raise typer.Exit(1)
|
|
250
|
+
|
|
251
|
+
check_secrets_cmd = CheckSecretsCommand(get_output_format())
|
|
252
|
+
check_secrets_cmd.add_secret_to_check(check_id, secret_id, env_variable)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@app.command("remove")
|
|
256
|
+
def remove_secret(
|
|
257
|
+
check_id: str = typer.Argument(..., help="Check ID to remove secret from"),
|
|
258
|
+
secret_id: str = typer.Argument(..., help="Secret ID to remove"),
|
|
259
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt"),
|
|
260
|
+
):
|
|
261
|
+
"""Remove a secret association from a check"""
|
|
262
|
+
check_secrets_cmd = CheckSecretsCommand(get_output_format())
|
|
263
|
+
check_secrets_cmd.remove_secret_from_check(check_id, secret_id, force)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@app.command("update-all")
|
|
267
|
+
def update_all_secrets(
|
|
268
|
+
check_id: str = typer.Argument(..., help="Check ID to update secrets for"),
|
|
269
|
+
associations_json: str = typer.Option(..., "--associations", help="JSON array of secret associations: '[{\"secret_id\": \"sec123\", \"env_variable\": \"VAR_NAME\"}]'"),
|
|
270
|
+
):
|
|
271
|
+
"""Replace all secret associations for a check"""
|
|
272
|
+
import json
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
associations = json.loads(associations_json)
|
|
276
|
+
if not isinstance(associations, list):
|
|
277
|
+
console.print("[red]Error:[/red] Associations must be a JSON array")
|
|
278
|
+
raise typer.Exit(1)
|
|
279
|
+
|
|
280
|
+
# Validate each association
|
|
281
|
+
for i, assoc in enumerate(associations):
|
|
282
|
+
if not isinstance(assoc, dict):
|
|
283
|
+
console.print(f"[red]Error:[/red] Association {i+1} must be an object")
|
|
284
|
+
raise typer.Exit(1)
|
|
285
|
+
if 'secret_id' not in assoc or 'env_variable' not in assoc:
|
|
286
|
+
console.print(f"[red]Error:[/red] Association {i+1} must have 'secret_id' and 'env_variable' fields")
|
|
287
|
+
raise typer.Exit(1)
|
|
288
|
+
if len(assoc['env_variable']) > 100:
|
|
289
|
+
console.print(f"[red]Error:[/red] Environment variable name in association {i+1} must be 100 characters or less")
|
|
290
|
+
raise typer.Exit(1)
|
|
291
|
+
|
|
292
|
+
except json.JSONDecodeError as e:
|
|
293
|
+
console.print(f"[red]Error:[/red] Invalid JSON: {str(e)}")
|
|
294
|
+
console.print("[dim]Example: '[{\"secret_id\": \"sec123abc456\", \"env_variable\": \"DATABASE_PASSWORD\"}]'[/dim]")
|
|
295
|
+
raise typer.Exit(1)
|
|
296
|
+
|
|
297
|
+
check_secrets_cmd = CheckSecretsCommand(get_output_format())
|
|
298
|
+
check_secrets_cmd.update_all_secrets_for_check(check_id, associations)
|
|
@@ -1183,7 +1183,11 @@ from .on_demand_checks import run_app, jobs_app
|
|
|
1183
1183
|
# Import check groups functionality
|
|
1184
1184
|
from .check_groups import app as groups_app
|
|
1185
1185
|
|
|
1186
|
-
#
|
|
1186
|
+
# Import check secrets functionality
|
|
1187
|
+
from .check_secrets import app as check_secrets_app
|
|
1188
|
+
|
|
1189
|
+
# Add the run, jobs, groups, and secrets subcommands to the main checks app
|
|
1187
1190
|
app.add_typer(run_app, name="run")
|
|
1188
1191
|
app.add_typer(jobs_app, name="jobs")
|
|
1189
|
-
app.add_typer(groups_app, name="groups")
|
|
1192
|
+
app.add_typer(groups_app, name="groups")
|
|
1193
|
+
app.add_typer(check_secrets_app, name="secrets")
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
Commands for managing organization secrets
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
from .base import BaseCommand
|
|
14
|
+
from ..utils.config import get_api_key, get_output_format
|
|
15
|
+
from ..utils.console import console
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SecretsCommand(BaseCommand):
|
|
19
|
+
"""
|
|
20
|
+
Commands for managing organization secrets
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, output_format: Optional[str] = None):
|
|
24
|
+
super().__init__(output_format)
|
|
25
|
+
|
|
26
|
+
def get_client(self):
|
|
27
|
+
"""Get Pingera SDK client with authentication"""
|
|
28
|
+
api_key = get_api_key()
|
|
29
|
+
if not api_key:
|
|
30
|
+
self.display_error("API key not found. Use 'pngr auth login --api-key <key>' to set it.")
|
|
31
|
+
raise typer.Exit(1)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
from pingera import ApiClient, Configuration
|
|
35
|
+
from pingera.api import SecretsApi
|
|
36
|
+
|
|
37
|
+
# Configure the client
|
|
38
|
+
configuration = Configuration()
|
|
39
|
+
configuration.host = "https://api.pingera.ru"
|
|
40
|
+
configuration.api_key['apiKeyAuth'] = api_key
|
|
41
|
+
|
|
42
|
+
# Create API client
|
|
43
|
+
api_client = ApiClient(configuration)
|
|
44
|
+
return SecretsApi(api_client)
|
|
45
|
+
except ImportError:
|
|
46
|
+
self.display_error("Pingera SDK not installed. Install with: pip install pingera-sdk")
|
|
47
|
+
raise typer.Exit(1)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
self.display_error(f"Failed to initialize client: {str(e)}")
|
|
50
|
+
raise typer.Exit(1)
|
|
51
|
+
|
|
52
|
+
def list_secrets(self, page: int = 1, page_size: int = 20):
|
|
53
|
+
"""List organization secrets"""
|
|
54
|
+
try:
|
|
55
|
+
secrets_api = self.get_client()
|
|
56
|
+
|
|
57
|
+
# Make API call
|
|
58
|
+
response = secrets_api.v1_secrets_get(page=page, page_size=page_size)
|
|
59
|
+
|
|
60
|
+
# Handle different output formats
|
|
61
|
+
if self.output_format in ['json', 'yaml']:
|
|
62
|
+
# Convert to dict for JSON/YAML output
|
|
63
|
+
secrets_data = {
|
|
64
|
+
'secrets': [secret.to_dict() for secret in response.items],
|
|
65
|
+
'pagination': {
|
|
66
|
+
'page': response.page,
|
|
67
|
+
'page_size': response.page_size,
|
|
68
|
+
'total': response.total,
|
|
69
|
+
'total_pages': response.total_pages
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
self.output_data(secrets_data)
|
|
73
|
+
else:
|
|
74
|
+
# Table format
|
|
75
|
+
if not response.items:
|
|
76
|
+
self.display_info("No secrets found")
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
table = Table(title=f"Organization Secrets (Page {response.page} of {response.total_pages})")
|
|
80
|
+
table.add_column("ID", style="cyan")
|
|
81
|
+
table.add_column("Name", style="white")
|
|
82
|
+
table.add_column("Created At", style="dim")
|
|
83
|
+
table.add_column("Updated At", style="dim")
|
|
84
|
+
table.add_column("Created By", style="dim")
|
|
85
|
+
|
|
86
|
+
for secret in response.items:
|
|
87
|
+
table.add_row(
|
|
88
|
+
secret.id or '',
|
|
89
|
+
secret.secret_name or '',
|
|
90
|
+
str(secret.created_at) if secret.created_at else '',
|
|
91
|
+
str(secret.updated_at) if secret.updated_at else '',
|
|
92
|
+
secret.created_by or ''
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
console.print(table)
|
|
96
|
+
|
|
97
|
+
# Show pagination info
|
|
98
|
+
if response.total_pages > 1:
|
|
99
|
+
console.print(f"\n[dim]Showing {len(response.items)} of {response.total} secrets[/dim]")
|
|
100
|
+
|
|
101
|
+
except Exception as e:
|
|
102
|
+
self.display_error(f"Failed to list secrets: {str(e)}")
|
|
103
|
+
raise typer.Exit(1)
|
|
104
|
+
|
|
105
|
+
def get_secret(self, secret_id: str):
|
|
106
|
+
"""Get a specific secret by ID"""
|
|
107
|
+
try:
|
|
108
|
+
secrets_api = self.get_client()
|
|
109
|
+
|
|
110
|
+
# Make API call
|
|
111
|
+
secret = secrets_api.v1_secrets_secret_id_get(secret_id)
|
|
112
|
+
|
|
113
|
+
# Handle different output formats
|
|
114
|
+
if self.output_format in ['json', 'yaml']:
|
|
115
|
+
self.output_data(secret.to_dict())
|
|
116
|
+
else:
|
|
117
|
+
# Table format
|
|
118
|
+
table = Table(title=f"Secret Details: {secret.secret_name}")
|
|
119
|
+
table.add_column("Property", style="cyan")
|
|
120
|
+
table.add_column("Value", style="white")
|
|
121
|
+
|
|
122
|
+
table.add_row("ID", secret.id or '')
|
|
123
|
+
table.add_row("Name", secret.secret_name or '')
|
|
124
|
+
|
|
125
|
+
# Show secret value if available (marked as sensitive)
|
|
126
|
+
if hasattr(secret, 'secret_value') and secret.secret_value:
|
|
127
|
+
table.add_row("Value", f"[red]{'*' * 20}[/red] (hidden for security)")
|
|
128
|
+
|
|
129
|
+
table.add_row("Created At", str(secret.created_at) if secret.created_at else '')
|
|
130
|
+
table.add_row("Updated At", str(secret.updated_at) if secret.updated_at else '')
|
|
131
|
+
table.add_row("Created By", secret.created_by or '')
|
|
132
|
+
table.add_row("Updated By", secret.updated_by or '')
|
|
133
|
+
|
|
134
|
+
console.print(table)
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
self.display_error(f"Failed to get secret: {str(e)}")
|
|
138
|
+
raise typer.Exit(1)
|
|
139
|
+
|
|
140
|
+
def create_secret(self, name: str, value: str):
|
|
141
|
+
"""Create a new secret"""
|
|
142
|
+
try:
|
|
143
|
+
secrets_api = self.get_client()
|
|
144
|
+
|
|
145
|
+
# Import the Secret model
|
|
146
|
+
from pingera.models import Secret1
|
|
147
|
+
|
|
148
|
+
# Create secret object
|
|
149
|
+
secret_data = Secret1(
|
|
150
|
+
secret_name=name,
|
|
151
|
+
secret_value=value
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Make API call
|
|
155
|
+
created_secret = secrets_api.v1_secrets_post(secret_data)
|
|
156
|
+
|
|
157
|
+
# Handle different output formats
|
|
158
|
+
if self.output_format in ['json', 'yaml']:
|
|
159
|
+
self.output_data(created_secret.to_dict())
|
|
160
|
+
else:
|
|
161
|
+
self.display_success(f"Secret '{name}' created successfully with ID: {created_secret.id}")
|
|
162
|
+
|
|
163
|
+
# Show details table
|
|
164
|
+
table = Table(title="Created Secret")
|
|
165
|
+
table.add_column("Property", style="cyan")
|
|
166
|
+
table.add_column("Value", style="white")
|
|
167
|
+
|
|
168
|
+
table.add_row("ID", created_secret.id or '')
|
|
169
|
+
table.add_row("Name", created_secret.secret_name or '')
|
|
170
|
+
table.add_row("Created At", str(created_secret.created_at) if created_secret.created_at else '')
|
|
171
|
+
|
|
172
|
+
console.print(table)
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
self.display_error(f"Failed to create secret: {str(e)}")
|
|
176
|
+
raise typer.Exit(1)
|
|
177
|
+
|
|
178
|
+
def update_secret(self, secret_id: str, value: str):
|
|
179
|
+
"""Update an existing secret"""
|
|
180
|
+
try:
|
|
181
|
+
secrets_api = self.get_client()
|
|
182
|
+
|
|
183
|
+
# Import the Secret model for update
|
|
184
|
+
from pingera.models import Secret2
|
|
185
|
+
|
|
186
|
+
# Create update object (only value can be updated)
|
|
187
|
+
secret_update = Secret2(secret_value=value)
|
|
188
|
+
|
|
189
|
+
# Make API call
|
|
190
|
+
updated_secret = secrets_api.v1_secrets_secret_id_patch(secret_id, secret_update)
|
|
191
|
+
|
|
192
|
+
# Handle different output formats
|
|
193
|
+
if self.output_format in ['json', 'yaml']:
|
|
194
|
+
self.output_data(updated_secret.to_dict())
|
|
195
|
+
else:
|
|
196
|
+
self.display_success(f"Secret '{updated_secret.secret_name}' updated successfully")
|
|
197
|
+
|
|
198
|
+
# Show details table
|
|
199
|
+
table = Table(title="Updated Secret")
|
|
200
|
+
table.add_column("Property", style="cyan")
|
|
201
|
+
table.add_column("Value", style="white")
|
|
202
|
+
|
|
203
|
+
table.add_row("ID", updated_secret.id or '')
|
|
204
|
+
table.add_row("Name", updated_secret.secret_name or '')
|
|
205
|
+
table.add_row("Updated At", str(updated_secret.updated_at) if updated_secret.updated_at else '')
|
|
206
|
+
|
|
207
|
+
console.print(table)
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
self.display_error(f"Failed to update secret: {str(e)}")
|
|
211
|
+
raise typer.Exit(1)
|
|
212
|
+
|
|
213
|
+
def delete_secret(self, secret_id: str, force: bool = False):
|
|
214
|
+
"""Delete a secret"""
|
|
215
|
+
try:
|
|
216
|
+
secrets_api = self.get_client()
|
|
217
|
+
|
|
218
|
+
# Get secret details first for confirmation
|
|
219
|
+
try:
|
|
220
|
+
secret = secrets_api.v1_secrets_secret_id_get(secret_id)
|
|
221
|
+
secret_name = secret.secret_name
|
|
222
|
+
except Exception:
|
|
223
|
+
secret_name = secret_id
|
|
224
|
+
|
|
225
|
+
# Confirmation prompt unless force is specified
|
|
226
|
+
if not force:
|
|
227
|
+
if not self.prompt_confirmation(
|
|
228
|
+
f"Are you sure you want to delete secret '{secret_name}'? This action cannot be undone.",
|
|
229
|
+
default=False
|
|
230
|
+
):
|
|
231
|
+
self.display_info("Operation cancelled")
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
# Make API call
|
|
235
|
+
secrets_api.v1_secrets_secret_id_delete(secret_id)
|
|
236
|
+
|
|
237
|
+
if self.output_format in ['json', 'yaml']:
|
|
238
|
+
self.output_data({"message": f"Secret '{secret_name}' deleted successfully", "secret_id": secret_id})
|
|
239
|
+
else:
|
|
240
|
+
self.display_success(f"Secret '{secret_name}' deleted successfully")
|
|
241
|
+
|
|
242
|
+
except Exception as e:
|
|
243
|
+
self.display_error(f"Failed to delete secret: {str(e)}")
|
|
244
|
+
raise typer.Exit(1)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# Create Typer app for secrets commands
|
|
248
|
+
app = typer.Typer(name="secrets", help="Manage organization secrets")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@app.command("list")
|
|
252
|
+
def list_secrets(
|
|
253
|
+
page: int = typer.Option(1, "--page", "-p", help="Page number (default: 1)"),
|
|
254
|
+
page_size: int = typer.Option(20, "--page-size", "-s", help="Number of items per page (default: 20, max: 100)"),
|
|
255
|
+
):
|
|
256
|
+
"""List all organization secrets"""
|
|
257
|
+
secrets_cmd = SecretsCommand(get_output_format())
|
|
258
|
+
secrets_cmd.list_secrets(page, page_size)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@app.command("get")
|
|
262
|
+
def get_secret(
|
|
263
|
+
secret_id: str = typer.Argument(..., help="Secret ID to retrieve"),
|
|
264
|
+
):
|
|
265
|
+
"""Get a specific secret by ID"""
|
|
266
|
+
secrets_cmd = SecretsCommand(get_output_format())
|
|
267
|
+
secrets_cmd.get_secret(secret_id)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@app.command("create")
|
|
271
|
+
def create_secret(
|
|
272
|
+
name: str = typer.Argument(..., help="Secret name (max 100 characters)"),
|
|
273
|
+
value: str = typer.Option(..., "--value", "-v", help="Secret value", prompt=True, hide_input=True),
|
|
274
|
+
):
|
|
275
|
+
"""Create a new secret"""
|
|
276
|
+
if len(name) > 100:
|
|
277
|
+
console.print("[red]Error:[/red] Secret name must be 100 characters or less")
|
|
278
|
+
raise typer.Exit(1)
|
|
279
|
+
|
|
280
|
+
secrets_cmd = SecretsCommand(get_output_format())
|
|
281
|
+
secrets_cmd.create_secret(name, value)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@app.command("update")
|
|
285
|
+
def update_secret(
|
|
286
|
+
secret_id: str = typer.Argument(..., help="Secret ID to update"),
|
|
287
|
+
value: str = typer.Option(..., "--value", "-v", help="New secret value", prompt=True, hide_input=True),
|
|
288
|
+
):
|
|
289
|
+
"""Update an existing secret (only the value can be updated)"""
|
|
290
|
+
secrets_cmd = SecretsCommand(get_output_format())
|
|
291
|
+
secrets_cmd.update_secret(secret_id, value)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@app.command("delete")
|
|
295
|
+
def delete_secret(
|
|
296
|
+
secret_id: str = typer.Argument(..., help="Secret ID to delete"),
|
|
297
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt"),
|
|
298
|
+
):
|
|
299
|
+
"""Delete a secret (this action cannot be undone)"""
|
|
300
|
+
secrets_cmd = SecretsCommand(get_output_format())
|
|
301
|
+
secrets_cmd.delete_secret(secret_id, force)
|
|
@@ -15,6 +15,7 @@ from . import __version__
|
|
|
15
15
|
from .commands.base import BaseCommand
|
|
16
16
|
from .commands.auth import auth_cmd
|
|
17
17
|
from .commands.checks import app as checks_app
|
|
18
|
+
from .commands.secrets import app as secrets_app
|
|
18
19
|
from .utils.console import console, error_console
|
|
19
20
|
from .utils.config import get_config
|
|
20
21
|
|
|
@@ -33,6 +34,7 @@ base_cmd = BaseCommand()
|
|
|
33
34
|
# Add subcommands
|
|
34
35
|
app.add_typer(auth_cmd.app, name="auth")
|
|
35
36
|
app.add_typer(checks_app, name="checks")
|
|
37
|
+
app.add_typer(secrets_app, name="secrets")
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
@app.command("version")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pingera-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A beautiful Python CLI tool built with typer and rich, distributed via pip and based on Pingera SDK
|
|
5
5
|
Home-page: https://github.com/pingera/pingera-cli
|
|
6
6
|
Author: PingeraCLI Team
|
|
@@ -44,7 +44,6 @@ Dynamic: home-page
|
|
|
44
44
|
Dynamic: license-file
|
|
45
45
|
Dynamic: requires-python
|
|
46
46
|
|
|
47
|
-
|
|
48
47
|
# Pingera Platform CLI 🚀
|
|
49
48
|
|
|
50
49
|
A beautiful Python CLI tool for the [Pingera Platform](https://pingera.ru) - built with typer and rich, distributed via pip and based on Pingera SDK.
|
|
@@ -61,6 +60,7 @@ A beautiful Python CLI tool for the [Pingera Platform](https://pingera.ru) - bui
|
|
|
61
60
|
- **Cross-Platform**: Works on Windows, macOS, and Linux
|
|
62
61
|
- **Easy Installation**: Simple pip install with all dependencies managed
|
|
63
62
|
- **Configuration Management**: Flexible configuration with environment variables and config files
|
|
63
|
+
- **Secrets Management**: Manage organization secrets for monitoring checks
|
|
64
64
|
|
|
65
65
|
## 🚀 Installation
|
|
66
66
|
|
|
@@ -136,6 +136,25 @@ pngr checks groups create \
|
|
|
136
136
|
|
|
137
137
|
# Assign a check to a group
|
|
138
138
|
pngr checks assign-group check_123 --group-id group_456
|
|
139
|
+
|
|
140
|
+
# Remove a check from its group
|
|
141
|
+
pngr checks assign-group check_123 --group-id null
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Secrets Management
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# List organization secrets
|
|
148
|
+
pngr secrets list
|
|
149
|
+
|
|
150
|
+
# Create a new secret
|
|
151
|
+
pngr secrets create "DATABASE_PASSWORD"
|
|
152
|
+
|
|
153
|
+
# Update a secret's value
|
|
154
|
+
pngr secrets update sec123abc456
|
|
155
|
+
|
|
156
|
+
# Delete a secret
|
|
157
|
+
pngr secrets delete sec123abc456
|
|
139
158
|
```
|
|
140
159
|
|
|
141
160
|
### List available regions
|
|
@@ -21,8 +21,10 @@ pingera_cli/commands/__init__.py
|
|
|
21
21
|
pingera_cli/commands/auth.py
|
|
22
22
|
pingera_cli/commands/base.py
|
|
23
23
|
pingera_cli/commands/check_groups.py
|
|
24
|
+
pingera_cli/commands/check_secrets.py
|
|
24
25
|
pingera_cli/commands/checks.py
|
|
25
26
|
pingera_cli/commands/on_demand_checks.py
|
|
27
|
+
pingera_cli/commands/secrets.py
|
|
26
28
|
pingera_cli/formatters/__init__.py
|
|
27
29
|
pingera_cli/formatters/base_formatter.py
|
|
28
30
|
pingera_cli/formatters/generic_formatter.py
|
|
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
|