Validate scriban from cli (#2800)

* Add validate scriban endpoint to cli

* missed a file

* Lint -- I miss C#

* docs
This commit is contained in:
Teo Voinea
2023-02-06 08:32:49 -05:00
committed by GitHub
parent 8f418f692c
commit c1f6dfc366
9 changed files with 109 additions and 63 deletions

View File

@ -330,9 +330,7 @@ If webhook is set to have Event Grid message format then the payload will look a
},
"required": [
"job_id",
"task",
"containers",
"tags"
"task"
],
"title": "TaskConfig",
"type": "object"
@ -2239,9 +2237,7 @@ If webhook is set to have Event Grid message format then the payload will look a
},
"required": [
"job_id",
"task",
"containers",
"tags"
"task"
],
"title": "TaskConfig",
"type": "object"
@ -2969,9 +2965,7 @@ If webhook is set to have Event Grid message format then the payload will look a
},
"required": [
"job_id",
"task",
"containers",
"tags"
"task"
],
"title": "TaskConfig",
"type": "object"
@ -3490,9 +3484,7 @@ If webhook is set to have Event Grid message format then the payload will look a
},
"required": [
"job_id",
"task",
"containers",
"tags"
"task"
],
"title": "TaskConfig",
"type": "object"
@ -3954,9 +3946,7 @@ If webhook is set to have Event Grid message format then the payload will look a
},
"required": [
"job_id",
"task",
"containers",
"tags"
"task"
],
"title": "TaskConfig",
"type": "object"
@ -4392,9 +4382,7 @@ If webhook is set to have Event Grid message format then the payload will look a
},
"required": [
"job_id",
"task",
"containers",
"tags"
"task"
],
"title": "TaskConfig",
"type": "object"
@ -4857,9 +4845,7 @@ If webhook is set to have Event Grid message format then the payload will look a
},
"required": [
"job_id",
"task",
"containers",
"tags"
"task"
],
"title": "TaskConfig",
"type": "object"
@ -6606,9 +6592,7 @@ If webhook is set to have Event Grid message format then the payload will look a
},
"required": [
"job_id",
"task",
"containers",
"tags"
"task"
],
"title": "TaskConfig",
"type": "object"

View File

@ -529,10 +529,11 @@ class Containers(Endpoint):
) -> None:
to_download: Dict[str, str] = {}
for task in tasks:
for container in task.config.containers:
info = self.onefuzz.containers.get(container.name)
name = os.path.join(container.type.name, container.name)
to_download[name] = info.sas_url
if task.config.containers is not None:
for container in task.config.containers:
info = self.onefuzz.containers.get(container.name)
name = os.path.join(container.type.name, container.name)
to_download[name] = info.sas_url
if output is None:
output = primitives.Directory(os.getcwd())
@ -1099,9 +1100,14 @@ class JobContainers(Endpoint):
containers = set()
tasks = self.onefuzz.tasks.list(job_id=job_id, state=[])
for task in tasks:
containers.update(
set(x.name for x in task.config.containers if x.type == container_type)
)
if task.config.containers is not None:
containers.update(
set(
x.name
for x in task.config.containers
if x.type == container_type
)
)
results: Dict[str, List[str]] = {}
for container in containers:
@ -1133,23 +1139,24 @@ class JobContainers(Endpoint):
containers = set()
to_delete = set()
for task in self.onefuzz.jobs.tasks.list(job_id=job.job_id):
for container in task.config.containers:
containers.add(container.name)
if container.type not in SAFE_TO_REMOVE:
continue
elif not only_job_specific:
to_delete.add(container.name)
elif only_job_specific and (
self.onefuzz.utils.build_container_name(
container_type=container.type,
project=job.config.project,
name=job.config.name,
build=job.config.build,
platform=task.os,
)
== container.name
):
to_delete.add(container.name)
if task.config.containers is not None:
for container in task.config.containers:
containers.add(container.name)
if container.type not in SAFE_TO_REMOVE:
continue
elif not only_job_specific:
to_delete.add(container.name)
elif only_job_specific and (
self.onefuzz.utils.build_container_name(
container_type=container.type,
project=job.config.project,
name=job.config.name,
build=job.config.build,
platform=task.os,
)
== container.name
):
to_delete.add(container.name)
to_keep = containers - to_delete
for container_name in to_keep:
@ -1713,6 +1720,17 @@ class InstanceConfigCmd(Endpoint):
)
class ValidateScriban(Endpoint):
"""Interact with Validate Scriban"""
endpoint = "ValidateScriban"
def post(
self, req: requests.TemplateValidationPost
) -> responses.TemplateValidationResponse:
return self._req_model("POST", responses.TemplateValidationResponse, data=req)
class Command:
def __init__(self, onefuzz: "Onefuzz", logger: logging.Logger):
self.onefuzz = onefuzz
@ -1803,6 +1821,7 @@ class Onefuzz:
self.webhooks = Webhooks(self)
self.tools = Tools(self)
self.instance_config = InstanceConfigCmd(self)
self.validate_scriban = ValidateScriban(self)
if self._backend.is_feature_enabled(PreviewFeature.job_templates.name):
self.job_templates = JobTemplates(self)

View File

@ -18,9 +18,11 @@ from azure.applicationinsights import ApplicationInsightsDataClient
from azure.applicationinsights.models import QueryBody
from azure.identity import AzureCliCredential
from azure.storage.blob import ContainerClient
from onefuzztypes import models, requests
from onefuzztypes.enums import ContainerType, TaskType
from onefuzztypes.models import BlobRef, Job, NodeAssignment, Report, Task, TaskConfig
from onefuzztypes.primitives import Container, Directory, PoolName
from onefuzztypes.responses import TemplateValidationResponse
from onefuzz.api import UUID_EXPANSION, Command, Onefuzz
@ -721,9 +723,10 @@ class DebugNotification(Command):
def _get_container(
self, task: Task, container_type: ContainerType
) -> Optional[Container]:
for container in task.config.containers:
if container.type == container_type:
return container.name
if task.config.containers is not None:
for container in task.config.containers:
if container.type == container_type:
return container.name
return None
def _get_storage_account(self, container_name: Container) -> str:
@ -731,6 +734,13 @@ class DebugNotification(Command):
_, netloc, _, _, _, _ = urlparse(sas_url)
return netloc.split(".")[0]
def template(
self, template: str, context: Optional[models.TemplateRenderContext]
) -> TemplateValidationResponse:
"""Validate scriban rendering of notification config"""
req = requests.TemplateValidationPost(template=template, context=context)
return self.onefuzz.validate_scriban.post(req)
def job(
self,
job_id: UUID_EXPANSION,

View File

@ -115,10 +115,11 @@ class Status(Command):
containers: DefaultDict[ContainerType, Set[Container]] = defaultdict(set)
for task in tasks:
for container in task.config.containers:
if container.type not in containers:
containers[container.type] = set()
containers[container.type].add(container.name)
if task.config.containers is not None:
for container in task.config.containers:
if container.type not in containers:
containers[container.type] = set()
containers[container.type].add(container.name)
print("\ncontainers:")
for container_type in containers:

View File

@ -68,8 +68,9 @@ class Top:
for task in self.onefuzz.tasks.list(job_id=job.job_id):
self.cache.add_task(task)
for container in task.config.containers:
self.add_container(container.name)
if task.config.containers is not None:
for container in task.config.containers:
self.add_container(container.name)
nodes = self.onefuzz.nodes.list()
for node in nodes:

View File

@ -63,7 +63,11 @@ class Template(Command):
self.onefuzz.tasks.delete(task.task_id)
if stop_notifications:
container_names = [x.name for x in task.config.containers]
container_names = (
[x.name for x in task.config.containers]
if task.config.containers is not None
else []
)
notifications = self.onefuzz.notifications.list(
container=container_names
)

View File

@ -7,7 +7,7 @@ from datetime import datetime
from typing import Any, Dict, Generic, List, Optional, TypeVar, Union
from uuid import UUID, uuid4
from pydantic import BaseModel, Field, root_validator, validator
from pydantic import AnyHttpUrl, BaseModel, Field, root_validator, validator
from pydantic.dataclasses import dataclass
from ._monkeypatch import _check_hotfix
@ -193,8 +193,8 @@ class TaskConfig(BaseModel):
task: TaskDetails
vm: Optional[TaskVm]
pool: Optional[TaskPool]
containers: List[TaskContainers]
tags: Dict[str, str]
containers: Optional[List[TaskContainers]]
tags: Optional[Dict[str, str]]
debug: Optional[List[TaskDebugFlag]]
colocate: Optional[bool]
@ -870,6 +870,18 @@ class ApiAccessRule(BaseModel):
allowed_groups: List[UUID]
class TemplateRenderContext(BaseModel):
report: Report
task: TaskConfig
job: JobConfig
report_url: AnyHttpUrl
input_url: AnyHttpUrl
target_url: AnyHttpUrl
report_container: Container
report_filename: str
repro_cmd: str
Endpoint = str
# json dumps doesn't support UUID as dictionary key
PrincipalID = str

View File

@ -20,7 +20,12 @@ from .enums import (
TaskState,
)
from .events import EventType
from .models import AutoScaleConfig, InstanceConfig, NotificationConfig
from .models import (
AutoScaleConfig,
InstanceConfig,
NotificationConfig,
TemplateRenderContext,
)
from .primitives import Container, PoolName, Region
from .webhooks import WebhookMessageFormat
@ -252,4 +257,9 @@ class InstanceConfigUpdate(BaseModel):
config: InstanceConfig
class TemplateValidationPost(BaseModel):
template: str
context: Optional[TemplateRenderContext]
_check_hotfix()

View File

@ -9,7 +9,7 @@ from uuid import UUID
from pydantic import BaseModel
from .enums import VmState
from .models import Forward, NodeCommandEnvelope
from .models import Forward, NodeCommandEnvelope, TemplateRenderContext
from .primitives import Region
@ -84,3 +84,8 @@ class PendingNodeCommand(BaseResponse):
class CanSchedule(BaseResponse):
allowed: bool
work_stopped: bool
class TemplateValidationResponse(BaseResponse):
rendered_template: str
available_context: TemplateRenderContext