Skip to content

Commit bee35dc

Browse files
Sagar-TalikotiSagar-Talikoti
authored andcommitted
code updates
1 parent 1a2ed5d commit bee35dc

6 files changed

Lines changed: 267 additions & 213 deletions

File tree

‎bin/mas-devops-feature-status-update‎

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Sub-commands
3535
Required indexes are created automatically on the first call if they do
3636
not already exist (idempotent).
3737
38-
Example — instance-level, ACTIVE:
38+
Example — instance-level, ACTIVE (single IP):
3939
mas-devops-feature-status-update status-update \\
4040
--region us-east-2 \\
4141
--instance-id inst02 \\
@@ -49,27 +49,46 @@ Sub-commands
4949
--deployment-start 2026-09-11T11:48:42+00:00 \\
5050
--deployment-end 2026-09-11T11:53:10+00:00
5151
52-
Example — cluster-level (no --instance-id), ERROR:
52+
Example — instance-level, ACTIVE (multiple IPs):
5353
mas-devops-feature-status-update status-update \\
5454
--region us-east-2 \\
55+
--instance-id inst02 \\
5556
--account fyre-noble10-dev \\
5657
--cluster noble10 \\
5758
--subscription-id sub-id01 \\
5859
--type allow-list \\
59-
--feature-details '{"ips": ["2405:201:d000:9062::/64"]}' \\
60+
--feature-details '{"ips": ["1.2.3.4/32", "2405:201:d000:9062::/64"]}' \\
61+
--status ACTIVE \\
62+
--status-details '{"message": "Allow list is active.", "request_configuration": "1.2.3.4/32, 2405:201:d000:9062::/64"}' \\
63+
--deployment-start 2026-09-11T11:48:42+00:00 \\
64+
--deployment-end 2026-09-11T11:53:10+00:00
65+
66+
Example — instance-level, ERROR (using --status-details-file to avoid shell quoting issues):
67+
cat > /tmp/status-details.json <<'EOF'
68+
{
69+
"message": "sample error message",
70+
"error_code": 401,
71+
"error_source": {
72+
"gitops_version": "8.6.0",
73+
"filename": "cis_ip_allowlist.yml",
74+
"log_file": "/var/log/gitops/run-001.log",
75+
"stacktrace": "Traceback (most recent call last): ..."
76+
},
77+
"request_configuration": "2405:201:d000:9060::/64"
78+
}
79+
EOF
80+
mas-devops-feature-status-update status-update \\
81+
--region us-east-2 \\
82+
--instance-id inst02 \\
83+
--account fyre-noble10-dev \\
84+
--cluster noble10 \\
85+
--subscription-id sub-id01 \\
86+
--type allow-list \\
87+
--feature-details '{"ips": ["2405:201:d000:9060::/64"]}' \\
6088
--status ERROR \\
61-
--status-details '{
62-
"message": "sample error message",
63-
"error_code": 401,
64-
"error_source": {
65-
"gitops_version": "8.6.0",
66-
"filename": "cis_ip_allowlist.yml",
67-
"line_no": 148,
68-
"log_file": "/var/log/gitops/run-001.log",
69-
"stacktrace": "Traceback (most recent call last): ..."
70-
},
71-
"request_configuration": "2405:201:d000:9060::/64"
72-
}'
89+
--status-details-file /tmp/status-details.json \\
90+
--deployment-start 2026-09-11T11:48:42+00:00 \\
91+
--deployment-end 2026-09-11T11:53:10+00:00
7392
7493
get
7594
Fetch and pretty-print a feature status entry by ObjectId or by
@@ -163,6 +182,19 @@ def _parse_json_arg(value: str, arg_name: str) -> dict:
163182
return result
164183

165184

185+
def _read_file_arg(path: str, arg_name: str) -> str:
186+
"""Read and return the contents of *path* for use as a CLI argument value.
187+
188+
Raises SystemExit(1) if the file cannot be read.
189+
"""
190+
try:
191+
with open(path) as fh:
192+
return fh.read()
193+
except OSError as exc:
194+
print(f"ERROR: --{arg_name}: cannot read file '{path}': {exc}", file=sys.stderr)
195+
sys.exit(1)
196+
197+
166198
def _parse_isodate(value: Optional[str], arg_name: str) -> Optional[datetime]:
167199
"""Parse an ISO-8601 datetime, stripping MongoDB ISODate() wrappers."""
168200
if value is None:
@@ -301,8 +333,8 @@ def cmd_status_update(args) -> int:
301333
"""Upsert a feature status entry into MongoDB.
302334
303335
Collection routing is determined by FEATURE_LEVEL_MAP:
304-
INSTANCE_LEVEL → instance_level_config (--instance-id required)
305-
otherwise → cluster_level_config (--instance-id must be absent)
336+
INSTANCE_LEVEL → instance_level_config (--instance-id and --subscription-id required)
337+
otherwise → cluster_level_config (--instance-id and --subscription-id must be absent)
306338
307339
Required indexes are created automatically if they do not already exist.
308340
"""
@@ -330,17 +362,24 @@ def cmd_status_update(args) -> int:
330362
print(f"ERROR: {exc}", file=sys.stderr)
331363
return 1
332364

333-
# Validate --instance-id consistency with the feature type's level.
365+
# Validate --instance-id / --subscription-id consistency with the feature type's level.
334366
if level == INSTANCE_LEVEL and not args.instance_id:
335367
print(f"ERROR: --instance-id is required for instance-level feature type '{args.type}'", file=sys.stderr)
336368
return 1
369+
if level == INSTANCE_LEVEL and not args.subscription_id:
370+
print(f"ERROR: --subscription-id is required for instance-level feature type '{args.type}'", file=sys.stderr)
371+
return 1
337372
if level != INSTANCE_LEVEL and args.instance_id:
338373
print(f"ERROR: --instance-id must not be supplied for cluster-level feature type '{args.type}'", file=sys.stderr)
339374
return 1
340375

341-
# Parse JSON arguments
376+
# Parse JSON arguments — --status-details-file takes precedence over --status-details
342377
feature_details = _parse_json_arg(args.feature_details, "feature-details")
343-
status_details = _parse_json_arg(args.status_details, "status-details")
378+
if args.status_details_file:
379+
status_details_raw = _read_file_arg(args.status_details_file, "status-details-file")
380+
status_details = _parse_json_arg(status_details_raw, "status-details-file")
381+
else:
382+
status_details = _parse_json_arg(args.status_details, "status-details")
344383

345384
# Type-specific validation
346385
try:
@@ -462,7 +501,13 @@ def build_parser() -> argparse.ArgumentParser:
462501
)
463502
identity.add_argument("--account", required=True, help="GitOps account name (e.g. fyre-noble10-dev)")
464503
identity.add_argument("--cluster", required=True, help="GitOps cluster name (e.g. noble10)")
465-
identity.add_argument("--subscription-id", required=True, dest="subscription_id", help="Subscription ID")
504+
identity.add_argument(
505+
"--subscription-id",
506+
required=False,
507+
default=None,
508+
dest="subscription_id",
509+
help="Subscription ID. Required for instance-level feature types; must be omitted for cluster-level types.",
510+
)
466511

467512
feature = su.add_argument_group("feature")
468513
feature.add_argument(
@@ -485,15 +530,30 @@ def build_parser() -> argparse.ArgumentParser:
485530
choices=["REQUESTED", "IN_PROGRESS", "ACTIVE", "ERROR"],
486531
help="Feature lifecycle status.",
487532
)
488-
status.add_argument(
533+
status_details_group = status.add_mutually_exclusive_group(required=True)
534+
status_details_group.add_argument(
489535
"--status-details",
490-
required=True,
536+
default=None,
491537
dest="status_details",
492538
metavar="JSON",
493539
help=(
494540
"JSON object describing the outcome. "
495-
"ACTIVE: {message, request_configuration}. "
496-
"ERROR: {message, error_code, error_source, request_configuration}."
541+
"ACTIVE/REQUESTED/IN_PROGRESS: {message, request_configuration}. "
542+
"ERROR: {message, error_code, error_source, request_configuration}. "
543+
"request_configuration may be a single CIDR or a comma/space-separated list. "
544+
"Mutually exclusive with --status-details-file."
545+
),
546+
)
547+
status_details_group.add_argument(
548+
"--status-details-file",
549+
default=None,
550+
dest="status_details_file",
551+
metavar="FILE",
552+
help=(
553+
"Path to a JSON file containing the status-details object. "
554+
"Useful for ERROR payloads whose message or stacktrace contains quotes "
555+
"that would break inline shell quoting. "
556+
"Mutually exclusive with --status-details."
497557
),
498558
)
499559

‎bin/mas-devops-feature-status-update.md‎

Lines changed: 94 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,15 @@ Required collection indexes (`instance_config_level`, `cluster_config_level`) ar
142142

143143
**Idempotency:** Safe to call multiple times with the same arguments. The underlying `find_one_and_update` with `upsert=True` guarantees that re-running with the same identity key produces the same final document state. `created_at` is set only on the first insert (`$setOnInsert`); subsequent calls update `updated_at` and all mutable fields without creating duplicate documents.
144144

145-
**Identity options** *(all required)*
145+
**Identity options**
146146

147-
| Flag | Description |
148-
|------|-------------|
149-
| `--region` | AWS region (e.g. `us-east-2`) |
150-
| `--instance-id` | MAS instance ID (e.g. `inst02`) |
151-
| `--account` | GitOps account name (e.g. `fyre-noble10-dev`) |
152-
| `--cluster` | GitOps cluster name (e.g. `noble10`) |
153-
| `--subscription-id` | Subscription ID |
147+
| Flag | Required | Description |
148+
|------|----------|-------------|
149+
| `--region` | Yes | AWS region (e.g. `us-east-2`) |
150+
| `--instance-id` | Instance-level types | MAS instance ID (e.g. `inst02`). When supplied routes to `instance_level_config`; when omitted routes to `cluster_level_config`. |
151+
| `--account` | Yes | GitOps account name (e.g. `fyre-noble10-dev`) |
152+
| `--cluster` | Yes | GitOps cluster name (e.g. `noble10`) |
153+
| `--subscription-id` | Instance-level types | Subscription ID. Required when `--instance-id` is supplied; must be omitted for cluster-level feature types. |
154154

155155
**Feature options** *(all required)*
156156

@@ -164,7 +164,8 @@ Required collection indexes (`instance_config_level`, `cluster_config_level`) ar
164164
| Flag | Description |
165165
|------|-------------|
166166
| `--status` | One of `REQUESTED`, `IN_PROGRESS`, `ACTIVE`, `ERROR` |
167-
| `--status-details JSON` | JSON object describing the outcome (see schema below) |
167+
| `--status-details JSON` | JSON object describing the outcome (see schema below). Mutually exclusive with `--status-details-file`. |
168+
| `--status-details-file FILE` | Path to a JSON file containing the status-details object. Use this for `ERROR` payloads whose `message` or `stacktrace` contains quote characters that would break inline shell interpolation. Mutually exclusive with `--status-details`. |
168169

169170
**Timestamp options** *(all optional, default: current UTC time)*
170171

@@ -179,6 +180,8 @@ The MongoDB connection URI is read from the `DEVOPS_MONGO_URI` environment varia
179180

180181
**`--status-details` schema**
181182

183+
`request_configuration` is a free-form string describing what was requested — it may be a single CIDR, a comma-separated list, or a space-separated list. The validator does not enforce format.
184+
182185
*REQUESTED* — pipeline has received the request but processing has not yet started.
183186
```json
184187
{
@@ -195,23 +198,22 @@ The MongoDB connection URI is read from the `DEVOPS_MONGO_URI` environment varia
195198
}
196199
```
197200

198-
*ACTIVE* — deployment completed successfully.
201+
*ACTIVE* — deployment completed successfully. Multiple IPs may be comma-separated.
199202
```json
200203
{
201204
"message": "Allow list is active.",
202-
"request_configuration": "2405:201:d000:9062::/64"
205+
"request_configuration": "1.2.3.4/32, 2405:201:d000:9062::/64"
203206
}
204207
```
205208

206-
*ERROR* — deployment failed.
209+
*ERROR* — deployment failed. `error_source` fields are all optional except that the object itself is required. Use `--status-details-file` when `message` or `stacktrace` may contain quote characters.
207210
```json
208211
{
209212
"message": "sample error message",
210213
"error_code": 401,
211214
"error_source": {
212215
"gitops_version": "8.6.0",
213216
"filename": "cis_ip_allowlist.yml",
214-
"line_no": 148,
215217
"log_file": "/var/log/gitops/run-001.log",
216218
"stacktrace": "Traceback (most recent call last): ..."
217219
},
@@ -248,7 +250,7 @@ mas-devops-feature-status-update status-update \
248250
--status-details '{"message": "Allow list deployment in progress.", "request_configuration": "2405:201:d000:9062::/64"}' \
249251
--deployment-start 2026-09-11T11:48:42+00:00
250252

251-
# ACTIVE status — record successful completion
253+
# ACTIVE status — single IP
252254
mas-devops-feature-status-update status-update \
253255
--region us-east-2 \
254256
--instance-id inst02 \
@@ -262,28 +264,44 @@ mas-devops-feature-status-update status-update \
262264
--deployment-start 2026-09-11T11:48:42+00:00 \
263265
--deployment-end 2026-09-11T11:53:10+00:00
264266

265-
# ERROR status — record a failed deployment
267+
# ACTIVE status — multiple IPs (request_configuration is comma-separated)
266268
mas-devops-feature-status-update status-update \
267269
--region us-east-2 \
268270
--instance-id inst02 \
269271
--account fyre-noble10-dev \
270272
--cluster noble10 \
271273
--subscription-id sub-id01 \
272274
--type allow-list \
273-
--feature-details '{"ips": ["2405:201:d000:9062::/64"]}' \
275+
--feature-details '{"ips": ["1.2.3.4/32", "2405:201:d000:9062::/64"]}' \
276+
--status ACTIVE \
277+
--status-details '{"message": "Allow list is active.", "request_configuration": "1.2.3.4/32, 2405:201:d000:9062::/64"}' \
278+
--deployment-start 2026-09-11T11:48:42+00:00 \
279+
--deployment-end 2026-09-11T11:53:10+00:00
280+
281+
# ERROR status — use --status-details-file to avoid shell quoting issues with error text
282+
cat > /tmp/status-details.json <<'EOF'
283+
{
284+
"message": "sample error message",
285+
"error_code": 401,
286+
"error_source": {
287+
"gitops_version": "8.6.0",
288+
"filename": "cis_ip_allowlist.yml",
289+
"log_file": "/var/log/gitops/run-001.log",
290+
"stacktrace": "Traceback (most recent call last): ..."
291+
},
292+
"request_configuration": "2405:201:d000:9060::/64"
293+
}
294+
EOF
295+
mas-devops-feature-status-update status-update \
296+
--region us-east-2 \
297+
--instance-id inst02 \
298+
--account fyre-noble10-dev \
299+
--cluster noble10 \
300+
--subscription-id sub-id01 \
301+
--type allow-list \
302+
--feature-details '{"ips": ["2405:201:d000:9060::/64"]}' \
274303
--status ERROR \
275-
--status-details '{
276-
"message": "sample error message",
277-
"error_code": 401,
278-
"error_source": {
279-
"gitops_version": "8.6.0",
280-
"filename": "cis_ip_allowlist.yml",
281-
"line_no": 148,
282-
"log_file": "/var/log/gitops/run-001.log",
283-
"stacktrace": "Traceback (most recent call last): ..."
284-
},
285-
"request_configuration": "2405:201:d000:9060::/64"
286-
}' \
304+
--status-details-file /tmp/status-details.json \
287305
--deployment-start 2026-09-11T11:48:42+00:00 \
288306
--deployment-end 2026-09-11T11:53:10+00:00
289307
```
@@ -414,6 +432,16 @@ db.cluster_level_config.drop()
414432
db.instance_level_config.drop()
415433
```
416434

435+
### Drop collections (removes schema & indexes) with auth
436+
```
437+
mongosh "mongodb://mas_devops_user:mas_devops_password@localhost:27017/mas_devops?authSource=mas_devops&tls=false&tlsAllowInvalidCertificates=true" \ # pragma: allowlist secret
438+
--eval "
439+
db.cluster_level_config.drop()
440+
db.instance_level_config.drop()
441+
print('collections dropped')
442+
"
443+
```
444+
417445
> **Note:** `drop()` removes the collection, all documents, and all indexes. Re-run `init_db.js` to recreate them.
418446
419447
### Indexes created by `init_db.js`
@@ -505,6 +533,44 @@ See the full sample playbook at [`playbooks/feature-status-update.yml`](../playb
505533
changed_when: "'written successfully' in status_update_result.stdout"
506534
```
507535
536+
For `ERROR` status, write the payload to a file first to avoid shell quoting problems with error text:
537+
538+
```yaml
539+
- name: Write ERROR status-details to file
540+
ansible.builtin.copy:
541+
dest: /tmp/mas-status-details.json
542+
content: |
543+
{
544+
"message": "{{ _error_msg | replace('\\', '\\\\') | replace('"', '\\"') }}",
545+
"error_code": {{ _error_code }},
546+
"error_source": {
547+
"gitops_version": "{{ lookup('env', 'GITOPS_VERSION') | default('', true) }}",
548+
"filename": "{{ _error_filename }}",
549+
"log_file": "{{ lookup('env', 'JUNIT_OUTPUT_DIR') | default('/var/log/gitops', true) }}/run.log",
550+
"stacktrace": "{{ _error_msg | replace('\\', '\\\\') | replace('"', '\\"') }}"
551+
},
552+
"request_configuration": "{{ _request_configuration }}"
553+
}
554+
555+
- name: Upsert feature status (ERROR)
556+
ansible.builtin.command:
557+
cmd: >-
558+
mas-devops-feature-status-update status-update
559+
--region {{ mas_region }}
560+
--instance-id {{ mas_instance_id }}
561+
--account {{ mas_account }}
562+
--cluster {{ mas_cluster }}
563+
--subscription-id {{ mas_subscription_id }}
564+
--type allow-list
565+
--feature-details {{ ('{"ips": ' + _normalised_ips | to_json + '}') | quote }}
566+
--status ERROR
567+
--status-details-file /tmp/mas-status-details.json
568+
--deployment-start {{ _deployment_start }}
569+
--deployment-end {{ _deployment_end }}
570+
register: status_update_result
571+
changed_when: "'written successfully' in status_update_result.stdout"
572+
```
573+
508574
### Minimal task — `get`
509575

510576
**By ObjectId** — extract the document ID from `status-update` output and fetch the written document:

0 commit comments

Comments
 (0)