mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-18 04:38:09 +00:00
Validate scriban from cli (#2800)
* Add validate scriban endpoint to cli * missed a file * Lint -- I miss C# * docs
This commit is contained in:
@ -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"
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user