Coverage for cli / commands / templates_cmd.py: 100.00%
138 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-10-28 16:19 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-10-28 16:19 +0000
1"""Job template commands."""
3import sys
4from typing import Any
6import click
8from ..config import GCOConfig
9from ..output import confirm, get_output_formatter
11pass_config = click.make_pass_decorator(GCOConfig, ensure=True)
14@click.group()
15@pass_config
16def templates(config: Any) -> None:
17 """Manage job templates.
19 Templates are reusable job configurations stored in DynamoDB.
20 They support parameter substitution using {{parameter}} syntax.
21 """
24@templates.command("list")
25@click.option("--region", "-r", help="Region to query (any region works)")
26@pass_config
27def templates_list(config: Any, region: Any) -> None:
28 """List all job templates.
30 Examples:
31 gco templates list
32 """
33 formatter = get_output_formatter(config)
35 try:
36 from ..aws_client import get_aws_client
38 aws_client = get_aws_client(config)
40 query_region = region or (config.default_region if config.use_regional_api else None)
41 result = aws_client.call_api(
42 method="GET",
43 path="/api/v1/templates",
44 region=query_region,
45 )
47 if config.output_format == "table":
48 templates_data = result.get("templates", [])
49 if not templates_data:
50 formatter.print_info("No templates found")
51 return
53 print(f"\n Job Templates ({result.get('count', 0)} total)")
54 print(" " + "-" * 70)
55 print(" NAME DESCRIPTION CREATED")
56 print(" " + "-" * 70)
57 for t in templates_data:
58 name = t.get("name", "")[:28]
59 desc = (t.get("description") or "")[:28]
60 created = (t.get("created_at") or "")[:19]
61 print(f" {name:<30} {desc:<30} {created}")
62 else:
63 formatter.print(result)
65 except Exception as e:
66 formatter.print_error(f"Failed to list templates: {e}")
67 sys.exit(1)
70@templates.command("get")
71@click.argument("name")
72@click.option("--region", "-r", help="Region to query (any region works)")
73@pass_config
74def templates_get(config: Any, name: Any, region: Any) -> None:
75 """Get details of a specific template.
77 Examples:
78 gco templates get gpu-training-template
79 """
80 formatter = get_output_formatter(config)
82 try:
83 from ..aws_client import get_aws_client
85 aws_client = get_aws_client(config)
87 query_region = region or (config.default_region if config.use_regional_api else None)
88 result = aws_client.call_api(
89 method="GET",
90 path=f"/api/v1/templates/{name}",
91 region=query_region,
92 )
94 template = result.get("template", {})
96 if config.output_format == "table":
97 print(f"\n Template: {template.get('name')}")
98 print(" " + "-" * 50)
99 print(f" Description: {template.get('description') or 'N/A'}")
100 print(f" Created: {template.get('created_at')}")
101 print(f" Updated: {template.get('updated_at')}")
103 params = template.get("parameters", {})
104 if params:
105 print("\n Default Parameters:")
106 for k, v in params.items():
107 print(f" {k}: {v}")
109 print("\n Manifest:")
110 import json
112 print(json.dumps(template.get("manifest", {}), indent=4))
113 else:
114 formatter.print(result)
116 except Exception as e:
117 formatter.print_error(f"Failed to get template: {e}")
118 sys.exit(1)
121@templates.command("create")
122@click.argument("manifest_path", type=click.Path(exists=True))
123@click.option("--name", "-n", required=True, help="Template name")
124@click.option("--description", "-d", help="Template description")
125@click.option("--param", "-p", multiple=True, help="Default parameter (key=value)")
126@click.option("--region", "-r", help="Region to use (any region works)")
127@pass_config
128def templates_create(
129 config: Any, manifest_path: Any, name: Any, description: Any, param: Any, region: Any
130) -> None:
131 """Create a new job template from a manifest file.
133 The manifest can contain {{parameter}} placeholders that will be
134 substituted when creating jobs from the template.
136 Examples:
137 gco templates create job.yaml --name gpu-template -d "GPU training template"
138 gco templates create job.yaml -n my-template -p image=pytorch:latest -p gpus=4
139 """
140 import yaml
142 formatter = get_output_formatter(config)
144 # Parse parameters
145 parameters = {}
146 for p in param:
147 if "=" in p:
148 k, v = p.split("=", 1)
149 parameters[k] = v
151 try:
152 # Load manifest
153 with open(manifest_path, encoding="utf-8") as f:
154 manifest = yaml.safe_load(f)
156 from ..aws_client import get_aws_client
158 aws_client = get_aws_client(config)
160 query_region = region or (config.default_region if config.use_regional_api else None)
161 result = aws_client.call_api(
162 method="POST",
163 path="/api/v1/templates",
164 region=query_region,
165 body={
166 "name": name,
167 "description": description,
168 "manifest": manifest,
169 "parameters": parameters if parameters else None,
170 },
171 )
173 formatter.print_success(f"Template '{name}' created successfully")
174 formatter.print(result)
176 except Exception as e:
177 formatter.print_error(f"Failed to create template: {e}")
178 sys.exit(1)
181@templates.command("delete")
182@click.argument("name")
183@click.option("--region", "-r", help="Region to use (any region works)")
184@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
185@pass_config
186def templates_delete(config: Any, name: Any, region: Any, yes: Any) -> None:
187 """Delete a job template.
189 Examples:
190 gco templates delete old-template
191 gco templates delete old-template -y
192 """
193 formatter = get_output_formatter(config)
195 if not yes:
196 confirm(f"Delete template '{name}'?", abort=True)
198 try:
199 from ..aws_client import get_aws_client
201 aws_client = get_aws_client(config)
203 query_region = region or (config.default_region if config.use_regional_api else None)
204 result = aws_client.call_api(
205 method="DELETE",
206 path=f"/api/v1/templates/{name}",
207 region=query_region,
208 )
210 formatter.print_success(f"Template '{name}' deleted")
211 formatter.print(result)
213 except Exception as e:
214 formatter.print_error(f"Failed to delete template: {e}")
215 sys.exit(1)
218@templates.command("run")
219@click.argument("template_name")
220@click.option("--name", "-n", required=True, help="Job name")
221@click.option("--namespace", default="gco-jobs", help="Kubernetes namespace")
222@click.option("--param", "-p", multiple=True, help="Parameter override (key=value)")
223@click.option("--region", "-r", required=True, help="Region to run the job")
224@pass_config
225def templates_run(
226 config: Any, template_name: Any, name: Any, namespace: Any, param: Any, region: Any
227) -> None:
228 """Create and run a job from a template.
230 Examples:
231 gco templates run gpu-template --name my-job --region us-east-1
232 gco templates run gpu-template -n my-job -r us-east-1 -p image=custom:v1
233 """
234 formatter = get_output_formatter(config)
236 # Parse parameters
237 parameters = {}
238 for p in param:
239 if "=" in p:
240 k, v = p.split("=", 1)
241 parameters[k] = v
243 try:
244 from ..aws_client import get_aws_client
246 aws_client = get_aws_client(config)
248 result = aws_client.call_api(
249 method="POST",
250 path=f"/api/v1/jobs/from-template/{template_name}",
251 region=region,
252 body={
253 "name": name,
254 "namespace": namespace,
255 "parameters": parameters if parameters else None,
256 },
257 )
259 if result.get("success"):
260 formatter.print_success(f"Job '{name}' created from template '{template_name}'")
261 else:
262 formatter.print_error("Failed to create job from template")
264 formatter.print(result)
266 except Exception as e:
267 formatter.print_error(f"Failed to run template: {e}")
268 sys.exit(1)