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

View File

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

View File

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

View File

@ -115,10 +115,11 @@ class Status(Command):
containers: DefaultDict[ContainerType, Set[Container]] = defaultdict(set) containers: DefaultDict[ContainerType, Set[Container]] = defaultdict(set)
for task in tasks: for task in tasks:
for container in task.config.containers: if task.config.containers is not None:
if container.type not in containers: for container in task.config.containers:
containers[container.type] = set() if container.type not in containers:
containers[container.type].add(container.name) containers[container.type] = set()
containers[container.type].add(container.name)
print("\ncontainers:") print("\ncontainers:")
for container_type in containers: 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): for task in self.onefuzz.tasks.list(job_id=job.job_id):
self.cache.add_task(task) self.cache.add_task(task)
for container in task.config.containers: if task.config.containers is not None:
self.add_container(container.name) for container in task.config.containers:
self.add_container(container.name)
nodes = self.onefuzz.nodes.list() nodes = self.onefuzz.nodes.list()
for node in nodes: for node in nodes:

View File

@ -63,7 +63,11 @@ class Template(Command):
self.onefuzz.tasks.delete(task.task_id) self.onefuzz.tasks.delete(task.task_id)
if stop_notifications: 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( notifications = self.onefuzz.notifications.list(
container=container_names container=container_names
) )

View File

@ -7,7 +7,7 @@ from datetime import datetime
from typing import Any, Dict, Generic, List, Optional, TypeVar, Union from typing import Any, Dict, Generic, List, Optional, TypeVar, Union
from uuid import UUID, uuid4 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 pydantic.dataclasses import dataclass
from ._monkeypatch import _check_hotfix from ._monkeypatch import _check_hotfix
@ -193,8 +193,8 @@ class TaskConfig(BaseModel):
task: TaskDetails task: TaskDetails
vm: Optional[TaskVm] vm: Optional[TaskVm]
pool: Optional[TaskPool] pool: Optional[TaskPool]
containers: List[TaskContainers] containers: Optional[List[TaskContainers]]
tags: Dict[str, str] tags: Optional[Dict[str, str]]
debug: Optional[List[TaskDebugFlag]] debug: Optional[List[TaskDebugFlag]]
colocate: Optional[bool] colocate: Optional[bool]
@ -870,6 +870,18 @@ class ApiAccessRule(BaseModel):
allowed_groups: List[UUID] 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 Endpoint = str
# json dumps doesn't support UUID as dictionary key # json dumps doesn't support UUID as dictionary key
PrincipalID = str PrincipalID = str

View File

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

View File

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