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.
- gitlab_compliance-2.0.0.dist-info/METADATA +92 -0
- gitlab_compliance-2.0.0.dist-info/RECORD +50 -0
- gitlab_compliance-2.0.0.dist-info/WHEEL +4 -0
- gitlab_compliance-2.0.0.dist-info/entry_points.txt +4 -0
- gitlab_compliance-2.0.0.dist-info/licenses/LICENSE +21 -0
- src/__init__.py +1 -0
- src/compliance/__init__.py +1 -0
- src/compliance/api_config.py +94 -0
- src/compliance/api_enrichment.py +52 -0
- src/compliance/behave_support/environment.py +43 -0
- src/compliance/behave_support/steps/__init__.py +0 -0
- src/compliance/behave_support/steps/given_steps.py +154 -0
- src/compliance/behave_support/steps/then_steps.py +234 -0
- src/compliance/behave_support/steps/when_steps.py +168 -0
- src/compliance/console.py +95 -0
- src/compliance/container_fix.py +135 -0
- src/compliance/gitlab_api.py +94 -0
- src/compliance/image_versions.py +316 -0
- src/compliance/include_fix.py +112 -0
- src/compliance/include_versions.py +250 -0
- src/compliance/metadata.py +245 -0
- src/compliance/model.py +277 -0
- src/compliance/models.py +29 -0
- src/compliance/oci_registry.py +147 -0
- src/compliance/policy_doc.py +177 -0
- src/compliance/release_cache.py +51 -0
- src/compliance/render.py +360 -0
- src/compliance/runner.py +310 -0
- src/compliance/stash.py +203 -0
- src/compliance/supply_chain_fix.py +69 -0
- src/gitlab_compliance.py +636 -0
- src/gitlab_docs.py +40 -0
- src/modules/__init__.py +1 -0
- src/modules/command_reference.py +220 -0
- src/modules/common.py +144 -0
- src/modules/constants.py +22 -0
- src/modules/doc_controller.py +178 -0
- src/modules/logging.py +36 -0
- src/modules/pipeline_data.py +364 -0
- src/modules/release.py +343 -0
- src/modules/swagger_html.py +408 -0
- src/modules/yaml_lines.py +70 -0
- src/modules/yaml_md_table.py +29 -0
- src/properties/__init__.py +0 -0
- src/properties/extract_job_attribute.py +76 -0
- src/properties/includes.py +178 -0
- src/properties/inputs.py +86 -0
- src/properties/jobs.py +167 -0
- src/properties/variables.py +91 -0
- src/properties/workflows.py +54 -0
src/gitlab_compliance.py
ADDED
|
@@ -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)))
|
src/modules/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.36"
|