gitlab-compliance 2.0.0__py3-none-any.whl

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.
Files changed (50) hide show
  1. gitlab_compliance-2.0.0.dist-info/METADATA +92 -0
  2. gitlab_compliance-2.0.0.dist-info/RECORD +50 -0
  3. gitlab_compliance-2.0.0.dist-info/WHEEL +4 -0
  4. gitlab_compliance-2.0.0.dist-info/entry_points.txt +4 -0
  5. gitlab_compliance-2.0.0.dist-info/licenses/LICENSE +21 -0
  6. src/__init__.py +1 -0
  7. src/compliance/__init__.py +1 -0
  8. src/compliance/api_config.py +94 -0
  9. src/compliance/api_enrichment.py +52 -0
  10. src/compliance/behave_support/environment.py +43 -0
  11. src/compliance/behave_support/steps/__init__.py +0 -0
  12. src/compliance/behave_support/steps/given_steps.py +154 -0
  13. src/compliance/behave_support/steps/then_steps.py +234 -0
  14. src/compliance/behave_support/steps/when_steps.py +168 -0
  15. src/compliance/console.py +95 -0
  16. src/compliance/container_fix.py +135 -0
  17. src/compliance/gitlab_api.py +94 -0
  18. src/compliance/image_versions.py +316 -0
  19. src/compliance/include_fix.py +112 -0
  20. src/compliance/include_versions.py +250 -0
  21. src/compliance/metadata.py +245 -0
  22. src/compliance/model.py +277 -0
  23. src/compliance/models.py +29 -0
  24. src/compliance/oci_registry.py +147 -0
  25. src/compliance/policy_doc.py +177 -0
  26. src/compliance/release_cache.py +51 -0
  27. src/compliance/render.py +360 -0
  28. src/compliance/runner.py +310 -0
  29. src/compliance/stash.py +203 -0
  30. src/compliance/supply_chain_fix.py +69 -0
  31. src/gitlab_compliance.py +636 -0
  32. src/gitlab_docs.py +40 -0
  33. src/modules/__init__.py +1 -0
  34. src/modules/command_reference.py +220 -0
  35. src/modules/common.py +144 -0
  36. src/modules/constants.py +22 -0
  37. src/modules/doc_controller.py +178 -0
  38. src/modules/logging.py +36 -0
  39. src/modules/pipeline_data.py +364 -0
  40. src/modules/release.py +343 -0
  41. src/modules/swagger_html.py +408 -0
  42. src/modules/yaml_lines.py +70 -0
  43. src/modules/yaml_md_table.py +29 -0
  44. src/properties/__init__.py +0 -0
  45. src/properties/extract_job_attribute.py +76 -0
  46. src/properties/includes.py +178 -0
  47. src/properties/inputs.py +86 -0
  48. src/properties/jobs.py +167 -0
  49. src/properties/variables.py +91 -0
  50. src/properties/workflows.py +54 -0
@@ -0,0 +1,636 @@
1
+ """
2
+ gitlab-compliance entrypoint to auto generate gitlab-ci documentation from yml configuration files
3
+ Author: Charlie Smith
4
+ """
5
+
6
+ # Import Thirdparty Libraries
7
+ import os
8
+ import shutil
9
+ from datetime import datetime
10
+
11
+ import click
12
+
13
+ import src.gitlab_docs as _gitlab_docs
14
+ import src.modules.doc_controller as md_writer
15
+ import src.properties.includes as includes
16
+ import src.properties.inputs as inputs
17
+ import src.properties.jobs as jobs
18
+ import src.properties.variables as variables
19
+ import src.properties.workflows as workflows
20
+ from src.compliance.metadata import build_policy_catalog
21
+ from src.compliance.oci_registry import (
22
+ DEFAULT_POLICY_DIR,
23
+ is_oci_reference,
24
+ pull_policies,
25
+ push_policies,
26
+ resolve_features_dir,
27
+ )
28
+ from src.compliance.policy_doc import render_policy_catalog
29
+ from src.compliance.render import render_compliance_report
30
+ from src.compliance.runner import run_compliance
31
+ from src.modules.command_reference import dumps
32
+ from src.modules.constants import (
33
+ COMPLIANCE_DEFAULT_OUTPUT_FILES,
34
+ COMPLIANCE_OUTPUT_FORMATS,
35
+ DEFAULT_OUTPUT_FILES,
36
+ POLICY_DOC_DEFAULT_OUTPUT_FILES,
37
+ POLICY_DOC_OUTPUT_FORMATS,
38
+ SUPPORTED_OUTPUT_FORMATS,
39
+ )
40
+ from src.modules.doc_controller import (
41
+ add_between_markers,
42
+ remove_duplicate_headings,
43
+ update_marked_block,
44
+ )
45
+ from src.modules.logging import logger
46
+ from src.modules.pipeline_data import collect_pipeline_data
47
+ from src.modules.release import release_notes
48
+ from src.modules.swagger_html import render_swagger_html
49
+ from src.properties.extract_job_attribute import get_job_attribute
50
+
51
+ __all__ = [
52
+ "DEFAULT_POLICY_DIR",
53
+ "build_policy_catalog",
54
+ "check",
55
+ "dumps",
56
+ "generate",
57
+ "generate_html",
58
+ "get_attributes",
59
+ "gitlab_compliance",
60
+ "gitlab_docs",
61
+ "is_oci_reference",
62
+ "policies",
63
+ "policies_doc",
64
+ "policies_pull",
65
+ "policies_push",
66
+ "pull_policies",
67
+ "push_policies",
68
+ "release_notes",
69
+ "render_compliance_report",
70
+ "resolve_features_dir",
71
+ "run_compliance",
72
+ ]
73
+
74
+
75
+ def _generate_markdown(
76
+ OUTPUT_FILE,
77
+ GLDOCS_CONFIG_FILE,
78
+ detailed=False,
79
+ ):
80
+ ENABLE_WORKFLOW_DOCUMENTATION = detailed
81
+ update_marked_block(file_path=OUTPUT_FILE, content="\n")
82
+ bootstrap = f"""# GITLAB COMPLIANCE - {GLDOCS_CONFIG_FILE}"""
83
+ add_between_markers(file_path=OUTPUT_FILE, content=bootstrap)
84
+ inputs.document_inputs(
85
+ GLDOCS_CONFIG_FILE=GLDOCS_CONFIG_FILE,
86
+ DISABLE_TITLE=True,
87
+ OUTPUT_FILE=OUTPUT_FILE,
88
+ )
89
+ variables.document_variables(
90
+ GLDOCS_CONFIG_FILE=GLDOCS_CONFIG_FILE,
91
+ DISABLE_TITLE=True,
92
+ OUTPUT_FILE=OUTPUT_FILE,
93
+ )
94
+ includes.document_includes(
95
+ GLDOCS_CONFIG_FILE=GLDOCS_CONFIG_FILE,
96
+ DISABLE_TITLE=True,
97
+ DISABLE_TYPE_HEADING=False,
98
+ OUTPUT_FILE=OUTPUT_FILE,
99
+ )
100
+ if ENABLE_WORKFLOW_DOCUMENTATION is True:
101
+ workflows.document_workflows(
102
+ GLDOCS_CONFIG_FILE=GLDOCS_CONFIG_FILE,
103
+ DISABLE_TITLE=True,
104
+ OUTPUT_FILE=OUTPUT_FILE,
105
+ )
106
+ jobs.get_jobs(
107
+ GLDOCS_CONFIG_FILE=GLDOCS_CONFIG_FILE,
108
+ DISABLE_TITLE=True,
109
+ DISABLE_TYPE_HEADING=False,
110
+ OUTPUT_FILE=OUTPUT_FILE,
111
+ detailed=detailed,
112
+ )
113
+
114
+
115
+ def _generate_html(
116
+ OUTPUT_FILE,
117
+ GLDOCS_CONFIG_FILE,
118
+ detailed=False,
119
+ ):
120
+ pipeline_data = collect_pipeline_data(
121
+ config_file=GLDOCS_CONFIG_FILE,
122
+ detailed=detailed,
123
+ )
124
+ html_output = render_swagger_html(pipeline_data)
125
+ with open(OUTPUT_FILE, "w", encoding="utf-8") as output_file:
126
+ output_file.write(html_output)
127
+
128
+
129
+ def _resolve_output_file(output_format, output_file):
130
+ if output_file:
131
+ return output_file
132
+ return DEFAULT_OUTPUT_FILES[output_format]
133
+
134
+
135
+ _LEGACY_CLI_NAME = "gitlab-docs"
136
+
137
+
138
+ class _DualBrandCliGroup(click.Group):
139
+ """Shared CLI group; warns when invoked via the legacy ``gitlab-compliance`` script name."""
140
+
141
+ _LEGACY_NOTICE = (
142
+ "Note: `gitlab-docs` is deprecated in favor of `gitlab-compliance`. "
143
+ "The `gitlab-docs` command will be removed in a future release."
144
+ )
145
+
146
+ def _emit_legacy_notice(self, ctx) -> None:
147
+ if ctx.info_name == _LEGACY_CLI_NAME:
148
+ click.secho(self._LEGACY_NOTICE, fg="yellow", err=True)
149
+
150
+ def invoke(self, ctx):
151
+ self._emit_legacy_notice(ctx)
152
+ return super().invoke(ctx)
153
+
154
+ def format_help(self, ctx, formatter):
155
+ if ctx.info_name == _LEGACY_CLI_NAME:
156
+ formatter.write(click.style(f"{self._LEGACY_NOTICE}\n\n", fg="yellow"))
157
+ return super().format_help(ctx, formatter)
158
+
159
+
160
+ @click.group(cls=_DualBrandCliGroup)
161
+ def gitlab_compliance():
162
+ """
163
+ GitLab CI compliance and pipeline documentation.
164
+
165
+ Run Gherkin policies with ``check`` (and optional GitLab API settings),
166
+ manage policy bundles with ``policies``, or generate Markdown/HTML
167
+ documentation from pipeline YAML.
168
+ """
169
+ pass
170
+
171
+
172
+ # Backward-compatible alias for imports and ``python -m src.gitlab_compliance``.
173
+ gitlab_docs = gitlab_compliance
174
+
175
+
176
+ # ENABLE_WORKFLOW_DOCUMENTATION = os.getenv("ENABLE_WORKFLOW_DOCUMENTATION", False)
177
+ @click.command()
178
+ @click.option(
179
+ "--attributes",
180
+ "-a",
181
+ "attributes",
182
+ required=False,
183
+ help="Pass a comma seperated list of gitlab ci yml attributes",
184
+ default="README.md",
185
+ )
186
+ @click.option(
187
+ "--output-file",
188
+ "-o",
189
+ "OUTPUT_FILE",
190
+ required=False,
191
+ help="Output location of the markdown documentation.",
192
+ default="README.md",
193
+ )
194
+ @click.option(
195
+ "--input-config",
196
+ "-i",
197
+ "GLDOCS_CONFIG_FILE",
198
+ required=False,
199
+ help="The Gitlab CI Input configuration file to generated documentation from.",
200
+ default=".gitlab-ci.yml",
201
+ )
202
+ @click.option(
203
+ "--json",
204
+ "-j",
205
+ "json_format",
206
+ required=False,
207
+ default=False,
208
+ type=bool,
209
+ help="Return results in json format.",
210
+ )
211
+ def get_attributes(OUTPUT_FILE, GLDOCS_CONFIG_FILE, attributes, json_format):
212
+ """
213
+ Compared to the generate command, the get-attribute command allows you to pass the properties you wish to document and produces a markdown table.
214
+ Args:
215
+ OUTPUT_FILE (_type_): _description_
216
+ GLDOCS_CONFIG_FILE (_type_): _description_
217
+ attributes (_type_): _description_
218
+ json (_type_): _description_
219
+ """
220
+ logger.info(f"Discovering attributes {attributes} from your gitlab-ci yml.")
221
+
222
+ get_job_attribute(
223
+ GLDOCS_CONFIG_FILE=GLDOCS_CONFIG_FILE,
224
+ DISABLE_TITLE=False,
225
+ DISABLE_TYPE_HEADING=False,
226
+ OUTPUT_FILE=OUTPUT_FILE,
227
+ attributes=attributes,
228
+ json_format=json_format,
229
+ )
230
+
231
+
232
+ @click.command()
233
+ @click.option(
234
+ "--detailed",
235
+ required=False,
236
+ help="Will include workflow and rules from jobs.",
237
+ is_flag=True,
238
+ default=False,
239
+ )
240
+ @click.option(
241
+ "--format",
242
+ "-f",
243
+ "output_format",
244
+ required=False,
245
+ type=click.Choice(SUPPORTED_OUTPUT_FORMATS, case_sensitive=False),
246
+ default="markdown",
247
+ help="Output format for generated documentation.",
248
+ )
249
+ @click.option(
250
+ "--dry-mode",
251
+ "-d",
252
+ "DRY_MODE",
253
+ required=False,
254
+ help="If set will disable documentation from being written",
255
+ is_flag=True,
256
+ default=False,
257
+ )
258
+ @click.option(
259
+ "--output-file",
260
+ "-o",
261
+ "OUTPUT_FILE",
262
+ required=False,
263
+ help="Output location of the generated documentation.",
264
+ default=None,
265
+ )
266
+ @click.option(
267
+ "--input-config",
268
+ "-i",
269
+ "GLDOCS_CONFIG_FILE",
270
+ required=False,
271
+ help="The Gitlab CI Input configuration file to generated documentation from.",
272
+ default=".gitlab-ci.yml",
273
+ )
274
+ def generate(detailed, output_format, OUTPUT_FILE, DRY_MODE, GLDOCS_CONFIG_FILE):
275
+ """
276
+ Will scan through your gitlab-ci yml and build documentation from the yml.
277
+ """
278
+ output_format = output_format.lower()
279
+ OUTPUT_FILE = _resolve_output_file(output_format, OUTPUT_FILE)
280
+ logger.success("Welcome to Gitlab Compliance")
281
+
282
+ if DRY_MODE:
283
+ logger.info(
284
+ f"Dry mode enabled; skipping write for {GLDOCS_CONFIG_FILE} ({output_format})"
285
+ )
286
+ return
287
+
288
+ if output_format == "html":
289
+ _generate_html(
290
+ OUTPUT_FILE=OUTPUT_FILE,
291
+ GLDOCS_CONFIG_FILE=GLDOCS_CONFIG_FILE,
292
+ detailed=detailed,
293
+ )
294
+ else:
295
+ _generate_markdown(
296
+ OUTPUT_FILE=OUTPUT_FILE,
297
+ GLDOCS_CONFIG_FILE=GLDOCS_CONFIG_FILE,
298
+ detailed=detailed,
299
+ )
300
+
301
+ logger.info(
302
+ f"Successfully generated {output_format} documentation for "
303
+ f"{GLDOCS_CONFIG_FILE} here: {OUTPUT_FILE}"
304
+ )
305
+
306
+
307
+ @click.command(
308
+ hidden=True,
309
+ deprecated=True,
310
+ help="Deprecated: use `generate --format html` instead.",
311
+ )
312
+ @click.option(
313
+ "--detailed",
314
+ required=False,
315
+ help="Will include workflow and rules from jobs.",
316
+ is_flag=True,
317
+ default=False,
318
+ )
319
+ @click.option(
320
+ "--output-file",
321
+ "-o",
322
+ "OUTPUT_FILE",
323
+ required=False,
324
+ help="Output location of the HTML documentation.",
325
+ default="gitlab-compliance.html",
326
+ )
327
+ @click.option(
328
+ "--input-config",
329
+ "-i",
330
+ "GLDOCS_CONFIG_FILE",
331
+ required=False,
332
+ help="The Gitlab CI Input configuration file to generated documentation from.",
333
+ default=".gitlab-ci.yml",
334
+ )
335
+ def generate_html(detailed, OUTPUT_FILE, GLDOCS_CONFIG_FILE):
336
+ """
337
+ Generate a Swagger-style HTML page from your gitlab-ci yml.
338
+ """
339
+ ctx = click.get_current_context(silent=True)
340
+ if ctx is not None:
341
+ ctx.invoke(
342
+ generate,
343
+ detailed=detailed,
344
+ output_format="html",
345
+ OUTPUT_FILE=OUTPUT_FILE,
346
+ DRY_MODE=False,
347
+ GLDOCS_CONFIG_FILE=GLDOCS_CONFIG_FILE,
348
+ )
349
+ return
350
+
351
+ OUTPUT_FILE = _resolve_output_file("html", OUTPUT_FILE)
352
+ logger.success("Welcome to GitLab Docs HTML")
353
+ _generate_html(
354
+ OUTPUT_FILE=OUTPUT_FILE,
355
+ GLDOCS_CONFIG_FILE=GLDOCS_CONFIG_FILE,
356
+ detailed=detailed,
357
+ )
358
+ logger.info(
359
+ f"Successfully generated html documentation for "
360
+ f"{GLDOCS_CONFIG_FILE} here: {OUTPUT_FILE}"
361
+ )
362
+
363
+
364
+ def _resolve_compliance_output(output_format, output_file):
365
+ if output_file:
366
+ return output_file
367
+ if output_format in COMPLIANCE_DEFAULT_OUTPUT_FILES:
368
+ return COMPLIANCE_DEFAULT_OUTPUT_FILES[output_format]
369
+ return None
370
+
371
+
372
+ def _resolve_policies_dir(
373
+ features_dir: str, policy_cache_dir: str | None = None
374
+ ) -> tuple[str, str]:
375
+ if os.path.isdir(features_dir):
376
+ resolved = os.path.abspath(features_dir)
377
+ return resolved, resolved
378
+ resolved = _gitlab_docs.resolve_features_dir(
379
+ features_dir, cache_dir=policy_cache_dir
380
+ )
381
+ return resolved, features_dir
382
+
383
+
384
+ @click.command("check")
385
+ @click.option(
386
+ "--features",
387
+ "-f",
388
+ "features_dir",
389
+ required=True,
390
+ help="Directory containing compliance policy .feature files or an OCI reference (oci://registry.example.com/policies:1.0.0).",
391
+ )
392
+ @click.option(
393
+ "--pipeline",
394
+ "-p",
395
+ "pipeline_file",
396
+ required=False,
397
+ default=".gitlab-ci.yml",
398
+ help="Path to the GitLab CI pipeline YAML file.",
399
+ )
400
+ @click.option(
401
+ "--format",
402
+ "output_format",
403
+ required=False,
404
+ type=click.Choice(COMPLIANCE_OUTPUT_FORMATS, case_sensitive=False),
405
+ default="console",
406
+ help="Output format for the compliance report.",
407
+ )
408
+ @click.option(
409
+ "--output-file",
410
+ "-o",
411
+ "output_file",
412
+ required=False,
413
+ default=None,
414
+ help="Write rendered report to this file (markdown, html, mr-comment).",
415
+ )
416
+ @click.option(
417
+ "--include-nested/--no-include-nested",
418
+ default=True,
419
+ help="Resolve nested local include files into the compliance stash.",
420
+ )
421
+ @click.option(
422
+ "--gitlab-url",
423
+ default=None,
424
+ help="GitLab instance URL (default: CI_SERVER_URL or https://gitlab.com).",
425
+ )
426
+ @click.option(
427
+ "--token",
428
+ default=None,
429
+ help="GitLab API token (default: GITLAB_TOKEN or CI_JOB_TOKEN).",
430
+ )
431
+ @click.option(
432
+ "--project",
433
+ default=None,
434
+ help="GitLab project path or ID for API-backed policy checks.",
435
+ )
436
+ @click.option(
437
+ "--group",
438
+ default=None,
439
+ help="GitLab group path or ID for API-backed policy checks.",
440
+ )
441
+ @click.option(
442
+ "--strict",
443
+ is_flag=True,
444
+ default=False,
445
+ help="Fail API-backed scenarios when connection info is missing (default: skip).",
446
+ )
447
+ @click.option(
448
+ "--update",
449
+ is_flag=True,
450
+ default=False,
451
+ help="Pull the latest policies from an OCI registry before running checks.",
452
+ )
453
+ @click.option(
454
+ "--policy-cache-dir",
455
+ default=None,
456
+ help="Directory used when pulling OCI policy bundles (default: system temp).",
457
+ )
458
+ @click.option(
459
+ "--dry-run",
460
+ is_flag=True,
461
+ default=False,
462
+ help="Parse and list scenarios without asserting.",
463
+ )
464
+ @click.option(
465
+ "--fix",
466
+ is_flag=True,
467
+ default=False,
468
+ help="Auto-fix outdated include refs and pin container images to sha256 digests.",
469
+ )
470
+ def check(
471
+ features_dir,
472
+ pipeline_file,
473
+ output_format,
474
+ output_file,
475
+ include_nested,
476
+ gitlab_url,
477
+ token,
478
+ project,
479
+ group,
480
+ strict,
481
+ update,
482
+ policy_cache_dir,
483
+ dry_run,
484
+ fix,
485
+ ):
486
+ """
487
+ Run Gherkin compliance policies against GitLab CI YAML and optional API settings.
488
+ """
489
+ output_format = output_format.lower()
490
+
491
+ if (
492
+ update
493
+ and _gitlab_docs.is_oci_reference(features_dir)
494
+ and policy_cache_dir
495
+ and os.path.isdir(policy_cache_dir)
496
+ ):
497
+ shutil.rmtree(policy_cache_dir)
498
+ result = _gitlab_docs.run_compliance(
499
+ features_dir=features_dir,
500
+ pipeline_file=pipeline_file,
501
+ include_nested=include_nested,
502
+ gitlab_url=gitlab_url,
503
+ token=token,
504
+ project=project,
505
+ group=group,
506
+ strict=strict,
507
+ dry_run=dry_run,
508
+ output_format=output_format,
509
+ policies_source=features_dir,
510
+ policy_cache_dir=policy_cache_dir,
511
+ fix=fix,
512
+ )
513
+
514
+ if output_format != "console":
515
+ report = _gitlab_docs.render_compliance_report(
516
+ result=result,
517
+ pipeline_file=pipeline_file,
518
+ features_dir=features_dir,
519
+ output_format=output_format,
520
+ )
521
+ target = _resolve_compliance_output(output_format, output_file)
522
+ if target:
523
+ with open(target, "w", encoding="utf-8") as handle:
524
+ handle.write(report)
525
+ logger.info(f"Compliance report written to {target}")
526
+ else:
527
+ click.echo(report)
528
+
529
+ if result.success:
530
+ logger.info(f"Compliance passed for {pipeline_file}")
531
+ else:
532
+ logger.error(f"Compliance failed for {pipeline_file}")
533
+ raise SystemExit(result.exit_code)
534
+
535
+
536
+ def _resolve_policy_doc_output(output_format, output_file):
537
+ if output_file:
538
+ return output_file
539
+ if output_format in POLICY_DOC_DEFAULT_OUTPUT_FILES:
540
+ return POLICY_DOC_DEFAULT_OUTPUT_FILES[output_format]
541
+ return None
542
+
543
+
544
+ @click.group()
545
+ def policies():
546
+ """Manage compliance policy bundles (catalog, OCI push/pull)."""
547
+ pass
548
+
549
+
550
+ @policies.command("doc")
551
+ @click.option(
552
+ "--features",
553
+ "-f",
554
+ "features_dir",
555
+ required=True,
556
+ help="Directory containing compliance policy .feature files.",
557
+ )
558
+ @click.option(
559
+ "--format",
560
+ "output_format",
561
+ required=False,
562
+ type=click.Choice(POLICY_DOC_OUTPUT_FORMATS, case_sensitive=False),
563
+ default="markdown",
564
+ help="Output format for the policy catalog.",
565
+ )
566
+ @click.option(
567
+ "--output-file",
568
+ "-o",
569
+ "output_file",
570
+ required=False,
571
+ default=None,
572
+ help="Write the policy catalog to this file.",
573
+ )
574
+ def policies_doc(features_dir, output_format, output_file):
575
+ """
576
+ Generate a searchable policy catalog from Conftest-style # METADATA annotations.
577
+ """
578
+ output_format = output_format.lower()
579
+ resolved_dir, _source = _resolve_policies_dir(features_dir)
580
+ catalog = _gitlab_docs.build_policy_catalog(resolved_dir)
581
+ report = render_policy_catalog(catalog, _source, output_format)
582
+ target = _resolve_policy_doc_output(output_format, output_file)
583
+ if target:
584
+ with open(target, "w", encoding="utf-8") as handle:
585
+ handle.write(report)
586
+ logger.info(f"Policy catalog written to {target}")
587
+ else:
588
+ click.echo(report)
589
+
590
+
591
+ @policies.command("push")
592
+ @click.option(
593
+ "--features",
594
+ "-f",
595
+ "features_dir",
596
+ required=True,
597
+ help="Directory containing compliance policy .feature files to publish.",
598
+ )
599
+ @click.argument("target")
600
+ def policies_push(features_dir, target):
601
+ """
602
+ Push a compliance policy bundle to an OCI registry (Conftest-style).
603
+ """
604
+ digest = _gitlab_docs.push_policies(features_dir, target)
605
+ logger.success(f"Pushed policy bundle to {target}")
606
+ if digest:
607
+ logger.info(f"Digest: {digest}")
608
+
609
+
610
+ @policies.command("pull")
611
+ @click.argument("target")
612
+ @click.option(
613
+ "--output-dir",
614
+ "-o",
615
+ "output_dir",
616
+ default=_gitlab_docs.DEFAULT_POLICY_DIR,
617
+ show_default=True,
618
+ help="Directory to extract pulled policies into.",
619
+ )
620
+ def policies_pull(target, output_dir):
621
+ """
622
+ Pull a compliance policy bundle from an OCI registry.
623
+ """
624
+ pulled_to = _gitlab_docs.pull_policies(target, output_dir=output_dir)
625
+ logger.success(f"Pulled policies to {pulled_to}")
626
+
627
+
628
+ gitlab_compliance.add_command(get_attributes)
629
+ gitlab_compliance.add_command(dumps)
630
+ gitlab_compliance.add_command(generate)
631
+ gitlab_compliance.add_command(generate_html)
632
+ gitlab_compliance.add_command(check)
633
+ gitlab_compliance.add_command(policies)
634
+ gitlab_compliance.add_command(release_notes)
635
+ if __name__ == "__main__": # pragma: no cover
636
+ gitlab_compliance(obj={})
src/gitlab_docs.py ADDED
@@ -0,0 +1,40 @@
1
+ """Backward-compatible import path for ``src.gitlab_compliance``.
2
+
3
+ Tests and older integrations import ``src.gitlab_docs``. Patchable CLI
4
+ dependencies are defined here so monkeypatches on this module affect the
5
+ commands in ``gitlab_compliance``.
6
+ """
7
+
8
+ from src.compliance.metadata import build_policy_catalog
9
+ from src.compliance.oci_registry import (
10
+ DEFAULT_POLICY_DIR,
11
+ is_oci_reference,
12
+ pull_policies,
13
+ push_policies,
14
+ resolve_features_dir,
15
+ )
16
+ from src.compliance.render import render_compliance_report
17
+ from src.compliance.runner import run_compliance
18
+
19
+ __all__ = [
20
+ "DEFAULT_POLICY_DIR",
21
+ "build_policy_catalog",
22
+ "is_oci_reference",
23
+ "pull_policies",
24
+ "push_policies",
25
+ "render_compliance_report",
26
+ "resolve_features_dir",
27
+ "run_compliance",
28
+ ]
29
+
30
+
31
+ def __getattr__(name: str):
32
+ import src.gitlab_compliance as mod
33
+
34
+ return getattr(mod, name)
35
+
36
+
37
+ def __dir__():
38
+ import src.gitlab_compliance as mod
39
+
40
+ return sorted(set(__all__) | set(dir(mod)))
@@ -0,0 +1 @@
1
+ __version__ = "0.0.36"