cici-tools 0.2.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.
- cici/__init__.py +0 -0
- cici/__main__.py +4 -0
- cici/_version.py +1 -0
- cici/cli/__init__.py +0 -0
- cici/cli/build.py +55 -0
- cici/cli/bundle.py +104 -0
- cici/cli/fmt.py +30 -0
- cici/cli/update.py +112 -0
- cici/constants.py +26 -0
- cici/main.py +17 -0
- cici/providers/__init__.py +0 -0
- cici/providers/gitlab/__init__.py +3 -0
- cici/providers/gitlab/constants.py +77 -0
- cici/providers/gitlab/converter.py +203 -0
- cici/providers/gitlab/models.py +223 -0
- cici/providers/gitlab/serializers.py +115 -0
- cici/providers/gitlab/utils.py +26 -0
- cici/schema/LICENSE.gitlab +28 -0
- cici/schema/__init__.py +0 -0
- cici/schema/gitlab-ci.json +1842 -0
- cici/utils.py +60 -0
- cici_tools-0.2.0.dist-info/LICENSE +21 -0
- cici_tools-0.2.0.dist-info/METADATA +69 -0
- cici_tools-0.2.0.dist-info/RECORD +27 -0
- cici_tools-0.2.0.dist-info/WHEEL +5 -0
- cici_tools-0.2.0.dist-info/entry_points.txt +3 -0
- cici_tools-0.2.0.dist-info/top_level.txt +1 -0
cici/__init__.py
ADDED
|
File without changes
|
cici/__main__.py
ADDED
cici/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
cici/cli/__init__.py
ADDED
|
File without changes
|
cici/cli/build.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from ..constants import DEFAULT_PROVIDER, PROVIDERS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_command(parser, args):
|
|
10
|
+
logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")
|
|
11
|
+
|
|
12
|
+
input_provider = import_module(f".{args.input_format}", "cici.providers")
|
|
13
|
+
output_provider = import_module(f".{args.output_format}", "cici.providers")
|
|
14
|
+
|
|
15
|
+
if not args.filename:
|
|
16
|
+
args.filename = input_provider.CI_FILE
|
|
17
|
+
|
|
18
|
+
if not Path(args.filename).exists():
|
|
19
|
+
parser.error(f"file not found: {args.filename}")
|
|
20
|
+
|
|
21
|
+
file = input_provider.load(args.filename)
|
|
22
|
+
output_provider.dump(file, sys.stdout)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_parser(subparsers):
|
|
26
|
+
parser = subparsers.add_parser(
|
|
27
|
+
"build", help="build BrettOps CI file into target format"
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument("filename", nargs="?")
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"-f",
|
|
32
|
+
"--from",
|
|
33
|
+
dest="input_format",
|
|
34
|
+
choices=PROVIDERS,
|
|
35
|
+
default=DEFAULT_PROVIDER,
|
|
36
|
+
help=f"input format [{DEFAULT_PROVIDER}]",
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"-t",
|
|
40
|
+
"--to",
|
|
41
|
+
dest="output_format",
|
|
42
|
+
choices=PROVIDERS,
|
|
43
|
+
default=DEFAULT_PROVIDER,
|
|
44
|
+
help=f"output format [{DEFAULT_PROVIDER}]",
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"-o",
|
|
48
|
+
"--output",
|
|
49
|
+
metavar="DIR",
|
|
50
|
+
dest="output_path",
|
|
51
|
+
type=Path,
|
|
52
|
+
default=Path.cwd().absolute(),
|
|
53
|
+
)
|
|
54
|
+
parser.set_defaults(func=build_command)
|
|
55
|
+
return parser
|
cici/cli/bundle.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import logging
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from termcolor import colored
|
|
8
|
+
|
|
9
|
+
from ..constants import CONFIG_DIR_NAME, DEFAULT_PROVIDER
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_job_prefix(pipeline_name: str, job_name: str) -> Optional[str]:
|
|
13
|
+
if job_name.startswith("."):
|
|
14
|
+
return None
|
|
15
|
+
return f"{pipeline_name}-"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def parse_groups(name, file, groups):
|
|
19
|
+
if groups:
|
|
20
|
+
groups = [group.strip() for group in groups.split(",")]
|
|
21
|
+
|
|
22
|
+
if not groups:
|
|
23
|
+
group_names = set()
|
|
24
|
+
for job_name in file.jobs.keys():
|
|
25
|
+
if job_name.startswith("."):
|
|
26
|
+
continue
|
|
27
|
+
group_name = job_name[len(f"{name}-") :]
|
|
28
|
+
group_name = group_name.split("-")[0]
|
|
29
|
+
group_names.add(group_name)
|
|
30
|
+
groups = sorted(list(group_names))
|
|
31
|
+
return groups
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def bundle_command(parser, args):
|
|
35
|
+
logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")
|
|
36
|
+
print(colored("pipeline name:", color="yellow"), args.pipeline_name)
|
|
37
|
+
|
|
38
|
+
provider = import_module(f".{DEFAULT_PROVIDER}", "cici.providers")
|
|
39
|
+
|
|
40
|
+
ci_file_path = args.config_path / provider.CI_FILE
|
|
41
|
+
|
|
42
|
+
if not Path(ci_file_path).exists():
|
|
43
|
+
parser.error(f"file not found: {ci_file_path}")
|
|
44
|
+
|
|
45
|
+
file = provider.load(ci_file_path)
|
|
46
|
+
|
|
47
|
+
args.output_path = Path(args.output_path)
|
|
48
|
+
args.groups = parse_groups(args.pipeline_name, file, args.groups)
|
|
49
|
+
print(colored("bundle names:", color="yellow"), args.groups)
|
|
50
|
+
|
|
51
|
+
for bundle_name in args.groups:
|
|
52
|
+
pattern = f"{args.pipeline_name}-{bundle_name}"
|
|
53
|
+
|
|
54
|
+
bundle = copy.deepcopy(file)
|
|
55
|
+
bundle.jobs = {}
|
|
56
|
+
|
|
57
|
+
for job_name, job in file.jobs.items():
|
|
58
|
+
job_prefix = get_job_prefix(
|
|
59
|
+
pipeline_name=args.pipeline_name,
|
|
60
|
+
job_name=job_name,
|
|
61
|
+
)
|
|
62
|
+
if not job_prefix:
|
|
63
|
+
continue
|
|
64
|
+
assert job_name.startswith(job_prefix)
|
|
65
|
+
if job_name.startswith(pattern):
|
|
66
|
+
bundle.jobs[job_name] = job
|
|
67
|
+
|
|
68
|
+
bundle_filename = args.output_path / f"{bundle_name}.yml"
|
|
69
|
+
|
|
70
|
+
with open(bundle_filename, "w") as stream:
|
|
71
|
+
provider.dump(bundle, stream)
|
|
72
|
+
print(colored("created", "magenta"), bundle_filename.name)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def bundle_parser(subparsers):
|
|
76
|
+
parser = subparsers.add_parser(
|
|
77
|
+
"bundle", help="distill a CI file to a job and its dependencies"
|
|
78
|
+
)
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"config_path",
|
|
81
|
+
metavar="DIR",
|
|
82
|
+
nargs="?",
|
|
83
|
+
type=Path,
|
|
84
|
+
default=(Path.cwd() / CONFIG_DIR_NAME).absolute(),
|
|
85
|
+
)
|
|
86
|
+
parser.add_argument("-g", "--groups", help="job group patterns")
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"-o",
|
|
89
|
+
"--output",
|
|
90
|
+
metavar="DIR",
|
|
91
|
+
dest="output_path",
|
|
92
|
+
type=Path,
|
|
93
|
+
default=Path.cwd().absolute(),
|
|
94
|
+
)
|
|
95
|
+
parser.add_argument(
|
|
96
|
+
"-n",
|
|
97
|
+
"--name",
|
|
98
|
+
metavar="DIR",
|
|
99
|
+
dest="pipeline_name",
|
|
100
|
+
default=str(Path.cwd().name),
|
|
101
|
+
help="the name of the pipeline",
|
|
102
|
+
)
|
|
103
|
+
parser.set_defaults(func=bundle_command)
|
|
104
|
+
return parser
|
cici/cli/fmt.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from importlib import import_module
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from termcolor import colored
|
|
5
|
+
|
|
6
|
+
from ..constants import DEFAULT_PROVIDER
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def fmt_command(parser, args):
|
|
10
|
+
provider = import_module(f".{DEFAULT_PROVIDER}", "cici.providers")
|
|
11
|
+
|
|
12
|
+
if not args.filenames:
|
|
13
|
+
args.filenames = [provider.CI_FILE]
|
|
14
|
+
|
|
15
|
+
for filename in args.filenames:
|
|
16
|
+
if not Path(filename).exists():
|
|
17
|
+
parser.error(f"file not found: {filename}")
|
|
18
|
+
|
|
19
|
+
for filename in args.filenames:
|
|
20
|
+
file = provider.load(filename)
|
|
21
|
+
with open(filename, "w") as stream:
|
|
22
|
+
provider.dump(file, stream)
|
|
23
|
+
print(colored(filename, "magenta"), "formatted")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def fmt_parser(subparsers):
|
|
27
|
+
parser = subparsers.add_parser("fmt", help="Reformat CI file")
|
|
28
|
+
parser.add_argument("filenames", metavar="FILE", nargs="*")
|
|
29
|
+
parser.set_defaults(func=fmt_command)
|
|
30
|
+
return parser
|
cici/cli/update.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import urllib.error
|
|
4
|
+
import urllib.parse
|
|
5
|
+
import urllib.request
|
|
6
|
+
|
|
7
|
+
import ruamel.yaml
|
|
8
|
+
from termcolor import colored
|
|
9
|
+
|
|
10
|
+
GITLAB_URL = "https://gitlab.com"
|
|
11
|
+
API_V4_URL = f"{GITLAB_URL}/api/v4"
|
|
12
|
+
|
|
13
|
+
HEADERS: dict[str, str] = {}
|
|
14
|
+
private_token = os.environ.get("GITLAB_PRIVATE_TOKEN")
|
|
15
|
+
if private_token:
|
|
16
|
+
HEADERS.update({"PRIVATE-TOKEN": private_token})
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def to_fragment(text):
|
|
20
|
+
if not isinstance(text, str):
|
|
21
|
+
return text
|
|
22
|
+
return urllib.parse.quote(text, safe="")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_apiv4_url(url):
|
|
26
|
+
try:
|
|
27
|
+
url = f"{API_V4_URL}{url}"
|
|
28
|
+
request = urllib.request.Request(url=url, headers=HEADERS)
|
|
29
|
+
response = urllib.request.urlopen(request)
|
|
30
|
+
content = response.read()
|
|
31
|
+
return json.loads(content)
|
|
32
|
+
except urllib.error.HTTPError:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_project(project_id):
|
|
37
|
+
return get_apiv4_url(f"/projects/{to_fragment(project_id)}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_latest_release(project_id):
|
|
41
|
+
return get_apiv4_url(
|
|
42
|
+
f"/projects/{to_fragment(project_id)}/releases/permalink/latest"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def cproject(project_name):
|
|
47
|
+
return colored(project_name, "magenta")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def ctag(tag_name):
|
|
51
|
+
return colored(tag_name, "yellow", attrs=["bold"])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def update_include(include):
|
|
55
|
+
if any(key not in include for key in ("project", "file")):
|
|
56
|
+
return include
|
|
57
|
+
|
|
58
|
+
project = get_project(include["project"])
|
|
59
|
+
project_name = project["path_with_namespace"]
|
|
60
|
+
|
|
61
|
+
latest_release = get_latest_release(project["id"])
|
|
62
|
+
if latest_release:
|
|
63
|
+
latest_tag = latest_release["tag_name"]
|
|
64
|
+
current_tag = include.get("ref", None)
|
|
65
|
+
if current_tag:
|
|
66
|
+
if current_tag != latest_tag:
|
|
67
|
+
print(
|
|
68
|
+
colored(project_name, "red", attrs=["bold"]),
|
|
69
|
+
"updated from {current} to {latest}".format(
|
|
70
|
+
current=ctag(current_tag),
|
|
71
|
+
latest=ctag(latest_tag),
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
print(cproject(project_name), "is the latest at", ctag(current_tag))
|
|
76
|
+
elif not current_tag:
|
|
77
|
+
print(cproject(project_name), "pinned to", ctag(latest_tag))
|
|
78
|
+
include["ref"] = latest_tag
|
|
79
|
+
else:
|
|
80
|
+
print(cproject(project_name), "has no releases")
|
|
81
|
+
newinclude = {}
|
|
82
|
+
newinclude["project"] = include["project"]
|
|
83
|
+
if "ref" in include:
|
|
84
|
+
newinclude["ref"] = include["ref"]
|
|
85
|
+
newinclude["file"] = include["file"]
|
|
86
|
+
return newinclude
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def update_includes(includes):
|
|
90
|
+
if not isinstance(includes, list):
|
|
91
|
+
return includes
|
|
92
|
+
|
|
93
|
+
return [update_include(include) for include in includes]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def update_command(parser, args):
|
|
97
|
+
yaml = ruamel.yaml.YAML()
|
|
98
|
+
data = yaml.load(open(args.filename))
|
|
99
|
+
|
|
100
|
+
if "include" in data:
|
|
101
|
+
data["include"] = update_includes(data["include"])
|
|
102
|
+
|
|
103
|
+
with open(args.filename, "w") as handle:
|
|
104
|
+
yaml.indent(mapping=2, sequence=4, offset=2)
|
|
105
|
+
yaml.dump(data, handle)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def update_parser(subparsers):
|
|
109
|
+
parser = subparsers.add_parser("update")
|
|
110
|
+
parser.add_argument("filename", nargs="?", default=".gitlab-ci.yml")
|
|
111
|
+
parser.set_defaults(func=update_command)
|
|
112
|
+
return parser
|
cici/constants.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
BASE_DIR = Path(__file__).parent.absolute()
|
|
4
|
+
|
|
5
|
+
SCHEMA_DIR = BASE_DIR / "schema"
|
|
6
|
+
|
|
7
|
+
COMMAND_DIR = BASE_DIR / "cli"
|
|
8
|
+
|
|
9
|
+
COMMANDS = sorted(
|
|
10
|
+
[path.stem for path in COMMAND_DIR.glob("*.py") if not path.stem.startswith("_")]
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
PROVIDER_DIR = BASE_DIR / "providers"
|
|
14
|
+
|
|
15
|
+
PROVIDERS = sorted(
|
|
16
|
+
[path.stem for path in PROVIDER_DIR.glob("*.py") if not path.stem.startswith("_")]
|
|
17
|
+
+ [
|
|
18
|
+
path.stem
|
|
19
|
+
for path in PROVIDER_DIR.glob("*/")
|
|
20
|
+
if path.is_dir() and not path.stem.startswith("_")
|
|
21
|
+
]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
DEFAULT_PROVIDER = PROVIDERS[0]
|
|
25
|
+
|
|
26
|
+
CONFIG_DIR_NAME = ".cici"
|
cici/main.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
|
|
4
|
+
from .constants import COMMANDS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
parser = argparse.ArgumentParser()
|
|
9
|
+
|
|
10
|
+
subparsers = parser.add_subparsers(required=True)
|
|
11
|
+
|
|
12
|
+
for command_name in COMMANDS:
|
|
13
|
+
command = import_module(f".{command_name}", "cici.cli")
|
|
14
|
+
getattr(command, f"{command_name}_parser")(subparsers=subparsers)
|
|
15
|
+
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
args.func(parser=parser, args=args)
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
ACCESS = "access"
|
|
2
|
+
ALWAYS = "always"
|
|
3
|
+
DEVELOPMENT = "development"
|
|
4
|
+
IF_NOT_PRESENT = "if-not-present"
|
|
5
|
+
NEVER = "never"
|
|
6
|
+
ON_SUCCESS = "on_success"
|
|
7
|
+
ON_FAILURE = "on_failure"
|
|
8
|
+
OTHER = "other"
|
|
9
|
+
PREPARE = "prepare"
|
|
10
|
+
PRODUCTION = "production"
|
|
11
|
+
PULL = "pull"
|
|
12
|
+
PULL_PUSH = "pull-push"
|
|
13
|
+
PUSH = "push"
|
|
14
|
+
STAGING = "staging"
|
|
15
|
+
START = "start"
|
|
16
|
+
STOP = "stop"
|
|
17
|
+
TESTING = "testing"
|
|
18
|
+
VERIFY = "verify"
|
|
19
|
+
|
|
20
|
+
CACHE_POLICIES = (PULL, PUSH, PULL_PUSH)
|
|
21
|
+
|
|
22
|
+
DEPLOYMENT_TIERS = (
|
|
23
|
+
PRODUCTION,
|
|
24
|
+
STAGING,
|
|
25
|
+
TESTING,
|
|
26
|
+
DEVELOPMENT,
|
|
27
|
+
OTHER,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
ENVIRONMENT_ACTIONS = (
|
|
31
|
+
START,
|
|
32
|
+
PREPARE,
|
|
33
|
+
STOP,
|
|
34
|
+
VERIFY,
|
|
35
|
+
ACCESS,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
PULL_POLICIES = (
|
|
39
|
+
ALWAYS,
|
|
40
|
+
IF_NOT_PRESENT,
|
|
41
|
+
NEVER,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
WHEN_CHOICES = (ON_SUCCESS, ON_FAILURE, ALWAYS)
|
|
45
|
+
|
|
46
|
+
RETRY_MAX_CHOICES = (0, 1, 2)
|
|
47
|
+
|
|
48
|
+
UNKNOWN_FAILURE = "unknown_failure"
|
|
49
|
+
SCRIPT_FAILURE = "script_failure"
|
|
50
|
+
API_FAILURE = "api_failure"
|
|
51
|
+
STUCK_OR_TIMEOUT_FAILURE = "stuck_or_timeout_failure"
|
|
52
|
+
RUNNER_SYSTEM_FAILURE = "runner_system_failure"
|
|
53
|
+
RUNNER_UNSUPPORTED = "runner_unsupported"
|
|
54
|
+
STALE_SCHEDULE = "stale_schedule"
|
|
55
|
+
JOB_EXECUTION_TIMEOUT = "job_execution_timeout"
|
|
56
|
+
ARCHIVED_FAILURE = "archived_failure"
|
|
57
|
+
UNMET_PREREQUISITES = "unmet_prerequisites"
|
|
58
|
+
SCHEDULER_FAILURE = "scheduler_failure"
|
|
59
|
+
DATA_INTEGRITY_FAILURE = "data_integrity_failure"
|
|
60
|
+
|
|
61
|
+
RETRY_WHEN_CHOICES = (
|
|
62
|
+
ALWAYS,
|
|
63
|
+
UNKNOWN_FAILURE,
|
|
64
|
+
SCRIPT_FAILURE,
|
|
65
|
+
API_FAILURE,
|
|
66
|
+
STUCK_OR_TIMEOUT_FAILURE,
|
|
67
|
+
RUNNER_SYSTEM_FAILURE,
|
|
68
|
+
RUNNER_UNSUPPORTED,
|
|
69
|
+
STALE_SCHEDULE,
|
|
70
|
+
JOB_EXECUTION_TIMEOUT,
|
|
71
|
+
ARCHIVED_FAILURE,
|
|
72
|
+
UNMET_PREREQUISITES,
|
|
73
|
+
SCHEDULER_FAILURE,
|
|
74
|
+
DATA_INTEGRITY_FAILURE,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
CI_FILE = ".gitlab-ci.yml"
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
import cattrs
|
|
5
|
+
from cattrs.gen import make_dict_structure_fn, make_dict_unstructure_fn, override
|
|
6
|
+
|
|
7
|
+
from ...utils import (
|
|
8
|
+
make_quoted_dict,
|
|
9
|
+
make_quoted_list_or_string,
|
|
10
|
+
make_quoted_string,
|
|
11
|
+
make_scalar_list,
|
|
12
|
+
)
|
|
13
|
+
from . import models
|
|
14
|
+
|
|
15
|
+
CONVERTER = cattrs.Converter(omit_if_default=True, forbid_extra_keys=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
make_dict_struct = functools.partial(
|
|
19
|
+
make_dict_structure_fn,
|
|
20
|
+
_cattrs_forbid_extra_keys=True,
|
|
21
|
+
_cattrs_omit_if_default=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
make_dict_unstruct = functools.partial(
|
|
25
|
+
make_dict_unstructure_fn,
|
|
26
|
+
_cattrs_forbid_extra_keys=True,
|
|
27
|
+
_cattrs_omit_if_default=True,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def quoted_string_struct_hook(object, __):
|
|
32
|
+
return make_quoted_string(object)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def quoted_list_or_string_struct_hook(object, __):
|
|
36
|
+
return make_quoted_list_or_string(object)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def quoted_dict_struct_hook(object, __):
|
|
40
|
+
return make_quoted_dict(object)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def structure_image(object, __):
|
|
44
|
+
if object is None:
|
|
45
|
+
return None
|
|
46
|
+
elif isinstance(object, str):
|
|
47
|
+
return make_quoted_string(CONVERTER.structure(object, str))
|
|
48
|
+
return CONVERTER.structure(object, models.Image)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def structure_include(object, __):
|
|
52
|
+
if isinstance(object, str):
|
|
53
|
+
return CONVERTER.structure(object, str)
|
|
54
|
+
items = []
|
|
55
|
+
for item in object:
|
|
56
|
+
if "local" in item:
|
|
57
|
+
items.append(CONVERTER.structure(item, models.IncludeLocal))
|
|
58
|
+
elif "project" in item:
|
|
59
|
+
items.append(CONVERTER.structure(item, models.IncludeProject))
|
|
60
|
+
elif "remote" in item:
|
|
61
|
+
items.append(CONVERTER.structure(item, models.IncludeRemote))
|
|
62
|
+
elif "template" in item:
|
|
63
|
+
items.append(CONVERTER.structure(item, models.IncludeTemplate))
|
|
64
|
+
return items
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def structure_script(object, __):
|
|
68
|
+
if isinstance(object, str):
|
|
69
|
+
return CONVERTER.structure(object, str)
|
|
70
|
+
items = []
|
|
71
|
+
for item in object:
|
|
72
|
+
if isinstance(item, str):
|
|
73
|
+
items.append(CONVERTER.structure(item, str))
|
|
74
|
+
else:
|
|
75
|
+
for entry in item:
|
|
76
|
+
items.append(CONVERTER.structure(entry, str))
|
|
77
|
+
return make_scalar_list(items)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def structure_service(object, __):
|
|
81
|
+
if isinstance(object, str):
|
|
82
|
+
return make_quoted_string(CONVERTER.structure(object, str))
|
|
83
|
+
return CONVERTER.structure(object, models.Service)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def structure_variables(object, __):
|
|
87
|
+
return make_quoted_dict(object)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
CONVERTER.register_structure_hook(
|
|
91
|
+
Union[
|
|
92
|
+
str,
|
|
93
|
+
list[
|
|
94
|
+
Union[
|
|
95
|
+
models.IncludeLocal,
|
|
96
|
+
models.IncludeProject,
|
|
97
|
+
models.IncludeRemote,
|
|
98
|
+
models.IncludeTemplate,
|
|
99
|
+
]
|
|
100
|
+
],
|
|
101
|
+
],
|
|
102
|
+
structure_include,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
CONVERTER.register_structure_hook(
|
|
107
|
+
Union[str, list[str]], quoted_list_or_string_struct_hook
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
CONVERTER.register_structure_hook(
|
|
111
|
+
Union[str, models.Service],
|
|
112
|
+
lambda object, _: CONVERTER.structure(
|
|
113
|
+
object, str if isinstance(object, str) else models.Service
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
CONVERTER.register_structure_hook(
|
|
118
|
+
Union[int, models.Retry],
|
|
119
|
+
lambda object, _: CONVERTER.structure(
|
|
120
|
+
object, int if isinstance(object, int) else models.Retry
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
CONVERTER.register_structure_hook(
|
|
126
|
+
Union[str, list[Union[str, list[str]]]],
|
|
127
|
+
structure_script,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
CONVERTER.register_structure_hook(
|
|
131
|
+
models.Image,
|
|
132
|
+
make_dict_struct(
|
|
133
|
+
models.Image,
|
|
134
|
+
CONVERTER,
|
|
135
|
+
name=override(struct_hook=quoted_string_struct_hook),
|
|
136
|
+
entrypoint=override(struct_hook=quoted_list_or_string_struct_hook),
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
CONVERTER.register_structure_hook(
|
|
141
|
+
models.Service,
|
|
142
|
+
make_dict_struct(
|
|
143
|
+
models.Service,
|
|
144
|
+
CONVERTER,
|
|
145
|
+
name=override(struct_hook=quoted_string_struct_hook),
|
|
146
|
+
command=override(struct_hook=quoted_list_or_string_struct_hook),
|
|
147
|
+
entrypoint=override(struct_hook=quoted_list_or_string_struct_hook),
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def unstructure_script(cls):
|
|
153
|
+
object = CONVERTER.unstructure(cls, str if isinstance(cls, str) else list[str])
|
|
154
|
+
if isinstance(object, str):
|
|
155
|
+
return object # make_quoted_scalar_string(object)
|
|
156
|
+
return make_scalar_list(object)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
CONVERTER.register_unstructure_hook(
|
|
160
|
+
Union[str, list[str]],
|
|
161
|
+
unstructure_script,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
CONVERTER.register_structure_hook(
|
|
165
|
+
Union[str, models.Image, None],
|
|
166
|
+
structure_image,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
CONVERTER.register_structure_hook(
|
|
170
|
+
models.Rule,
|
|
171
|
+
make_dict_struct(
|
|
172
|
+
models.Rule,
|
|
173
|
+
CONVERTER,
|
|
174
|
+
if_=override(rename="if"),
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
CONVERTER.register_unstructure_hook(
|
|
178
|
+
models.Rule,
|
|
179
|
+
make_dict_unstruct(
|
|
180
|
+
models.Rule,
|
|
181
|
+
CONVERTER,
|
|
182
|
+
if_=override(rename="if"),
|
|
183
|
+
),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
CONVERTER.register_structure_hook(
|
|
188
|
+
models.Job,
|
|
189
|
+
make_dict_struct(
|
|
190
|
+
models.Job,
|
|
191
|
+
CONVERTER,
|
|
192
|
+
variables=override(struct_hook=quoted_dict_struct_hook),
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
CONVERTER.register_structure_hook(
|
|
197
|
+
models.File,
|
|
198
|
+
make_dict_struct(
|
|
199
|
+
models.File,
|
|
200
|
+
CONVERTER,
|
|
201
|
+
variables=override(struct_hook=quoted_dict_struct_hook),
|
|
202
|
+
),
|
|
203
|
+
)
|