dmtri 0.1.0__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.
- dmtri-0.1.0/PKG-INFO +174 -0
- dmtri-0.1.0/README.md +159 -0
- dmtri-0.1.0/pyproject.toml +28 -0
- dmtri-0.1.0/setup.cfg +4 -0
- dmtri-0.1.0/src/dmtri/__init__.py +0 -0
- dmtri-0.1.0/src/dmtri/cli.py +118 -0
- dmtri-0.1.0/src/dmtri/doctor.py +52 -0
- dmtri-0.1.0/src/dmtri/execution_plan.py +59 -0
- dmtri-0.1.0/src/dmtri/hooks/__init__.py +0 -0
- dmtri-0.1.0/src/dmtri/hooks/hooks.py +45 -0
- dmtri-0.1.0/src/dmtri/paths.py +49 -0
- dmtri-0.1.0/src/dmtri/utils.py +85 -0
- dmtri-0.1.0/src/dmtri.egg-info/PKG-INFO +174 -0
- dmtri-0.1.0/src/dmtri.egg-info/SOURCES.txt +21 -0
- dmtri-0.1.0/src/dmtri.egg-info/dependency_links.txt +1 -0
- dmtri-0.1.0/src/dmtri.egg-info/entry_points.txt +2 -0
- dmtri-0.1.0/src/dmtri.egg-info/requires.txt +7 -0
- dmtri-0.1.0/src/dmtri.egg-info/top_level.txt +3 -0
- dmtri-0.1.0/tests/test_cli.py +175 -0
- dmtri-0.1.0/tests/test_doctor.py +53 -0
- dmtri-0.1.0/tests/test_execution_plan.py +82 -0
- dmtri-0.1.0/tests/test_hooks.py +60 -0
- dmtri-0.1.0/tests/test_utils.py +116 -0
dmtri-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dmtri
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool for triggering datacenter metadata updates
|
|
5
|
+
Author-email: Nikos Sokos <nsokos@noa.com>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: ansible>=6.7.0
|
|
9
|
+
Requires-Dist: ansible-runner>=2.3.6
|
|
10
|
+
Requires-Dist: appdirs>=1.4.4
|
|
11
|
+
Requires-Dist: pytest>=8.3.5
|
|
12
|
+
Requires-Dist: pytest-cov>=5.0.0
|
|
13
|
+
Requires-Dist: requests
|
|
14
|
+
Requires-Dist: tomli>=2.2.1
|
|
15
|
+
|
|
16
|
+
# dmtri
|
|
17
|
+
|
|
18
|
+
[](https://github.com/EIDA/dmtri/actions/workflows/pytest.yml)
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
`dmtri` is a command-line tool designed to trigger metadata and data refresh simulations across EIDA nodes using Ansible automation.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
- `seedpsd`
|
|
25
|
+
- `wfcatalog`
|
|
26
|
+
- `availability`
|
|
27
|
+
|
|
28
|
+
It uses `.dmtri_jobs.json` per host to track async job state and integrates with `uv` and `ansible` for CLI automation.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
You can use `dmtri` in two ways, depending on your preference:
|
|
35
|
+
|
|
36
|
+
### Option 1: Using `uvx`
|
|
37
|
+
|
|
38
|
+
You can run commands using:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uvx dmtri <command> [options]
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### Option 2: Global (Tool-based) — Ideal for global CLI use
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv tool install dmtri
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This makes `dmtri` available globally:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
dmtri <command> [options]
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
You can update it any time using:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv tool upgrade dmtri
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### 2. Configure inventory
|
|
69
|
+
|
|
70
|
+
Edit the file: `src/inventory/hosts.ini`
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
|
|
74
|
+
```ini
|
|
75
|
+
[seedpsd_nodes]
|
|
76
|
+
<SEEDPSD_HOST> ansible_user=<USER> ansible_ssh_pass="<PASSWORD>" ansible_connection=ssh ansible_become=false ansible_ssh_common_args='-o StrictHostKeyChecking=no'
|
|
77
|
+
|
|
78
|
+
[seedpsd_nodes:vars]
|
|
79
|
+
seedpsd_dir=/home/<USER>/seedpsd
|
|
80
|
+
|
|
81
|
+
[wf_catalogue]
|
|
82
|
+
<WFCATALOG_HOST> ansible_user=<USER> ansible_ssh_pass="<PASSWORD>" ansible_connection=ssh ansible_become=false ansible_ssh_common_args='-o StrictHostKeyChecking=no'
|
|
83
|
+
|
|
84
|
+
[wf_catalogue:vars]
|
|
85
|
+
collector_dir=/home/<USER>/Programs/wfcatalogue<VERSION>/wfcatalog/collector2
|
|
86
|
+
|
|
87
|
+
[ws_availability]
|
|
88
|
+
<AVAILABILITY_HOST> ansible_user=<USER> ansible_ssh_pass="<PASSWORD>" ansible_connection=ssh ansible_become=false ansible_ssh_common_args='-o StrictHostKeyChecking=no'
|
|
89
|
+
|
|
90
|
+
[ws_availability:vars]
|
|
91
|
+
availability_dir=/home/<USER>/Programs/ws-availability<VERSION>/views
|
|
92
|
+
mongo_user=<MONGO_USERNAME>
|
|
93
|
+
mongo_pass=<MONGO_PASSWORD>
|
|
94
|
+
mongo_authdb=<MONGO_DATABASE>
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### 3. Run example jobs
|
|
101
|
+
|
|
102
|
+
By default, this command runs **all refresh playbooks** (seedpsd, wfcatalog, and availability):
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
uv run dmtri refresh -e "network=HL station=ATH starttime=2024-01-01T00:00:00 endtime=2024-01-02T00:00:00"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### 4. Track jobs across hosts
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
uv run dmtri track
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Shows per-host job status:
|
|
117
|
+
```
|
|
118
|
+
Job ID: j123... Type: seedpsd Station: ATH Status: ✅ Finished
|
|
119
|
+
Job ID: manual-... Type: availability Status: ✅ Manual (not tracked)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### 5. Troubleshooting with `dmtri doctor`
|
|
125
|
+
|
|
126
|
+
Check SSH connectivity to your configured hosts using:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
uv run dmtri doctor
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This uses the Ansible `ping` module to test access to all servers in your inventory.
|
|
133
|
+
|
|
134
|
+
#### Options
|
|
135
|
+
|
|
136
|
+
- `--verbose` — Show full output from each host
|
|
137
|
+
|
|
138
|
+
#### Example
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
uv run dmtri doctor --verbose
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
If no response is received, ensure:
|
|
145
|
+
|
|
146
|
+
- You're connected to the VPN (if applicable)
|
|
147
|
+
- SSH access is configured correctly in your inventory file
|
|
148
|
+
- Host IPs and credentials are reachable and valid
|
|
149
|
+
|
|
150
|
+
### Operation Details
|
|
151
|
+
|
|
152
|
+
`dmtri` supports two main operations for both metadata and data: `refresh` and `clean`. These affect the following components:
|
|
153
|
+
|
|
154
|
+
#### 🔄 Refresh
|
|
155
|
+
|
|
156
|
+
- **Metadata**
|
|
157
|
+
- `seedpsd`: Scans for new metadata.
|
|
158
|
+
- `availability`: Refreshes the view in MongoDB.
|
|
159
|
+
|
|
160
|
+
- **Data**
|
|
161
|
+
- Identifies data files in the archive for the specified epoch (by NSLC and time range).
|
|
162
|
+
- `seedpsd`: Recalculates seedPSD for new files.
|
|
163
|
+
- `wfcatalog`: Runs the collector on new data files.
|
|
164
|
+
|
|
165
|
+
#### 🧹 Clean
|
|
166
|
+
|
|
167
|
+
- **Metadata**
|
|
168
|
+
- `seedpsd`: Removes the metadata entries.
|
|
169
|
+
- `availability`: Refreshes the view in MongoDB.
|
|
170
|
+
|
|
171
|
+
- **Data**
|
|
172
|
+
- Identifies data files in the archive for the specified epoch.
|
|
173
|
+
- `seedpsd`: Removes associated seedPSD data (via CLI).
|
|
174
|
+
- `wfcatalog`: Removes files using the `--delete` option in the collector.
|
dmtri-0.1.0/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# dmtri
|
|
2
|
+
|
|
3
|
+
[](https://github.com/EIDA/dmtri/actions/workflows/pytest.yml)
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
`dmtri` is a command-line tool designed to trigger metadata and data refresh simulations across EIDA nodes using Ansible automation.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
- `seedpsd`
|
|
10
|
+
- `wfcatalog`
|
|
11
|
+
- `availability`
|
|
12
|
+
|
|
13
|
+
It uses `.dmtri_jobs.json` per host to track async job state and integrates with `uv` and `ansible` for CLI automation.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
You can use `dmtri` in two ways, depending on your preference:
|
|
20
|
+
|
|
21
|
+
### Option 1: Using `uvx`
|
|
22
|
+
|
|
23
|
+
You can run commands using:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uvx dmtri <command> [options]
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
### Option 2: Global (Tool-based) — Ideal for global CLI use
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv tool install dmtri
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This makes `dmtri` available globally:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
dmtri <command> [options]
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
You can update it any time using:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv tool upgrade dmtri
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
### 2. Configure inventory
|
|
54
|
+
|
|
55
|
+
Edit the file: `src/inventory/hosts.ini`
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
|
|
59
|
+
```ini
|
|
60
|
+
[seedpsd_nodes]
|
|
61
|
+
<SEEDPSD_HOST> ansible_user=<USER> ansible_ssh_pass="<PASSWORD>" ansible_connection=ssh ansible_become=false ansible_ssh_common_args='-o StrictHostKeyChecking=no'
|
|
62
|
+
|
|
63
|
+
[seedpsd_nodes:vars]
|
|
64
|
+
seedpsd_dir=/home/<USER>/seedpsd
|
|
65
|
+
|
|
66
|
+
[wf_catalogue]
|
|
67
|
+
<WFCATALOG_HOST> ansible_user=<USER> ansible_ssh_pass="<PASSWORD>" ansible_connection=ssh ansible_become=false ansible_ssh_common_args='-o StrictHostKeyChecking=no'
|
|
68
|
+
|
|
69
|
+
[wf_catalogue:vars]
|
|
70
|
+
collector_dir=/home/<USER>/Programs/wfcatalogue<VERSION>/wfcatalog/collector2
|
|
71
|
+
|
|
72
|
+
[ws_availability]
|
|
73
|
+
<AVAILABILITY_HOST> ansible_user=<USER> ansible_ssh_pass="<PASSWORD>" ansible_connection=ssh ansible_become=false ansible_ssh_common_args='-o StrictHostKeyChecking=no'
|
|
74
|
+
|
|
75
|
+
[ws_availability:vars]
|
|
76
|
+
availability_dir=/home/<USER>/Programs/ws-availability<VERSION>/views
|
|
77
|
+
mongo_user=<MONGO_USERNAME>
|
|
78
|
+
mongo_pass=<MONGO_PASSWORD>
|
|
79
|
+
mongo_authdb=<MONGO_DATABASE>
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### 3. Run example jobs
|
|
86
|
+
|
|
87
|
+
By default, this command runs **all refresh playbooks** (seedpsd, wfcatalog, and availability):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
uv run dmtri refresh -e "network=HL station=ATH starttime=2024-01-01T00:00:00 endtime=2024-01-02T00:00:00"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### 4. Track jobs across hosts
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
uv run dmtri track
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Shows per-host job status:
|
|
102
|
+
```
|
|
103
|
+
Job ID: j123... Type: seedpsd Station: ATH Status: ✅ Finished
|
|
104
|
+
Job ID: manual-... Type: availability Status: ✅ Manual (not tracked)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### 5. Troubleshooting with `dmtri doctor`
|
|
110
|
+
|
|
111
|
+
Check SSH connectivity to your configured hosts using:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uv run dmtri doctor
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This uses the Ansible `ping` module to test access to all servers in your inventory.
|
|
118
|
+
|
|
119
|
+
#### Options
|
|
120
|
+
|
|
121
|
+
- `--verbose` — Show full output from each host
|
|
122
|
+
|
|
123
|
+
#### Example
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
uv run dmtri doctor --verbose
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
If no response is received, ensure:
|
|
130
|
+
|
|
131
|
+
- You're connected to the VPN (if applicable)
|
|
132
|
+
- SSH access is configured correctly in your inventory file
|
|
133
|
+
- Host IPs and credentials are reachable and valid
|
|
134
|
+
|
|
135
|
+
### Operation Details
|
|
136
|
+
|
|
137
|
+
`dmtri` supports two main operations for both metadata and data: `refresh` and `clean`. These affect the following components:
|
|
138
|
+
|
|
139
|
+
#### 🔄 Refresh
|
|
140
|
+
|
|
141
|
+
- **Metadata**
|
|
142
|
+
- `seedpsd`: Scans for new metadata.
|
|
143
|
+
- `availability`: Refreshes the view in MongoDB.
|
|
144
|
+
|
|
145
|
+
- **Data**
|
|
146
|
+
- Identifies data files in the archive for the specified epoch (by NSLC and time range).
|
|
147
|
+
- `seedpsd`: Recalculates seedPSD for new files.
|
|
148
|
+
- `wfcatalog`: Runs the collector on new data files.
|
|
149
|
+
|
|
150
|
+
#### 🧹 Clean
|
|
151
|
+
|
|
152
|
+
- **Metadata**
|
|
153
|
+
- `seedpsd`: Removes the metadata entries.
|
|
154
|
+
- `availability`: Refreshes the view in MongoDB.
|
|
155
|
+
|
|
156
|
+
- **Data**
|
|
157
|
+
- Identifies data files in the archive for the specified epoch.
|
|
158
|
+
- `seedpsd`: Removes associated seedPSD data (via CLI).
|
|
159
|
+
- `wfcatalog`: Removes files using the `--delete` option in the collector.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dmtri"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "CLI tool for triggering datacenter metadata updates"
|
|
5
|
+
authors = [{ name = "Nikos Sokos", email = "nsokos@noa.com" }]
|
|
6
|
+
dependencies = [
|
|
7
|
+
"ansible>=6.7.0",
|
|
8
|
+
"ansible-runner>=2.3.6",
|
|
9
|
+
"appdirs>=1.4.4",
|
|
10
|
+
"pytest>=8.3.5",
|
|
11
|
+
"pytest-cov>=5.0.0",
|
|
12
|
+
"requests",
|
|
13
|
+
"tomli>=2.2.1",
|
|
14
|
+
]
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
requires-python = ">=3.8"
|
|
17
|
+
[tool.uv]
|
|
18
|
+
dev-dependencies = [
|
|
19
|
+
"pytest",
|
|
20
|
+
"pytest-cov"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
dmtri = "dmtri.cli:main"
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["setuptools>=62.0", "wheel"]
|
|
28
|
+
build-backend = "setuptools.build_meta"
|
dmtri-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from dmtri.execution_plan import print_execution_plan
|
|
8
|
+
from dmtri.utils import get_version, validate_and_normalize_args
|
|
9
|
+
from dmtri.hooks.hooks import run_hook
|
|
10
|
+
from dmtri.paths import COMMAND_PLAYBOOKS
|
|
11
|
+
|
|
12
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
PROJECT_URL = "https://github.com/EIDA/dmtri/issues"
|
|
16
|
+
|
|
17
|
+
def main():
|
|
18
|
+
global_options = argparse.ArgumentParser(add_help=False)
|
|
19
|
+
|
|
20
|
+
parser = argparse.ArgumentParser(
|
|
21
|
+
description="Trigger data/metadata refresh or cleaning for multiple EIDA endpoints.",
|
|
22
|
+
parents=[global_options]
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--version",
|
|
26
|
+
action="version",
|
|
27
|
+
version=f"%(prog)s version {get_version()} — See {PROJECT_URL} for updates or to report issues."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
31
|
+
|
|
32
|
+
shared = argparse.ArgumentParser(add_help=False, parents=[global_options])
|
|
33
|
+
shared.add_argument("-n", "--network", nargs="+", default=["*"], help="FDSN network code(s), supports wildcards")
|
|
34
|
+
shared.add_argument("-s", "--station", nargs="+", default=["*"], help="FDSN station code(s), supports wildcards")
|
|
35
|
+
shared.add_argument("-l", "--location", nargs="+", default=["*"], help="FDSN location code(s), supports wildcards")
|
|
36
|
+
shared.add_argument("-c", "--channel", nargs="+", default=["*"], help="FDSN channel code(s), supports wildcards")
|
|
37
|
+
shared.add_argument("-S", "--starttime", required=True, help="Start time (ISO 8601 format)")
|
|
38
|
+
|
|
39
|
+
now_str = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
|
|
40
|
+
shared.add_argument("-E", "--endtime", default=now_str, help="End time (default: now in UTC)")
|
|
41
|
+
shared.add_argument("--type", default="data,metadata", help="Comma-separated types to process (data, metadata)")
|
|
42
|
+
shared.add_argument("--no-confirm", action="store_true", help="Skip confirmation prompt before executing")
|
|
43
|
+
shared.add_argument("--debug", action="store_true", help="Show verbose output from Ansible")
|
|
44
|
+
|
|
45
|
+
subparsers.add_parser("refresh", parents=[shared], help="Refresh data/metadata using configured playbooks.")
|
|
46
|
+
subparsers.add_parser("clean", parents=[shared], help="Clean outdated data/metadata using configured playbooks.")
|
|
47
|
+
subparsers.add_parser("track", help="Track job status via tracking playbook.")
|
|
48
|
+
doctor_parser = subparsers.add_parser("doctor", help="Check SSH connectivity to all inventory hosts")
|
|
49
|
+
doctor_parser.add_argument("-v", "--verbose", action="store_true", help="Show full Ansible output per host")
|
|
50
|
+
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
if args.command == "track":
|
|
53
|
+
cfg_file = "ansible_verbose.cfg"
|
|
54
|
+
else:
|
|
55
|
+
cfg_file = "ansible_verbose.cfg" if getattr(args, "debug", False) else "ansible_quiet.cfg"
|
|
56
|
+
|
|
57
|
+
cfg_path = os.path.abspath(cfg_file)
|
|
58
|
+
os.environ["ANSIBLE_CONFIG"] = cfg_path
|
|
59
|
+
|
|
60
|
+
if args.command == "doctor":
|
|
61
|
+
from dmtri.doctor import run_diagnostics
|
|
62
|
+
return run_diagnostics(verbose=args.verbose)
|
|
63
|
+
|
|
64
|
+
elif args.command in ("refresh", "clean"):
|
|
65
|
+
start_dt, end_dt, types = validate_and_normalize_args(args, parser)
|
|
66
|
+
|
|
67
|
+
vars_to_pass = {
|
|
68
|
+
"network": args.network,
|
|
69
|
+
"station": args.station,
|
|
70
|
+
"location": args.location,
|
|
71
|
+
"channel": args.channel,
|
|
72
|
+
"starttime": args.starttime,
|
|
73
|
+
"endtime": args.endtime,
|
|
74
|
+
"type": [t.strip() for t in args.type.split(",")],
|
|
75
|
+
"debug": args.debug,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if args.command == "refresh":
|
|
79
|
+
selected_types = vars_to_pass["type"]
|
|
80
|
+
valid_types = set(COMMAND_PLAYBOOKS["refresh"].keys())
|
|
81
|
+
|
|
82
|
+
invalid = set(selected_types) - valid_types
|
|
83
|
+
if invalid:
|
|
84
|
+
print(f" Invalid --type: {', '.join(invalid)}. Valid types are: {', '.join(valid_types)}.")
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
|
|
87
|
+
playbooks = []
|
|
88
|
+
for t in selected_types:
|
|
89
|
+
playbooks.extend(COMMAND_PLAYBOOKS["refresh"][t])
|
|
90
|
+
else:
|
|
91
|
+
playbooks = COMMAND_PLAYBOOKS.get("clean", [])
|
|
92
|
+
|
|
93
|
+
if not playbooks:
|
|
94
|
+
print(f"No playbooks configured for command '{args.command}'.")
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
|
|
97
|
+
if not args.no_confirm:
|
|
98
|
+
for pb in playbooks:
|
|
99
|
+
print_execution_plan(args.command, vars_to_pass, pb)
|
|
100
|
+
proceed = input("\nDo you want to proceed with executing all playbooks? (y/N): ").strip().lower()
|
|
101
|
+
if proceed != "y":
|
|
102
|
+
print("Aborted by user.")
|
|
103
|
+
sys.exit(0)
|
|
104
|
+
|
|
105
|
+
for pb in playbooks:
|
|
106
|
+
run_hook(pb, vars_to_pass)
|
|
107
|
+
|
|
108
|
+
elif args.command == "track":
|
|
109
|
+
playbooks = COMMAND_PLAYBOOKS.get("track", [])
|
|
110
|
+
if not playbooks:
|
|
111
|
+
print("No playbooks configured for command 'track'.")
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
for pb in playbooks:
|
|
114
|
+
run_hook(pb, {})
|
|
115
|
+
|
|
116
|
+
else:
|
|
117
|
+
parser.print_help()
|
|
118
|
+
sys.exit(1)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import ansible_runner
|
|
3
|
+
from dmtri.paths import INVENTORY_FILE
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
def run_diagnostics(verbose=False, retries=1):
|
|
8
|
+
logger.info(f"Running connectivity check using inventory: {INVENTORY_FILE}")
|
|
9
|
+
|
|
10
|
+
ok_hosts = set()
|
|
11
|
+
unreachable_hosts = set()
|
|
12
|
+
|
|
13
|
+
for attempt in range(retries):
|
|
14
|
+
logger.info(f"Attempt {attempt + 1} of {retries}")
|
|
15
|
+
|
|
16
|
+
r = ansible_runner.run(
|
|
17
|
+
private_data_dir=".",
|
|
18
|
+
inventory=str(INVENTORY_FILE.resolve()),
|
|
19
|
+
module="ping",
|
|
20
|
+
host_pattern="all",
|
|
21
|
+
quiet=not verbose,
|
|
22
|
+
envvars={
|
|
23
|
+
"ANSIBLE_CACHE_PLUGIN": "memory" # Disable caching to avoid plugin warnings
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
for event in r.events:
|
|
28
|
+
if not isinstance(event, dict):
|
|
29
|
+
continue
|
|
30
|
+
data = event.get("event_data", {})
|
|
31
|
+
host = data.get("host")
|
|
32
|
+
if not host:
|
|
33
|
+
continue
|
|
34
|
+
if event["event"] == "runner_on_ok":
|
|
35
|
+
ok_hosts.add(host)
|
|
36
|
+
elif event["event"] == "runner_on_unreachable":
|
|
37
|
+
unreachable_hosts.add(host)
|
|
38
|
+
|
|
39
|
+
# Break early if all hosts responded
|
|
40
|
+
if ok_hosts and not unreachable_hosts:
|
|
41
|
+
break
|
|
42
|
+
|
|
43
|
+
logger.info("\nResults:")
|
|
44
|
+
|
|
45
|
+
if not ok_hosts and not unreachable_hosts:
|
|
46
|
+
logger.warning("No responses received. Are you connected to the VPN?")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
for host in sorted(ok_hosts):
|
|
50
|
+
logger.info(f"[OK] {host}")
|
|
51
|
+
for host in sorted(unreachable_hosts):
|
|
52
|
+
logger.error(f"[UNREACHABLE] {host}")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_playbook_metadata(playbook_path: str):
|
|
7
|
+
try:
|
|
8
|
+
with open(playbook_path) as f:
|
|
9
|
+
playbook = yaml.safe_load(f)
|
|
10
|
+
|
|
11
|
+
if not playbook or not isinstance(playbook, list) or not playbook[0]:
|
|
12
|
+
return {"error": "Empty playbook"}
|
|
13
|
+
|
|
14
|
+
metadata = {
|
|
15
|
+
"hosts": playbook[0].get("hosts", "unknown"),
|
|
16
|
+
"description": playbook[0].get("vars", {}).get("description", ""),
|
|
17
|
+
"tasks": []
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for task in playbook[0].get("tasks", []):
|
|
21
|
+
desc = task.get("vars", {}).get("description", task.get("name", "Unnamed Task"))
|
|
22
|
+
metadata["tasks"].append(desc)
|
|
23
|
+
|
|
24
|
+
return metadata
|
|
25
|
+
|
|
26
|
+
except Exception as e:
|
|
27
|
+
return {"error": str(e)}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def print_execution_plan(command: str, variables: dict, playbook_path: str, metadata=None):
|
|
34
|
+
if metadata is None:
|
|
35
|
+
metadata = get_playbook_metadata(playbook_path)
|
|
36
|
+
|
|
37
|
+
print("\nExecution Plan")
|
|
38
|
+
print("-" * 40)
|
|
39
|
+
print(f"Command : {command}")
|
|
40
|
+
print(f"Playbook : {playbook_path}")
|
|
41
|
+
|
|
42
|
+
if "error" in metadata:
|
|
43
|
+
print(f"Warning : {metadata['error']}")
|
|
44
|
+
else:
|
|
45
|
+
print(f"Hosts : {metadata['hosts']}")
|
|
46
|
+
print(f"Description : {metadata['description']}")
|
|
47
|
+
print("Steps :")
|
|
48
|
+
for idx, desc in enumerate(metadata['tasks'], start=1):
|
|
49
|
+
print(f" {idx}. {desc}")
|
|
50
|
+
|
|
51
|
+
if variables:
|
|
52
|
+
print("\nVariables:")
|
|
53
|
+
for k, v in variables.items():
|
|
54
|
+
print(f" {k:10}: {v}")
|
|
55
|
+
|
|
56
|
+
confirm = input("\nProceed with this execution plan? [y/N]: ").strip().lower()
|
|
57
|
+
if confirm != "y":
|
|
58
|
+
print("Aborted by user.")
|
|
59
|
+
sys.exit(0)
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import ansible_runner
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import logging
|
|
5
|
+
from dmtri.paths import INVENTORY_FILE
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
def run_hook(playbook_name: Path, vars: dict,inventory: Path = INVENTORY_FILE) -> None:
|
|
10
|
+
"""
|
|
11
|
+
Run an Ansible playbook using ansible_runner.run_command, bypassing the project layout.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Build the extra vars string manually
|
|
15
|
+
extra_vars = []
|
|
16
|
+
for key, value in vars.items():
|
|
17
|
+
if isinstance(value, list):
|
|
18
|
+
value = ",".join(value)
|
|
19
|
+
extra_vars.extend(["-e", f"{key}='{value}'"])
|
|
20
|
+
|
|
21
|
+
cmd = [
|
|
22
|
+
"ansible-playbook",
|
|
23
|
+
str(playbook_name.resolve()),
|
|
24
|
+
"-i",
|
|
25
|
+
str(inventory.resolve()),
|
|
26
|
+
|
|
27
|
+
] + extra_vars
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
logger.info(f"Running: {' '.join(str(x) for x in cmd)}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
out, err, rc = ansible_runner.run_command(
|
|
34
|
+
executable_cmd="ansible-playbook",
|
|
35
|
+
cmdline_args=cmd[1:],
|
|
36
|
+
input_fd=sys.stdin,
|
|
37
|
+
output_fd=sys.stdout,
|
|
38
|
+
error_fd=sys.stderr,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if rc != 0:
|
|
42
|
+
logger.error(f"Playbook {playbook_name} failed with return code {rc}")
|
|
43
|
+
sys.exit(rc)
|
|
44
|
+
|
|
45
|
+
logger.info(f"Playbook {playbook_name} completed successfully")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
MODULE_DIR = Path(__file__).resolve().parent
|
|
4
|
+
SRC_DIR = MODULE_DIR.parent
|
|
5
|
+
|
|
6
|
+
# Directories
|
|
7
|
+
PLAYBOOKS_DIR = SRC_DIR / "playbooks"
|
|
8
|
+
INVENTORY_DIR = SRC_DIR / "inventory"
|
|
9
|
+
|
|
10
|
+
# Inventory file
|
|
11
|
+
INVENTORY_FILE = INVENTORY_DIR / "hosts.ini"
|
|
12
|
+
|
|
13
|
+
# Specific playbooks
|
|
14
|
+
PLAYBOOK_TRACK = PLAYBOOKS_DIR / "track.yml"
|
|
15
|
+
PLAYBOOK_LIST_SDS = PLAYBOOKS_DIR / "list_sds_files.yml"
|
|
16
|
+
|
|
17
|
+
# Refresh Data playbooks
|
|
18
|
+
PLAYBOOK_SEEDPSD_REFRESH = PLAYBOOKS_DIR / "refresh" / "seedpsd_refresh.yml"
|
|
19
|
+
PLAYBOOK_WFCATALOG_REFRESH = PLAYBOOKS_DIR / "refresh" / "wfcatalog_refresh.yml"
|
|
20
|
+
PLAYBOOK_AVAILABILITY_REFRESH = PLAYBOOKS_DIR / "refresh" / "availability_refresh.yml"
|
|
21
|
+
# Refresh metadata playbooks
|
|
22
|
+
PLAYBOOK_METADATA_REFRESH = PLAYBOOKS_DIR / "refresh" / "seedpsd_metadata_refresh.yml"
|
|
23
|
+
|
|
24
|
+
# Clean playbooks
|
|
25
|
+
PLAYBOOK_SEEDPSD_CLEAN = PLAYBOOKS_DIR / "clean" /"seedpsd_clean.yml"
|
|
26
|
+
PLAYBOOK_WFCATALOG_CLEAN = PLAYBOOKS_DIR / "clean" / "wfcatalog_clean.yml"
|
|
27
|
+
PLAYBOOK_AVAILABILITY_CLEAN = PLAYBOOKS_DIR / "clean" / "availability_clean.yml"
|
|
28
|
+
|
|
29
|
+
# Group playbooks by command
|
|
30
|
+
COMMAND_PLAYBOOKS = {
|
|
31
|
+
"refresh": {
|
|
32
|
+
"data": [
|
|
33
|
+
PLAYBOOK_SEEDPSD_REFRESH,
|
|
34
|
+
PLAYBOOK_WFCATALOG_REFRESH,
|
|
35
|
+
PLAYBOOK_AVAILABILITY_REFRESH,
|
|
36
|
+
],
|
|
37
|
+
"metadata": [
|
|
38
|
+
PLAYBOOK_METADATA_REFRESH,
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"clean": [
|
|
42
|
+
PLAYBOOK_SEEDPSD_CLEAN,
|
|
43
|
+
PLAYBOOK_WFCATALOG_CLEAN,
|
|
44
|
+
PLAYBOOK_AVAILABILITY_CLEAN,
|
|
45
|
+
],
|
|
46
|
+
"track": [
|
|
47
|
+
PLAYBOOK_TRACK
|
|
48
|
+
]
|
|
49
|
+
}
|