gulp-cli 1.0.7__tar.gz → 1.0.9__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.
- {gulp_cli-1.0.7/src/gulp_cli.egg-info → gulp_cli-1.0.9}/PKG-INFO +1 -1
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/docs/command-reference.md +44 -1
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/docs/examples.md +36 -3
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/_version.py +3 -3
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/ingest.py +187 -2
- {gulp_cli-1.0.7 → gulp_cli-1.0.9/src/gulp_cli.egg-info}/PKG-INFO +1 -1
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/.github/workflows/python-package.yml +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/.gitignore +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/README.md +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/docs/extensions.md +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/docs/getting-started.md +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/docs/resource-management.md +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/docs/troubleshooting-cli.md +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/pyproject.toml +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/setup.cfg +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/__init__.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/__main__.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/cli.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/client.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/__init__.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/acl.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/auth.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/collab.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/context.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/db.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/enhance_map.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/glyph.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/mapping.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/operations.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/plugin.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/query.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/source.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/stats.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/storage.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/user_group.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/commands/users.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/config.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/extension/__init__.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/extension/query_sigma_zip.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/extension/story.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/extension_helpers.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/extensions.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/output.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli/utils.py +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli.egg-info/SOURCES.txt +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli.egg-info/dependency_links.txt +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli.egg-info/entry_points.txt +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli.egg-info/requires.txt +0 -0
- {gulp_cli-1.0.7 → gulp_cli-1.0.9}/src/gulp_cli.egg-info/top_level.txt +0 -0
|
@@ -520,6 +520,8 @@ gulp-cli ingest file-to-source source123 '/new/*.evtx'
|
|
|
520
520
|
|
|
521
521
|
Ingest from a ZIP archive.
|
|
522
522
|
|
|
523
|
+
> TO BE DEPRECATED...
|
|
524
|
+
|
|
523
525
|
```bash
|
|
524
526
|
gulp-cli ingest zip OPERATION_ID ZIP_FILE [OPTIONS]
|
|
525
527
|
```
|
|
@@ -527,7 +529,7 @@ gulp-cli ingest zip OPERATION_ID ZIP_FILE [OPTIONS]
|
|
|
527
529
|
**Arguments:**
|
|
528
530
|
|
|
529
531
|
- `OPERATION_ID` — Target operation
|
|
530
|
-
- `ZIP_FILE` — Path to ZIP file
|
|
532
|
+
- `ZIP_FILE` — Path to ZIP file containing a `metadata.json` in the root, describing the content as specified in gulp's `ingest_zip` docs.
|
|
531
533
|
|
|
532
534
|
**Options:**
|
|
533
535
|
|
|
@@ -546,6 +548,47 @@ gulp-cli ingest zip my_op /data/evidence.zip --create-operation
|
|
|
546
548
|
|
|
547
549
|
---
|
|
548
550
|
|
|
551
|
+
#### `zip-create`
|
|
552
|
+
|
|
553
|
+
Create a ZIP archive from one or more source path expressions.
|
|
554
|
+
|
|
555
|
+
Path expressions may be:
|
|
556
|
+
|
|
557
|
+
- a single file path
|
|
558
|
+
- a directory path
|
|
559
|
+
- a glob mask (for example `/bla/somef*.txt`, `/bla/*`, `**/*.evtx`)
|
|
560
|
+
|
|
561
|
+
Environment variables and home shortcuts are expanded in all input paths (for example `$EVIDENCE_DIR/*.evtx`, `~/cases/case-01/*`).
|
|
562
|
+
|
|
563
|
+
```bash
|
|
564
|
+
gulp-cli ingest zip-create OUTPUT_ZIP [PATH_PATTERN...] [OPTIONS]
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**Arguments:**
|
|
568
|
+
|
|
569
|
+
- `OUTPUT_ZIP` — Destination ZIP path (supports environment variables and `~`)
|
|
570
|
+
- `PATH_PATTERN` — File, directory, or glob path expression (multiple allowed)
|
|
571
|
+
|
|
572
|
+
**Options:**
|
|
573
|
+
|
|
574
|
+
- `--paths-file TEXT` — Text file with one source path expression per line (supports environment variables and `~` per line)
|
|
575
|
+
- `--overwrite` — Overwrite output ZIP if it already exists
|
|
576
|
+
|
|
577
|
+
**Examples:**
|
|
578
|
+
|
|
579
|
+
```bash
|
|
580
|
+
# Create a ZIP from mixed path expressions
|
|
581
|
+
gulp-cli ingest zip-create /tmp/evidence.zip /data/host1/*.evtx /data/host2 /data/readme.txt
|
|
582
|
+
|
|
583
|
+
# Use environment variables and home shortcuts
|
|
584
|
+
gulp-cli ingest zip-create '$CASE_DIR/evidence.zip' '$CASE_DIR/raw/*.json' '~/pcaps/*.pcap' --overwrite
|
|
585
|
+
|
|
586
|
+
# Read source path expressions from a file
|
|
587
|
+
gulp-cli ingest zip-create /tmp/evidence.zip --paths-file /tmp/evidence_paths.txt --overwrite
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
549
592
|
#### `raw`
|
|
550
593
|
|
|
551
594
|
Ingest raw payload chunks into an operation.
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- [JSON Logs Ingestion](#json-logs-ingestion)
|
|
10
10
|
- [Add More Evidence to Existing Source](#add-more-evidence-to-existing-source)
|
|
11
11
|
- [ZIP Archive Ingestion](#zip-archive-ingestion)
|
|
12
|
+
- [Create ZIP Archive from Paths and Masks](#create-zip-archive-from-paths-and-masks)
|
|
12
13
|
- [Raw Payload Ingestion](#raw-payload-ingestion)
|
|
13
14
|
- [Request Stats Monitoring Workflows](#request-stats-monitoring-workflows)
|
|
14
15
|
- [Monitor Ongoing Requests (Live)](#monitor-ongoing-requests-live)
|
|
@@ -122,7 +123,7 @@ gulp-cli auth logout
|
|
|
122
123
|
|
|
123
124
|
---
|
|
124
125
|
|
|
125
|
-
|
|
126
|
+
## Ingestion Workflows
|
|
126
127
|
|
|
127
128
|
### Single File Ingestion
|
|
128
129
|
|
|
@@ -171,6 +172,14 @@ wait # Wait for all background jobs
|
|
|
171
172
|
# Ingest CSV with specific delimiter and encoding
|
|
172
173
|
gulp-cli ingest file incident-001 csv /data/access_log.csv \
|
|
173
174
|
--plugin-params '{"delimiter":";","encoding":"iso-8859-1","has_header":true}'
|
|
175
|
+
|
|
176
|
+
# pass mapping directly without using a mapping file
|
|
177
|
+
gulp-cli ingest file test_operation csv ./samples/mftecmd/sample_record.csv --plugin-params '{ "mapping_parameters": { "mappings": { "test
|
|
178
|
+
_mapping": { "fields": { "Created0x10": { "ecs": [ "@timestamp" ] } } } } } }' --reset-operation --wait
|
|
179
|
+
|
|
180
|
+
# pass mapping using a gulp mapping file with mapping_id to specify which mapping to use in the file
|
|
181
|
+
gulp-cli ingest file test_operation csv ./samples/mftecmd/sample_record.csv --plugin-params '{ "mapping_parameters"
|
|
182
|
+
: { "mapping_file": "mftecmd_csv.json", "mapping_id": "record" } }' --wait --reset-operation
|
|
174
183
|
```
|
|
175
184
|
|
|
176
185
|
### JSON Logs Ingestion
|
|
@@ -206,6 +215,29 @@ gulp-cli ingest zip incident-001 /evidence/evidence.zip --wait
|
|
|
206
215
|
gulp-cli ingest zip incident-001 /evidence/evidence.zip --create-operation
|
|
207
216
|
```
|
|
208
217
|
|
|
218
|
+
### Create ZIP Archive from Paths and Masks
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# Build ZIP from mixed sources: file, directory, and wildcard mask
|
|
222
|
+
gulp-cli ingest zip-create /evidence/evidence.zip /forensic/host1/*.evtx /forensic/host2 /forensic/notes.txt --overwrite
|
|
223
|
+
|
|
224
|
+
# Use environment variables and ~ in source expressions and output path
|
|
225
|
+
export CASE_ROOT=~/cases/incident-001
|
|
226
|
+
gulp-cli ingest zip-create '$CASE_ROOT/evidence.zip' '$CASE_ROOT/windows/*.evtx' '$CASE_ROOT/network/*' --overwrite
|
|
227
|
+
|
|
228
|
+
# Build ZIP from a text file (one path expression per line)
|
|
229
|
+
cat > /tmp/evidence_paths.txt <<'EOF'
|
|
230
|
+
$CASE_ROOT/windows/*.evtx
|
|
231
|
+
$CASE_ROOT/linux/**/*.log
|
|
232
|
+
~/captures/*.pcap
|
|
233
|
+
EOF
|
|
234
|
+
|
|
235
|
+
gulp-cli ingest zip-create '$CASE_ROOT/evidence.zip' --paths-file /tmp/evidence_paths.txt --overwrite
|
|
236
|
+
|
|
237
|
+
# Then ingest the generated ZIP
|
|
238
|
+
gulp-cli ingest zip incident-001 '$CASE_ROOT/evidence.zip' --wait
|
|
239
|
+
```
|
|
240
|
+
|
|
209
241
|
### Raw Payload Ingestion
|
|
210
242
|
|
|
211
243
|
```bash
|
|
@@ -505,6 +537,7 @@ gulp-cli query external incident-001 \
|
|
|
505
537
|
--q '{"query":{"match_all":{}}}' \
|
|
506
538
|
--preview --limit 100 --offset 0
|
|
507
539
|
```
|
|
540
|
+
|
|
508
541
|
```
|
|
509
542
|
|
|
510
543
|
### Export Query Results
|
|
@@ -967,7 +1000,7 @@ gulp-cli acl make-private link-456 --obj-type link
|
|
|
967
1000
|
|
|
968
1001
|
---
|
|
969
1002
|
|
|
970
|
-
## Index Management Workflows
|
|
1003
|
+
## Index Management Workflows
|
|
971
1004
|
|
|
972
1005
|
### Inspect Indexes
|
|
973
1006
|
|
|
@@ -1093,7 +1126,7 @@ gulp-cli enhance-map delete <enhance_map_obj_id>
|
|
|
1093
1126
|
### Query Sigma Rules from ZIP (Extension)
|
|
1094
1127
|
|
|
1095
1128
|
> needs non-free `query_sigma_zip` plugin to be installed on the server, this is provided just as an example.
|
|
1096
|
-
|
|
1129
|
+
|
|
1097
1130
|
```bash
|
|
1098
1131
|
# Execute all Sigma rules inside a zip archive
|
|
1099
1132
|
gulp-cli query sigma-zip incident-001 \
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
21
|
+
__version__ = version = '1.0.9'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 9)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gb7a79dce7'
|
|
@@ -2,7 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
|
-
|
|
5
|
+
import os
|
|
6
|
+
import zipfile
|
|
7
|
+
from glob import glob, has_magic
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from typing import Any
|
|
8
10
|
|
|
@@ -64,6 +66,140 @@ def _expand_file_patterns(file_patterns: list[str]) -> list[str]:
|
|
|
64
66
|
return unique_files
|
|
65
67
|
|
|
66
68
|
|
|
69
|
+
def _resolve_path(raw_path: str) -> Path:
|
|
70
|
+
expanded = _expand_path_expression(raw_path)
|
|
71
|
+
return Path(expanded).resolve()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _expand_path_expression(raw_path: str) -> str:
|
|
75
|
+
"""Expand shell-style path expressions (env vars and '~')."""
|
|
76
|
+
return os.path.expandvars(os.path.expanduser(raw_path.strip()))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _expand_zip_source_patterns(
|
|
80
|
+
path_patterns: list[str],
|
|
81
|
+
paths_file: str | None,
|
|
82
|
+
) -> list[tuple[Path, Path]]:
|
|
83
|
+
if path_patterns and paths_file is not None:
|
|
84
|
+
raise typer.BadParameter(
|
|
85
|
+
"Provide paths either as arguments or via --paths-file, not both"
|
|
86
|
+
)
|
|
87
|
+
if not path_patterns and paths_file is None:
|
|
88
|
+
raise typer.BadParameter(
|
|
89
|
+
"Provide at least one path argument or --paths-file"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
raw_entries = list(path_patterns)
|
|
93
|
+
if paths_file is not None:
|
|
94
|
+
file_path = _resolve_path(paths_file)
|
|
95
|
+
if not file_path.is_file():
|
|
96
|
+
raise typer.BadParameter(f"Paths file not found: {file_path}")
|
|
97
|
+
raw_entries.extend(
|
|
98
|
+
line.strip()
|
|
99
|
+
for line in file_path.read_text(encoding="utf-8").splitlines()
|
|
100
|
+
if line.strip() and not line.lstrip().startswith("#")
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
expanded_paths: list[tuple[Path, Path]] = []
|
|
104
|
+
seen: set[tuple[Path, Path]] = set()
|
|
105
|
+
for raw_entry in raw_entries:
|
|
106
|
+
expanded_entry = _expand_path_expression(raw_entry)
|
|
107
|
+
if not expanded_entry:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
if has_magic(expanded_entry):
|
|
111
|
+
pattern_path = Path(expanded_entry)
|
|
112
|
+
base_dir = _resolve_glob_base(pattern_path)
|
|
113
|
+
matches = [Path(match).resolve() for match in sorted(glob(expanded_entry, recursive=True))]
|
|
114
|
+
if not matches:
|
|
115
|
+
raise typer.BadParameter(f"Path mask did not match any files: {expanded_entry}")
|
|
116
|
+
|
|
117
|
+
for match in matches:
|
|
118
|
+
item = (match, base_dir)
|
|
119
|
+
if item not in seen:
|
|
120
|
+
seen.add(item)
|
|
121
|
+
expanded_paths.append(item)
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
resolved_path = _resolve_path(expanded_entry)
|
|
125
|
+
if not resolved_path.exists():
|
|
126
|
+
raise typer.BadParameter(f"Path not found: {resolved_path}")
|
|
127
|
+
|
|
128
|
+
base_dir = resolved_path.parent
|
|
129
|
+
item = (resolved_path, base_dir)
|
|
130
|
+
if item not in seen:
|
|
131
|
+
seen.add(item)
|
|
132
|
+
expanded_paths.append(item)
|
|
133
|
+
|
|
134
|
+
return expanded_paths
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _resolve_glob_base(pattern_path: Path) -> Path:
|
|
138
|
+
parts = pattern_path.parts
|
|
139
|
+
if pattern_path.is_absolute():
|
|
140
|
+
base_dir = Path(pattern_path.anchor)
|
|
141
|
+
part_index = 1
|
|
142
|
+
else:
|
|
143
|
+
base_dir = Path.cwd()
|
|
144
|
+
part_index = 0
|
|
145
|
+
|
|
146
|
+
for part in parts[part_index:]:
|
|
147
|
+
if has_magic(part):
|
|
148
|
+
break
|
|
149
|
+
base_dir /= part
|
|
150
|
+
|
|
151
|
+
return base_dir.resolve()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _build_zip_from_sources(output_zip: Path, sources: list[tuple[Path, Path]]) -> tuple[int, list[str]]:
|
|
155
|
+
output_zip.parent.mkdir(parents=True, exist_ok=True)
|
|
156
|
+
|
|
157
|
+
archived_entries: list[str] = []
|
|
158
|
+
archived_count = 0
|
|
159
|
+
seen_archive_entries: set[str] = set()
|
|
160
|
+
seen_source_files: set[Path] = set()
|
|
161
|
+
|
|
162
|
+
def _record_directory(archive_name_str: str, archive: zipfile.ZipFile) -> None:
|
|
163
|
+
entry_name = f"{archive_name_str}/"
|
|
164
|
+
if entry_name in seen_archive_entries:
|
|
165
|
+
return
|
|
166
|
+
archive.writestr(entry_name, "")
|
|
167
|
+
seen_archive_entries.add(entry_name)
|
|
168
|
+
archived_entries.append(entry_name)
|
|
169
|
+
|
|
170
|
+
def _record_file(source_path: Path, archive_name_str: str, archive: zipfile.ZipFile) -> None:
|
|
171
|
+
nonlocal archived_count
|
|
172
|
+
if archive_name_str in seen_archive_entries or source_path in seen_source_files:
|
|
173
|
+
return
|
|
174
|
+
archive.write(source_path, arcname=archive_name_str)
|
|
175
|
+
seen_archive_entries.add(archive_name_str)
|
|
176
|
+
seen_source_files.add(source_path)
|
|
177
|
+
archived_entries.append(archive_name_str)
|
|
178
|
+
archived_count += 1
|
|
179
|
+
|
|
180
|
+
with zipfile.ZipFile(output_zip, mode="w", compression=zipfile.ZIP_DEFLATED) as archive:
|
|
181
|
+
for source_path, base_dir in sources:
|
|
182
|
+
archive_root = source_path.relative_to(base_dir)
|
|
183
|
+
|
|
184
|
+
if source_path.is_file():
|
|
185
|
+
_record_file(source_path, archive_root.as_posix(), archive)
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
child_paths = sorted(source_path.rglob("*"))
|
|
189
|
+
if not child_paths:
|
|
190
|
+
_record_directory(archive_root.as_posix(), archive)
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
for current_path in child_paths:
|
|
194
|
+
archive_name_str = current_path.relative_to(base_dir).as_posix()
|
|
195
|
+
if current_path.is_dir():
|
|
196
|
+
_record_directory(archive_name_str, archive)
|
|
197
|
+
continue
|
|
198
|
+
_record_file(current_path, archive_name_str, archive)
|
|
199
|
+
|
|
200
|
+
return archived_count, archived_entries
|
|
201
|
+
|
|
202
|
+
|
|
67
203
|
async def _wait_for_stats_create(
|
|
68
204
|
client: Any,
|
|
69
205
|
req_ids: list[str],
|
|
@@ -600,7 +736,10 @@ def ingest_file_to_source(
|
|
|
600
736
|
@app.command("zip")
|
|
601
737
|
def ingest_zip(
|
|
602
738
|
operation_id: str,
|
|
603
|
-
zip_file: str
|
|
739
|
+
zip_file: str = typer.Argument(
|
|
740
|
+
...,
|
|
741
|
+
help="Path to a ZIP file which must contain a `metadata.json` in the root, describing the content as specified in gulp's `ingest_zip` docs.",
|
|
742
|
+
),
|
|
604
743
|
context_name: str = typer.Option("sdk_context", "--context-name"),
|
|
605
744
|
flt: str | None = typer.Option(None, "--flt", help="JSON object for GulpIngestionFilter"),
|
|
606
745
|
reset_operation: bool = typer.Option(
|
|
@@ -691,6 +830,52 @@ def ingest_zip(
|
|
|
691
830
|
asyncio.run(_run())
|
|
692
831
|
|
|
693
832
|
|
|
833
|
+
@app.command("zip-create")
|
|
834
|
+
def ingest_zip_create(
|
|
835
|
+
output_zip: str = typer.Argument(
|
|
836
|
+
...,
|
|
837
|
+
help="Path to the ZIP file to create (supports $VARS and ~)",
|
|
838
|
+
),
|
|
839
|
+
path_patterns: list[str] = typer.Argument(
|
|
840
|
+
None,
|
|
841
|
+
help="One or more files, directories, or glob patterns to archive (supports $VARS and ~)",
|
|
842
|
+
),
|
|
843
|
+
paths_file: str | None = typer.Option(
|
|
844
|
+
None,
|
|
845
|
+
"--paths-file",
|
|
846
|
+
help="Text file with one file, directory, or glob pattern per line (supports $VARS and ~ per line)",
|
|
847
|
+
),
|
|
848
|
+
overwrite: bool = typer.Option(
|
|
849
|
+
False,
|
|
850
|
+
"--overwrite",
|
|
851
|
+
help="Overwrite the ZIP file if it already exists",
|
|
852
|
+
),
|
|
853
|
+
) -> None:
|
|
854
|
+
"""Create a ZIP archive from files, directories, or glob patterns."""
|
|
855
|
+
|
|
856
|
+
output_path = _resolve_path(output_zip)
|
|
857
|
+
source_paths = _expand_zip_source_patterns(path_patterns or [], paths_file)
|
|
858
|
+
|
|
859
|
+
if output_path.exists() and not overwrite:
|
|
860
|
+
raise typer.BadParameter(
|
|
861
|
+
f"Output ZIP already exists: {output_path}. Use --overwrite to replace it"
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
archived_count, archived_entries = _build_zip_from_sources(output_path, source_paths)
|
|
865
|
+
print_result(
|
|
866
|
+
{
|
|
867
|
+
"zip_file": str(output_path),
|
|
868
|
+
"sources": [str(path) for path, _ in source_paths],
|
|
869
|
+
"files_archived": archived_count,
|
|
870
|
+
"entries": archived_entries,
|
|
871
|
+
},
|
|
872
|
+
formatter=lambda data: console.print(
|
|
873
|
+
f"Created ZIP {data['zip_file']} from {len(data['sources'])} source"
|
|
874
|
+
f"{'s' if len(data['sources']) != 1 else ''} with {data['files_archived']} file(s)."
|
|
875
|
+
),
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
|
|
694
879
|
@app.command("raw")
|
|
695
880
|
def ingest_raw(
|
|
696
881
|
operation_id: str,
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|