opentide 0.1.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.
- opentide/__init__.py +8 -0
- opentide/_version.py +24 -0
- opentide/ci/__init__.py +6 -0
- opentide/ci/azure.py +174 -0
- opentide/ci/discovery.py +31 -0
- opentide/ci/github.py +279 -0
- opentide/ci/gitlab.py +176 -0
- opentide/ci/inflight.py +283 -0
- opentide/ci/models.py +86 -0
- opentide/ci/render.py +23 -0
- opentide/ci/stages.py +64 -0
- opentide/cli/__init__.py +540 -0
- opentide/cli/context.py +72 -0
- opentide/cli/enums.py +113 -0
- opentide/cli/exit_codes.py +59 -0
- opentide/cli/output.py +101 -0
- opentide/cli/services/__init__.py +1 -0
- opentide/cli/services/ci_generator.py +19 -0
- opentide/cli/services/deploy.py +123 -0
- opentide/cli/services/document.py +46 -0
- opentide/cli/services/export.py +49 -0
- opentide/cli/services/extraction.py +54 -0
- opentide/cli/services/generation.py +151 -0
- opentide/cli/services/info.py +74 -0
- opentide/cli/services/setup/__init__.py +17 -0
- opentide/cli/services/setup/ci.py +68 -0
- opentide/cli/services/setup/interactive.py +158 -0
- opentide/cli/services/setup/mcp.py +90 -0
- opentide/cli/services/setup/orchestrator.py +281 -0
- opentide/cli/services/setup/platforms.py +74 -0
- opentide/cli/services/setup/repo.py +128 -0
- opentide/cli/services/setup/skills.py +245 -0
- opentide/cli/services/setup/skills_registry.py +252 -0
- opentide/cli/services/setup/templates.py +69 -0
- opentide/cli/services/setup/vscode.py +145 -0
- opentide/cli/services/validation.py +246 -0
- opentide/cli/setup_app.py +498 -0
- opentide/core/__init__.py +5 -0
- opentide/core/chaining.py +32 -0
- opentide/core/debug.py +17 -0
- opentide/core/environment.py +111 -0
- opentide/core/errors.py +82 -0
- opentide/core/files.py +182 -0
- opentide/core/index_manager.py +52 -0
- opentide/core/io.py +113 -0
- opentide/core/logging/__init__.py +35 -0
- opentide/core/logging/config.py +201 -0
- opentide/core/logging/console.py +89 -0
- opentide/core/logging/render.py +63 -0
- opentide/core/object_refs.py +27 -0
- opentide/core/registry.py +601 -0
- opentide/core/root.py +41 -0
- opentide/core/runtime.py +23 -0
- opentide/core/time.py +21 -0
- opentide/core/types.py +23 -0
- opentide/core/typing.py +5 -0
- opentide/data/__init__.py +1 -0
- opentide/data/configurations/__init__.py +1 -0
- opentide/data/configurations/deployment.toml +61 -0
- opentide/data/configurations/documentation.toml +38 -0
- opentide/data/configurations/global.toml +54 -0
- opentide/data/configurations/paths.toml +46 -0
- opentide/data/configurations/platforms/__init__.py +1 -0
- opentide/data/configurations/platforms/carbon_black_cloud.toml +29 -0
- opentide/data/configurations/platforms/crowdstrike.toml +31 -0
- opentide/data/configurations/platforms/defender_for_endpoint.toml +32 -0
- opentide/data/configurations/platforms/harfanglab.toml +31 -0
- opentide/data/configurations/platforms/sentinel.toml +34 -0
- opentide/data/configurations/platforms/sentinel_one.toml +33 -0
- opentide/data/configurations/platforms/splunk.toml +68 -0
- opentide/data/configurations/resources.toml +2 -0
- opentide/data/configurations/schema.toml +32 -0
- opentide/data/configurations/visibility.toml +4 -0
- opentide/data/external/.gitkeep +0 -0
- opentide/data/log_sources/LOG0000 - Log Source TEMPLATE.yaml +38 -0
- opentide/data/log_sources/LOG0001 - Proto AWS Security Hub.yaml +172 -0
- opentide/data/log_sources/Proto Doc AWS Security Hub.md +138 -0
- opentide/data/pins/objective.toml +15 -0
- opentide/data/pins/rule.toml +10 -0
- opentide/data/pins/threat.toml +29 -0
- opentide/data/setup/mcp/README.md +17 -0
- opentide/data/setup/mcp/claude-code.json +7 -0
- opentide/data/setup/mcp/cursor.json +7 -0
- opentide/data/setup/mcp/generic.json +7 -0
- opentide/data/setup/mcp/vscode.json +7 -0
- opentide/data/setup/skills/AGENTS.md.template +30 -0
- opentide/data/setup/skills/CLAUDE.md.template +29 -0
- opentide/data/setup/skills/copilot-instructions.md +21 -0
- opentide/data/setup/skills/detection-ops/SKILL.md +47 -0
- opentide/data/setup/vscode/settings.fragment.json +5 -0
- opentide/data/skills/manifest.json +143 -0
- opentide/data/vocabulary/actors.vocab.toml +6766 -0
- opentide/data/vocabulary/alert_severity.vocab.toml +30 -0
- opentide/data/vocabulary/att&ck.groups.vocab.toml +894 -0
- opentide/data/vocabulary/att&ck.vocab.toml +6188 -0
- opentide/data/vocabulary/chaining_relations.vocab.toml +72 -0
- opentide/data/vocabulary/collection.vocab.toml +386 -0
- opentide/data/vocabulary/criticality.vocab.toml +48 -0
- opentide/data/vocabulary/datasources.vocab.toml +721 -0
- opentide/data/vocabulary/detection.composition.vocab.toml +35 -0
- opentide/data/vocabulary/detection.methodology.vocab.toml +55 -0
- opentide/data/vocabulary/detection.types.vocab.toml +25 -0
- opentide/data/vocabulary/efforts.vocab.toml +22 -0
- opentide/data/vocabulary/feasibility.vocab.toml +40 -0
- opentide/data/vocabulary/impact.vocab.toml +101 -0
- opentide/data/vocabulary/killchain.vocab.toml +133 -0
- opentide/data/vocabulary/level.vocab.toml +35 -0
- opentide/data/vocabulary/leverage.vocab.toml +106 -0
- opentide/data/vocabulary/malapi.vocab.toml +2254 -0
- opentide/data/vocabulary/maturity.vocab.toml +60 -0
- opentide/data/vocabulary/mitigations.vocab.toml +660 -0
- opentide/data/vocabulary/objectives.vocab.toml +84 -0
- opentide/data/vocabulary/pap.vocab.toml +30 -0
- opentide/data/vocabulary/resources.vocab.toml +41 -0
- opentide/data/vocabulary/responders.vocab.toml +7 -0
- opentide/data/vocabulary/rsit.vocab.toml +201 -0
- opentide/data/vocabulary/scheduling.vocab.toml +93 -0
- opentide/data/vocabulary/sectors.vocab.toml +253 -0
- opentide/data/vocabulary/severity.vocab.toml +36 -0
- opentide/data/vocabulary/signal.entities.vocab.toml +188 -0
- opentide/data/vocabulary/sophistication.vocab.toml +49 -0
- opentide/data/vocabulary/stakeholders.vocab.toml +46 -0
- opentide/data/vocabulary/surface.vocab.toml +4528 -0
- opentide/data/vocabulary/tier.vocab.toml +49 -0
- opentide/data/vocabulary/tlp.vocab.toml +41 -0
- opentide/data/vocabulary/viability.vocab.toml +53 -0
- opentide/data/vocabulary/violation.vocab.toml +41 -0
- opentide/deployment/__init__.py +53 -0
- opentide/deployment/ci.py +49 -0
- opentide/deployment/git_backend.py +192 -0
- opentide/deployment/git_repo.py +276 -0
- opentide/deployment/planning.py +364 -0
- opentide/deployment/preview.py +90 -0
- opentide/deployment/utils.py +226 -0
- opentide/documentation/__init__.py +78 -0
- opentide/documentation/api.py +138 -0
- opentide/documentation/catalog.py +427 -0
- opentide/documentation/cli.py +328 -0
- opentide/documentation/config.py +60 -0
- opentide/documentation/context.py +38 -0
- opentide/documentation/diagram/__init__.py +13 -0
- opentide/documentation/diagram/builders.py +186 -0
- opentide/documentation/diagram/flowchart.py +41 -0
- opentide/documentation/diagram/graph.py +48 -0
- opentide/documentation/diagram/mindmap.py +17 -0
- opentide/documentation/diagram/sanitize.py +37 -0
- opentide/documentation/format/__init__.py +6 -0
- opentide/documentation/format/azure_devops.py +50 -0
- opentide/documentation/format/factory.py +21 -0
- opentide/documentation/format/generic.py +17 -0
- opentide/documentation/format/github.py +17 -0
- opentide/documentation/format/gitlab.py +49 -0
- opentide/documentation/format/protocol.py +114 -0
- opentide/documentation/markdown/builder.py +8 -0
- opentide/documentation/markdown/links.py +117 -0
- opentide/documentation/objects/__init__.py +14 -0
- opentide/documentation/objects/base.py +79 -0
- opentide/documentation/objects/objective.py +45 -0
- opentide/documentation/objects/rule.py +47 -0
- opentide/documentation/objects/threat.py +84 -0
- opentide/documentation/parts/__init__.py +5 -0
- opentide/documentation/parts/sections.py +585 -0
- opentide/documentation/publish/index.py +178 -0
- opentide/documentation/publish/paths.py +33 -0
- opentide/documentation/publish/writer.py +11 -0
- opentide/documentation/types.py +46 -0
- opentide/documentation/vocabulary.py +51 -0
- opentide/export/attack_navigator_layer.py +160 -0
- opentide/export/explorer_export.py +238 -0
- opentide/export/playbook_map.py +42 -0
- opentide/export/revisions_export.py +57 -0
- opentide/export/table_export.py +129 -0
- opentide/extraction/__init__.py +1 -0
- opentide/extraction/malapi.py +94 -0
- opentide/extraction/mde_importer.py +228 -0
- opentide/extraction/sentinel_importer.py +387 -0
- opentide/generation/__init__.py +34 -0
- opentide/generation/artifact_gate.py +135 -0
- opentide/generation/framework.py +530 -0
- opentide/generation/model_json_schema.py +21 -0
- opentide/generation/pydantic_metaschema.py +345 -0
- opentide/generation/pydantic_schemas.py +111 -0
- opentide/generation/pydantic_templates.py +48 -0
- opentide/generation/router_schema.py +46 -0
- opentide/generation/schema.py +46 -0
- opentide/generation/schema_pipeline.py +894 -0
- opentide/generation/schema_utils.py +14 -0
- opentide/generation/template.py +10 -0
- opentide/generation/template_engine.py +289 -0
- opentide/generation/template_renderer.py +103 -0
- opentide/generation/vocabulary.py +486 -0
- opentide/generation/vscode_snippets.py +83 -0
- opentide/indexing/__init__.py +4 -0
- opentide/indexing/inflight.py +304 -0
- opentide/indexing/inflight_change.py +127 -0
- opentide/indexing/inflight_shard.py +71 -0
- opentide/indexing/object_vocab.py +138 -0
- opentide/loading/__init__.py +13 -0
- opentide/loading/compat.py +20 -0
- opentide/loading/config_loader.py +64 -0
- opentide/loading/object_loader.py +95 -0
- opentide/loading/objective_loader.py +18 -0
- opentide/loading/platform_loader.py +465 -0
- opentide/loading/rule_loader.py +58 -0
- opentide/mcp_server/__init__.py +1 -0
- opentide/mcp_server/catalog.py +114 -0
- opentide/mcp_server/constants.py +8 -0
- opentide/mcp_server/resources.py +96 -0
- opentide/mcp_server/server.py +166 -0
- opentide/mcp_server/tools.py +148 -0
- opentide/models/__init__.py +58 -0
- opentide/models/base.py +49 -0
- opentide/models/deployment_enums.py +49 -0
- opentide/models/enums.py +43 -0
- opentide/models/metadata.py +59 -0
- opentide/models/migration.py +24 -0
- opentide/models/object_types.py +17 -0
- opentide/models/objective.py +82 -0
- opentide/models/platform.py +157 -0
- opentide/models/platform_configs.py +321 -0
- opentide/models/platform_schema.py +200 -0
- opentide/models/response.py +24 -0
- opentide/models/results.py +18 -0
- opentide/models/rule.py +116 -0
- opentide/models/schema_registry.py +138 -0
- opentide/models/system_config.py +238 -0
- opentide/models/threat.py +58 -0
- opentide/models/version.py +102 -0
- opentide/models/visibility.py +59 -0
- opentide/models/vocab_pins.py +67 -0
- opentide/mutation/__init__.py +1 -0
- opentide/mutation/file_name.py +60 -0
- opentide/mutation/promotion.py +213 -0
- opentide/mutation/references.py +138 -0
- opentide/mutation/security_domain.py +122 -0
- opentide/package/__init__.py +5 -0
- opentide/package/paths.py +30 -0
- opentide/platforms/__init__.py +1 -0
- opentide/platforms/base.py +54 -0
- opentide/platforms/carbon_black/__init__.py +15 -0
- opentide/platforms/carbon_black/client.py +133 -0
- opentide/platforms/carbon_black/deployer.py +195 -0
- opentide/platforms/carbon_black/validator.py +96 -0
- opentide/platforms/carbon_black.py +7 -0
- opentide/platforms/config.py +243 -0
- opentide/platforms/crowdstrike/__init__.py +3 -0
- opentide/platforms/crowdstrike/client.py +144 -0
- opentide/platforms/crowdstrike/deployer.py +148 -0
- opentide/platforms/crowdstrike.py +7 -0
- opentide/platforms/defender_for_endpoint/__init__.py +3 -0
- opentide/platforms/defender_for_endpoint/client.py +196 -0
- opentide/platforms/defender_for_endpoint/deployer.py +278 -0
- opentide/platforms/defender_for_endpoint/validator.py +196 -0
- opentide/platforms/defender_for_endpoint.py +7 -0
- opentide/platforms/enabled.py +30 -0
- opentide/platforms/harfanglab/__init__.py +3 -0
- opentide/platforms/harfanglab/client.py +405 -0
- opentide/platforms/harfanglab/deployer.py +227 -0
- opentide/platforms/harfanglab.py +7 -0
- opentide/platforms/kql.py +62 -0
- opentide/platforms/plugins.py +211 -0
- opentide/platforms/registry.py +186 -0
- opentide/platforms/sentinel/__init__.py +3 -0
- opentide/platforms/sentinel/client.py +39 -0
- opentide/platforms/sentinel/deployer.py +189 -0
- opentide/platforms/sentinel/validator.py +83 -0
- opentide/platforms/sentinel.py +7 -0
- opentide/platforms/sentinel_one/__init__.py +3 -0
- opentide/platforms/sentinel_one/client.py +180 -0
- opentide/platforms/sentinel_one/deployer.py +143 -0
- opentide/platforms/sentinel_one/validator.py +55 -0
- opentide/platforms/sentinel_one.py +7 -0
- opentide/platforms/splunk/__init__.py +15 -0
- opentide/platforms/splunk/client.py +264 -0
- opentide/platforms/splunk/deployer.py +466 -0
- opentide/platforms/splunk/validator.py +120 -0
- opentide/platforms/splunk.py +7 -0
- opentide/py.typed +0 -0
- opentide/registry/__init__.py +15 -0
- opentide/registry/artifacts.py +29 -0
- opentide/registry/builder.py +289 -0
- opentide/registry/discovery.py +41 -0
- opentide/registry/paths.py +175 -0
- opentide/schemas/__init__.py +8 -0
- opentide/schemas/data/platform_templates/MDR Systems Deployment/Templates/Carbon Black Cloud Enterprise EDR Template.yaml +15 -0
- opentide/schemas/data/platform_templates/MDR Systems Deployment/Templates/Crowdstrike Falcon Correlation Rules Template.yaml +13 -0
- opentide/schemas/data/platform_templates/MDR Systems Deployment/Templates/Crowdstrike Falcon Template.yaml +13 -0
- opentide/schemas/data/platform_templates/MDR Systems Deployment/Templates/HarfangLab Template.yaml +14 -0
- opentide/schemas/data/platform_templates/MDR Systems Deployment/Templates/Microsoft Defender for Endpoint Template.yaml +17 -0
- opentide/schemas/data/platform_templates/MDR Systems Deployment/Templates/Microsoft Sentinel Template.yaml +17 -0
- opentide/schemas/data/platform_templates/MDR Systems Deployment/Templates/Sentinel One Template.yaml +11 -0
- opentide/schemas/data/platform_templates/MDR Systems Deployment/Templates/Splunk Enterprise Template.yaml +16 -0
- opentide/schemas/data/vocabulary.schema.json +45 -0
- opentide/schemas/store.py +45 -0
- opentide/validation/__init__.py +42 -0
- opentide/validation/carbon_black_cloud_query.py +7 -0
- opentide/validation/checks/__init__.py +1 -0
- opentide/validation/checks/cross_object.py +133 -0
- opentide/validation/checks/kinds.py +14 -0
- opentide/validation/cve.py +35 -0
- opentide/validation/cve_check.py +78 -0
- opentide/validation/deprecated_fields.py +104 -0
- opentide/validation/errors.py +115 -0
- opentide/validation/field_vocab.py +164 -0
- opentide/validation/id_scan.py +74 -0
- opentide/validation/id_uniqueness.py +33 -0
- opentide/validation/issues.py +60 -0
- opentide/validation/legacy.py +47 -0
- opentide/validation/parallel.py +79 -0
- opentide/validation/pipeline.py +78 -0
- opentide/validation/preflight.py +171 -0
- opentide/validation/scope.py +75 -0
- opentide/validation/session.py +398 -0
- opentide/validation/splunk_query.py +7 -0
- opentide/validation/tide_schema.py +68 -0
- opentide/validation/uuid_v4.py +33 -0
- opentide/validation/vocab_resolver.py +321 -0
- opentide/vocabulary/__init__.py +21 -0
- opentide/vocabulary/fetch_stix.py +71 -0
- opentide/vocabulary/generate_actors.py +86 -0
- opentide/vocabulary/generate_attack.py +133 -0
- opentide/vocabulary/io.py +100 -0
- opentide/vocabulary/stix_attack.py +229 -0
- opentide/vocabulary/writers.py +43 -0
- opentide-0.1.0.dist-info/METADATA +96 -0
- opentide-0.1.0.dist-info/RECORD +329 -0
- opentide-0.1.0.dist-info/WHEEL +4 -0
- opentide-0.1.0.dist-info/entry_points.txt +12 -0
- opentide-0.1.0.dist-info/licenses/LICENSE +287 -0
opentide/__init__.py
ADDED
opentide/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
opentide/ci/__init__.py
ADDED
opentide/ci/azure.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Azure DevOps pipeline renderer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import textwrap
|
|
6
|
+
|
|
7
|
+
from opentide.ci.inflight import azure_inflight_job, azure_inflight_prune_job
|
|
8
|
+
from opentide.ci.models import CiRenderOptions
|
|
9
|
+
from opentide.ci.stages import (
|
|
10
|
+
document_steps,
|
|
11
|
+
header_comment,
|
|
12
|
+
pip_install,
|
|
13
|
+
production_deploy_steps,
|
|
14
|
+
promotion_steps,
|
|
15
|
+
staging_deploy_steps,
|
|
16
|
+
)
|
|
17
|
+
from opentide.cli.enums import QUERY_VALIDATION_PLATFORMS
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _bash_script(commands: list[str], indent: int = 12) -> str:
|
|
21
|
+
joined = " && ".join(commands)
|
|
22
|
+
pad = " " * indent
|
|
23
|
+
return f"{pad}- script: |\n{pad} {joined}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _python_setup(options: CiRenderOptions, indent: int = 10) -> str:
|
|
27
|
+
pad = " " * indent
|
|
28
|
+
return textwrap.dedent(
|
|
29
|
+
f"""\
|
|
30
|
+
{pad}- task: UsePythonVersion@0
|
|
31
|
+
{pad} inputs:
|
|
32
|
+
{pad} versionSpec: '{options.python_version}'
|
|
33
|
+
{pad}- script: {pip_install(options)}
|
|
34
|
+
{pad} displayName: Install OpenTide
|
|
35
|
+
"""
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def render_azure(options: CiRenderOptions) -> str:
|
|
40
|
+
branch = options.default_branch
|
|
41
|
+
validate_steps = _python_setup(options) + "\n" + _bash_script(["opentide validate"])
|
|
42
|
+
|
|
43
|
+
query_jobs: list[str] = []
|
|
44
|
+
for platform in options.platforms:
|
|
45
|
+
if platform not in QUERY_VALIDATION_PLATFORMS:
|
|
46
|
+
continue
|
|
47
|
+
safe = platform.replace("_", "-")
|
|
48
|
+
query_jobs.append(
|
|
49
|
+
textwrap.dedent(
|
|
50
|
+
f"""\
|
|
51
|
+
- job: validate_query_{safe}
|
|
52
|
+
displayName: Validate query ({platform})
|
|
53
|
+
steps:
|
|
54
|
+
{_python_setup(options)}
|
|
55
|
+
{_bash_script([f"opentide validate query --platform {platform}"])}
|
|
56
|
+
"""
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
staging_job = ""
|
|
61
|
+
inflight_job = ""
|
|
62
|
+
inflight_prune_job = ""
|
|
63
|
+
if options.staging:
|
|
64
|
+
staging_job = textwrap.dedent(
|
|
65
|
+
f"""\
|
|
66
|
+
- job: deploy_staging
|
|
67
|
+
displayName: Deploy Staging
|
|
68
|
+
dependsOn: generate
|
|
69
|
+
condition: eq(variables['Build.Reason'], 'PullRequest')
|
|
70
|
+
steps:
|
|
71
|
+
{_python_setup(options)}
|
|
72
|
+
{_bash_script(staging_deploy_steps(options))}
|
|
73
|
+
"""
|
|
74
|
+
)
|
|
75
|
+
if options.inflight:
|
|
76
|
+
inflight_job = azure_inflight_job(
|
|
77
|
+
python_version=options.python_version,
|
|
78
|
+
opentide_version=options.opentide_version,
|
|
79
|
+
default_branch=branch,
|
|
80
|
+
)
|
|
81
|
+
inflight_prune_job = azure_inflight_prune_job(
|
|
82
|
+
python_version=options.python_version,
|
|
83
|
+
opentide_version=options.opentide_version,
|
|
84
|
+
default_branch=branch,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
promote_stage = ""
|
|
88
|
+
promote_cmds = promotion_steps(options)
|
|
89
|
+
if promote_cmds:
|
|
90
|
+
promote_stage = textwrap.dedent(
|
|
91
|
+
f"""\
|
|
92
|
+
- stage: Promote
|
|
93
|
+
displayName: Promote
|
|
94
|
+
dependsOn: Deploy
|
|
95
|
+
condition: >
|
|
96
|
+
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/{branch}'))
|
|
97
|
+
jobs:
|
|
98
|
+
- job: promote
|
|
99
|
+
displayName: Promote rules
|
|
100
|
+
steps:
|
|
101
|
+
{_python_setup(options)}
|
|
102
|
+
{_bash_script(promotion_steps(options))}
|
|
103
|
+
"""
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
query_jobs_block = "\n".join(query_jobs)
|
|
107
|
+
validate_jobs = textwrap.dedent(
|
|
108
|
+
f"""\
|
|
109
|
+
- job: validate
|
|
110
|
+
displayName: Validate objects
|
|
111
|
+
steps:
|
|
112
|
+
{validate_steps}
|
|
113
|
+
"""
|
|
114
|
+
)
|
|
115
|
+
if query_jobs_block:
|
|
116
|
+
validate_jobs = validate_jobs + query_jobs_block
|
|
117
|
+
|
|
118
|
+
body = textwrap.dedent(
|
|
119
|
+
f"""\
|
|
120
|
+
trigger:
|
|
121
|
+
branches:
|
|
122
|
+
include:
|
|
123
|
+
- {branch}
|
|
124
|
+
|
|
125
|
+
pr:
|
|
126
|
+
branches:
|
|
127
|
+
include:
|
|
128
|
+
- {branch}
|
|
129
|
+
|
|
130
|
+
stages:
|
|
131
|
+
- stage: Validate
|
|
132
|
+
displayName: Validate
|
|
133
|
+
jobs:
|
|
134
|
+
{validate_jobs}
|
|
135
|
+
|
|
136
|
+
- stage: Generate
|
|
137
|
+
displayName: Generate
|
|
138
|
+
dependsOn: Validate
|
|
139
|
+
jobs:
|
|
140
|
+
- job: generate
|
|
141
|
+
displayName: Generate framework artifacts
|
|
142
|
+
steps:
|
|
143
|
+
{_python_setup(options)}
|
|
144
|
+
{_bash_script(["opentide generate"])}
|
|
145
|
+
|
|
146
|
+
- stage: Deploy
|
|
147
|
+
displayName: Deploy
|
|
148
|
+
dependsOn: Generate
|
|
149
|
+
jobs:
|
|
150
|
+
{staging_job}
|
|
151
|
+
{inflight_job}
|
|
152
|
+
{inflight_prune_job}
|
|
153
|
+
- job: deploy_production
|
|
154
|
+
displayName: Deploy Production
|
|
155
|
+
dependsOn: generate
|
|
156
|
+
condition: eq(variables['Build.SourceBranch'], 'refs/heads/{branch}')
|
|
157
|
+
steps:
|
|
158
|
+
{_python_setup(options)}
|
|
159
|
+
{_bash_script(production_deploy_steps(options))}
|
|
160
|
+
|
|
161
|
+
- stage: Document
|
|
162
|
+
displayName: Document
|
|
163
|
+
dependsOn: Generate
|
|
164
|
+
condition: eq(variables['Build.SourceBranch'], 'refs/heads/{branch}')
|
|
165
|
+
jobs:
|
|
166
|
+
- job: document
|
|
167
|
+
displayName: Generate documentation
|
|
168
|
+
steps:
|
|
169
|
+
{_python_setup(options)}
|
|
170
|
+
{_bash_script(document_steps(options))}
|
|
171
|
+
{promote_stage}
|
|
172
|
+
"""
|
|
173
|
+
)
|
|
174
|
+
return header_comment(options) + body
|
opentide/ci/discovery.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Discover enabled platforms from repository configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from opentide.registry.discovery import OPENTIDE_DIR
|
|
9
|
+
|
|
10
|
+
_PLATFORM_SECTION = re.compile(r"^\[platform\]\s*$", re.MULTILINE)
|
|
11
|
+
_ENABLED = re.compile(r"^enabled\s*=\s*true\s*$", re.MULTILINE | re.IGNORECASE)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def platforms_config_dir(repo: Path) -> Path:
|
|
15
|
+
return repo / OPENTIDE_DIR / "configurations" / "platforms"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def discover_enabled_platforms(repo: Path) -> list[str]:
|
|
19
|
+
"""Return platform identifiers with ``enabled = true`` in platform TOML files."""
|
|
20
|
+
config_dir = platforms_config_dir(repo)
|
|
21
|
+
if not config_dir.is_dir():
|
|
22
|
+
return []
|
|
23
|
+
enabled: list[str] = []
|
|
24
|
+
for path in sorted(config_dir.glob("*.toml")):
|
|
25
|
+
text = path.read_text(encoding="utf-8")
|
|
26
|
+
if not _PLATFORM_SECTION.search(text):
|
|
27
|
+
continue
|
|
28
|
+
if not _ENABLED.search(text):
|
|
29
|
+
continue
|
|
30
|
+
enabled.append(path.stem)
|
|
31
|
+
return enabled
|
opentide/ci/github.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""GitHub Actions workflow renderer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import textwrap
|
|
6
|
+
|
|
7
|
+
from opentide.ci.inflight import github_inflight_job, github_inflight_prune_job
|
|
8
|
+
from opentide.ci.models import CiRenderOptions
|
|
9
|
+
from opentide.ci.stages import (
|
|
10
|
+
document_steps,
|
|
11
|
+
header_comment,
|
|
12
|
+
pip_install,
|
|
13
|
+
production_deploy_steps,
|
|
14
|
+
promotion_steps,
|
|
15
|
+
query_platforms,
|
|
16
|
+
staging_deploy_steps,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _indent_yaml(text: str, spaces: int) -> str:
|
|
21
|
+
pad = " " * spaces
|
|
22
|
+
return "\n".join(pad + line if line else line for line in text.splitlines())
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _run_steps(commands: list[str], indent: int = 10) -> str:
|
|
26
|
+
lines = []
|
|
27
|
+
for cmd in commands:
|
|
28
|
+
lines.append(f"- run: {cmd}")
|
|
29
|
+
return _indent_yaml("\n".join(lines), indent)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _explorer_jobs(branch: str, python_version: str) -> list[str]:
|
|
33
|
+
explorer_job = textwrap.dedent(
|
|
34
|
+
f"""\
|
|
35
|
+
explorer:
|
|
36
|
+
name: Build explorer
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
needs: document
|
|
39
|
+
env:
|
|
40
|
+
HAS_GITHUB_APP: ${{{{ secrets.GH_APP_ID && secrets.GH_APP_KEY && 'true' || 'false' }}}}
|
|
41
|
+
steps:
|
|
42
|
+
- name: Checkout repository
|
|
43
|
+
uses: actions/checkout@v4
|
|
44
|
+
|
|
45
|
+
- name: Generate GitHub App token
|
|
46
|
+
id: generate_token
|
|
47
|
+
if: ${{{{ env.HAS_GITHUB_APP == 'true' }}}}
|
|
48
|
+
uses: actions/create-github-app-token@v1
|
|
49
|
+
with:
|
|
50
|
+
app-id: ${{{{ secrets.GH_APP_ID }}}}
|
|
51
|
+
private-key: ${{{{ secrets.GH_APP_KEY }}}}
|
|
52
|
+
owner: OpenTideHQ
|
|
53
|
+
repositories: opentide
|
|
54
|
+
|
|
55
|
+
- name: Checkout opentide
|
|
56
|
+
uses: actions/checkout@v4
|
|
57
|
+
with:
|
|
58
|
+
repository: OpenTideHQ/opentide
|
|
59
|
+
ref: development
|
|
60
|
+
path: opentide
|
|
61
|
+
token: ${{{{ steps.generate_token.outputs.token || secrets.OPENTIDE_REPO_TOKEN }}}}
|
|
62
|
+
|
|
63
|
+
- name: Checkout explorer
|
|
64
|
+
uses: actions/checkout@v4
|
|
65
|
+
with:
|
|
66
|
+
repository: OpenTideHQ/explorer
|
|
67
|
+
ref: main
|
|
68
|
+
path: explorer
|
|
69
|
+
|
|
70
|
+
- uses: actions/setup-python@v5
|
|
71
|
+
with:
|
|
72
|
+
python-version: "{python_version}"
|
|
73
|
+
|
|
74
|
+
- uses: actions/setup-node@v4
|
|
75
|
+
with:
|
|
76
|
+
node-version: "22"
|
|
77
|
+
|
|
78
|
+
- name: Install opentide
|
|
79
|
+
run: pip install -e ./opentide
|
|
80
|
+
|
|
81
|
+
- name: Build explorer static site
|
|
82
|
+
run: |
|
|
83
|
+
opentide explorer build \
|
|
84
|
+
--output ./out/explorer \
|
|
85
|
+
--base-path /${{{{ github.event.repository.name }}}}
|
|
86
|
+
env:
|
|
87
|
+
OPENTIDE_REPO_ROOT: ${{{{ github.workspace }}}}
|
|
88
|
+
OPENTIDE_EXPLORER_PATH: ${{{{ github.workspace }}}}/explorer
|
|
89
|
+
|
|
90
|
+
- name: Upload Pages artifact
|
|
91
|
+
uses: actions/upload-pages-artifact@v3
|
|
92
|
+
with:
|
|
93
|
+
path: ./out/explorer
|
|
94
|
+
"""
|
|
95
|
+
)
|
|
96
|
+
deploy_job = textwrap.dedent(
|
|
97
|
+
f"""\
|
|
98
|
+
deploy-explorer:
|
|
99
|
+
name: Deploy explorer to GitHub Pages
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
needs: explorer
|
|
102
|
+
if: github.event_name == 'push' && github.ref == format('refs/heads/{branch}')
|
|
103
|
+
permissions:
|
|
104
|
+
pages: write
|
|
105
|
+
id-token: write
|
|
106
|
+
environment:
|
|
107
|
+
name: github-pages
|
|
108
|
+
url: ${{{{ steps.deployment.outputs.page_url }}}}
|
|
109
|
+
steps:
|
|
110
|
+
- name: Deploy to GitHub Pages
|
|
111
|
+
id: deployment
|
|
112
|
+
uses: actions/deploy-pages@v4
|
|
113
|
+
"""
|
|
114
|
+
)
|
|
115
|
+
return [explorer_job, deploy_job]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def render_github(options: CiRenderOptions) -> str:
|
|
119
|
+
branch = options.default_branch
|
|
120
|
+
setup_steps = textwrap.dedent(
|
|
121
|
+
f"""\
|
|
122
|
+
- uses: actions/checkout@v4
|
|
123
|
+
- uses: actions/setup-python@v5
|
|
124
|
+
with:
|
|
125
|
+
python-version: "{options.python_version}"
|
|
126
|
+
- run: {pip_install(options)}
|
|
127
|
+
"""
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
validate_body = setup_steps + _run_steps(
|
|
131
|
+
["opentide validate"]
|
|
132
|
+
+ [f"opentide validate query --platform {p}" for p in query_platforms(options)]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
generate_body = setup_steps + _run_steps(["opentide generate"])
|
|
136
|
+
|
|
137
|
+
jobs: list[str] = []
|
|
138
|
+
|
|
139
|
+
jobs.append(
|
|
140
|
+
textwrap.dedent(
|
|
141
|
+
f"""\
|
|
142
|
+
validate:
|
|
143
|
+
name: Validate
|
|
144
|
+
runs-on: ubuntu-latest
|
|
145
|
+
steps:
|
|
146
|
+
{validate_body}
|
|
147
|
+
"""
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
jobs.append(
|
|
152
|
+
textwrap.dedent(
|
|
153
|
+
f"""\
|
|
154
|
+
generate:
|
|
155
|
+
name: Generate
|
|
156
|
+
needs: validate
|
|
157
|
+
runs-on: ubuntu-latest
|
|
158
|
+
steps:
|
|
159
|
+
{generate_body}
|
|
160
|
+
"""
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if options.staging:
|
|
165
|
+
staging_cmds = staging_deploy_steps(options)
|
|
166
|
+
jobs.append(
|
|
167
|
+
textwrap.dedent(
|
|
168
|
+
f"""\
|
|
169
|
+
deploy_staging:
|
|
170
|
+
name: Deploy Staging
|
|
171
|
+
needs: generate
|
|
172
|
+
if: github.event_name == 'pull_request'
|
|
173
|
+
runs-on: ubuntu-latest
|
|
174
|
+
steps:
|
|
175
|
+
{setup_steps}{_run_steps(staging_cmds)}
|
|
176
|
+
"""
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if options.inflight:
|
|
181
|
+
jobs.append(
|
|
182
|
+
github_inflight_job(
|
|
183
|
+
python_version=options.python_version,
|
|
184
|
+
opentide_version=options.opentide_version,
|
|
185
|
+
default_branch=branch,
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
jobs.append(
|
|
189
|
+
github_inflight_prune_job(
|
|
190
|
+
python_version=options.python_version,
|
|
191
|
+
opentide_version=options.opentide_version,
|
|
192
|
+
default_branch=branch,
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
prod_needs = "deploy_staging" if options.staging else "generate"
|
|
197
|
+
prod_if = f"github.event_name == 'push' && github.ref == format('refs/heads/{branch}')"
|
|
198
|
+
jobs.append(
|
|
199
|
+
textwrap.dedent(
|
|
200
|
+
f"""\
|
|
201
|
+
deploy_production:
|
|
202
|
+
name: Deploy Production
|
|
203
|
+
needs: {prod_needs}
|
|
204
|
+
if: {prod_if}
|
|
205
|
+
runs-on: ubuntu-latest
|
|
206
|
+
steps:
|
|
207
|
+
{setup_steps}{_run_steps(production_deploy_steps(options))}
|
|
208
|
+
"""
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if options.promotion and promotion_steps(options):
|
|
213
|
+
jobs.append(
|
|
214
|
+
textwrap.dedent(
|
|
215
|
+
f"""\
|
|
216
|
+
promote:
|
|
217
|
+
name: Promote Rules
|
|
218
|
+
needs: deploy_production
|
|
219
|
+
if: {prod_if}
|
|
220
|
+
runs-on: ubuntu-latest
|
|
221
|
+
steps:
|
|
222
|
+
{setup_steps}{_run_steps(promotion_steps(options))}
|
|
223
|
+
"""
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
jobs.append(
|
|
228
|
+
textwrap.dedent(
|
|
229
|
+
f"""\
|
|
230
|
+
document:
|
|
231
|
+
name: Document
|
|
232
|
+
needs: generate
|
|
233
|
+
if: {prod_if}
|
|
234
|
+
runs-on: ubuntu-latest
|
|
235
|
+
steps:
|
|
236
|
+
{setup_steps}{_run_steps(document_steps(options))}
|
|
237
|
+
"""
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if options.explorer_pages:
|
|
242
|
+
jobs.extend(_explorer_jobs(branch, options.python_version))
|
|
243
|
+
|
|
244
|
+
permissions = ""
|
|
245
|
+
concurrency = ""
|
|
246
|
+
if options.explorer_pages:
|
|
247
|
+
permissions = textwrap.dedent(
|
|
248
|
+
"""\
|
|
249
|
+
permissions:
|
|
250
|
+
contents: read
|
|
251
|
+
pages: write
|
|
252
|
+
id-token: write
|
|
253
|
+
|
|
254
|
+
"""
|
|
255
|
+
)
|
|
256
|
+
concurrency = textwrap.dedent(
|
|
257
|
+
"""\
|
|
258
|
+
concurrency:
|
|
259
|
+
group: pages
|
|
260
|
+
cancel-in-progress: false
|
|
261
|
+
|
|
262
|
+
"""
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
body = textwrap.dedent(
|
|
266
|
+
f"""\
|
|
267
|
+
name: OpenTide
|
|
268
|
+
|
|
269
|
+
on:
|
|
270
|
+
pull_request:
|
|
271
|
+
push:
|
|
272
|
+
branches:
|
|
273
|
+
- {branch}
|
|
274
|
+
|
|
275
|
+
{permissions}{concurrency}jobs:
|
|
276
|
+
"""
|
|
277
|
+
)
|
|
278
|
+
job_yaml = "\n".join(_indent_yaml(job, 2) for job in jobs)
|
|
279
|
+
return header_comment(options) + body + job_yaml
|
opentide/ci/gitlab.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""GitLab CI pipeline renderer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import textwrap
|
|
6
|
+
|
|
7
|
+
from opentide.ci.inflight import gitlab_inflight_job, gitlab_inflight_prune_job
|
|
8
|
+
from opentide.ci.models import CiRenderOptions
|
|
9
|
+
from opentide.ci.stages import (
|
|
10
|
+
document_steps,
|
|
11
|
+
header_comment,
|
|
12
|
+
pip_install,
|
|
13
|
+
production_deploy_steps,
|
|
14
|
+
promotion_steps,
|
|
15
|
+
staging_deploy_steps,
|
|
16
|
+
)
|
|
17
|
+
from opentide.cli.enums import QUERY_VALIDATION_PLATFORMS
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _script_block(commands: list[str], indent: int = 4) -> str:
|
|
21
|
+
pad = " " * indent
|
|
22
|
+
lines = [f"{pad}- {cmd}" for cmd in commands]
|
|
23
|
+
return "\n".join(lines)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _base_before_script(options: CiRenderOptions) -> str:
|
|
27
|
+
return _script_block([pip_install(options)])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def render_gitlab(options: CiRenderOptions) -> str:
|
|
31
|
+
stages = ["validate", "generate", "deploy", "document"]
|
|
32
|
+
promote_cmds = promotion_steps(options)
|
|
33
|
+
if promote_cmds:
|
|
34
|
+
stages.insert(3, "promote")
|
|
35
|
+
|
|
36
|
+
validate_script = _script_block(["opentide validate"])
|
|
37
|
+
query_jobs: list[str] = []
|
|
38
|
+
for platform in options.platforms:
|
|
39
|
+
if platform not in QUERY_VALIDATION_PLATFORMS:
|
|
40
|
+
continue
|
|
41
|
+
job_name = platform.replace("_", "-")
|
|
42
|
+
query_jobs.append(
|
|
43
|
+
textwrap.dedent(
|
|
44
|
+
f"""\
|
|
45
|
+
validate_query_{job_name}:
|
|
46
|
+
stage: validate
|
|
47
|
+
image: python:{options.python_version}-slim
|
|
48
|
+
before_script:
|
|
49
|
+
{_base_before_script(options)}
|
|
50
|
+
script:
|
|
51
|
+
- opentide validate query --platform {platform}
|
|
52
|
+
"""
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
deploy_jobs: list[str] = []
|
|
57
|
+
if options.staging:
|
|
58
|
+
deploy_jobs.append(
|
|
59
|
+
textwrap.dedent(
|
|
60
|
+
f"""\
|
|
61
|
+
deploy_staging:
|
|
62
|
+
stage: deploy
|
|
63
|
+
image: python:{options.python_version}-slim
|
|
64
|
+
rules:
|
|
65
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
66
|
+
before_script:
|
|
67
|
+
{_base_before_script(options)}
|
|
68
|
+
script:
|
|
69
|
+
{_script_block(staging_deploy_steps(options))}
|
|
70
|
+
needs:
|
|
71
|
+
- generate
|
|
72
|
+
"""
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
deploy_jobs.append(
|
|
77
|
+
textwrap.dedent(
|
|
78
|
+
f"""\
|
|
79
|
+
deploy_production:
|
|
80
|
+
stage: deploy
|
|
81
|
+
image: python:{options.python_version}-slim
|
|
82
|
+
rules:
|
|
83
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
84
|
+
before_script:
|
|
85
|
+
{_base_before_script(options)}
|
|
86
|
+
script:
|
|
87
|
+
{_script_block(production_deploy_steps(options))}
|
|
88
|
+
needs:
|
|
89
|
+
- generate
|
|
90
|
+
"""
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if options.inflight:
|
|
95
|
+
deploy_jobs.append(
|
|
96
|
+
gitlab_inflight_job(
|
|
97
|
+
python_version=options.python_version,
|
|
98
|
+
opentide_version=options.opentide_version,
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
deploy_jobs.append(
|
|
102
|
+
gitlab_inflight_prune_job(
|
|
103
|
+
python_version=options.python_version,
|
|
104
|
+
opentide_version=options.opentide_version,
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
promote_job = ""
|
|
109
|
+
if promote_cmds:
|
|
110
|
+
promote_job = textwrap.dedent(
|
|
111
|
+
f"""\
|
|
112
|
+
promote:
|
|
113
|
+
stage: promote
|
|
114
|
+
image: python:{options.python_version}-slim
|
|
115
|
+
rules:
|
|
116
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
117
|
+
before_script:
|
|
118
|
+
{_base_before_script(options)}
|
|
119
|
+
script:
|
|
120
|
+
{_script_block(promotion_steps(options))}
|
|
121
|
+
needs:
|
|
122
|
+
- deploy_production
|
|
123
|
+
"""
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
document_job = textwrap.dedent(
|
|
127
|
+
f"""\
|
|
128
|
+
document:
|
|
129
|
+
stage: document
|
|
130
|
+
image: python:{options.python_version}-slim
|
|
131
|
+
rules:
|
|
132
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
133
|
+
before_script:
|
|
134
|
+
{_base_before_script(options)}
|
|
135
|
+
script:
|
|
136
|
+
{_script_block(document_steps(options))}
|
|
137
|
+
needs:
|
|
138
|
+
- generate
|
|
139
|
+
"""
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
stage_lines = "\n".join(f" - {stage}" for stage in stages)
|
|
143
|
+
|
|
144
|
+
core = textwrap.dedent(
|
|
145
|
+
f"""\
|
|
146
|
+
stages:
|
|
147
|
+
{stage_lines}
|
|
148
|
+
|
|
149
|
+
default:
|
|
150
|
+
image: python:{options.python_version}-slim
|
|
151
|
+
|
|
152
|
+
validate:
|
|
153
|
+
stage: validate
|
|
154
|
+
before_script:
|
|
155
|
+
{_base_before_script(options)}
|
|
156
|
+
script:
|
|
157
|
+
{validate_script}
|
|
158
|
+
|
|
159
|
+
generate:
|
|
160
|
+
stage: generate
|
|
161
|
+
before_script:
|
|
162
|
+
{_base_before_script(options)}
|
|
163
|
+
script:
|
|
164
|
+
- opentide generate
|
|
165
|
+
needs:
|
|
166
|
+
- validate
|
|
167
|
+
"""
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
parts = [header_comment(options), core]
|
|
171
|
+
parts.extend(query_jobs)
|
|
172
|
+
parts.extend(deploy_jobs)
|
|
173
|
+
if promote_job:
|
|
174
|
+
parts.append(promote_job)
|
|
175
|
+
parts.append(document_job)
|
|
176
|
+
return "\n".join(parts)
|