diff --git a/apis/tilebox/v1/id.proto b/apis/tilebox/v1/id.proto index 1c0cf3d..bd07585 100644 --- a/apis/tilebox/v1/id.proto +++ b/apis/tilebox/v1/id.proto @@ -2,9 +2,16 @@ edition = "2023"; package tilebox.v1; +import "buf/validate/validate.proto"; + option features.field_presence = IMPLICIT; // Bytes field (in message) message ID { - bytes uuid = 1; + bytes uuid = 1 [(buf.validate.field).bytes = { + len: 16 + not_in: [ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // Nil UUID + ] + }]; } diff --git a/apis/workflows/v1/automation.proto b/apis/workflows/v1/automation.proto index 08b20a4..380ca75 100644 --- a/apis/workflows/v1/automation.proto +++ b/apis/workflows/v1/automation.proto @@ -4,6 +4,7 @@ edition = "2023"; package workflows.v1; +import "buf/validate/validate.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; import "tilebox/v1/id.proto"; @@ -14,16 +15,28 @@ option features.field_presence = IMPLICIT; // StorageType specifies a kind of storage bucket that we support. enum StorageType { STORAGE_TYPE_UNSPECIFIED = 0; - STORAGE_TYPE_GCS = 1; // Google Cloud Storage - STORAGE_TYPE_S3 = 2; // Amazon Web Services S3 - STORAGE_TYPE_FS = 3; // Local filesystem + // Google Cloud Storage + STORAGE_TYPE_GCS = 1; + // Amazon Web Services S3 + STORAGE_TYPE_S3 = 2; + // Local filesystem + STORAGE_TYPE_FS = 3; } // Storage location is some kind of storage that can contain data files or objects and be used as a trigger source. message StorageLocation { - tilebox.v1.ID id = 1; // Unique identifier for the storage location - string location = 2; // A unique identifier for the storage location in the storage system - StorageType type = 3; // The type of the storage location, e.g. GCS, S3, FS + // Unique identifier for the storage location + tilebox.v1.ID id = 1 [(buf.validate.field).required = true]; + // A unique identifier for the storage location in the storage system + string location = 2 [(buf.validate.field).string = { + min_bytes: 1 + max_bytes: 512 + }]; + // The type of the storage location, e.g. GCS, S3, FS + StorageType type = 3 [(buf.validate.field).enum = { + defined_only: true + not_in: [0] + }]; } // Buckets is a list of storage buckets @@ -34,16 +47,33 @@ message StorageLocations { // AutomationPrototype is a task prototype that can result in many submitted tasks. Task submissions are triggered by // NRT triggers, such as bucket triggers or cron triggers. message AutomationPrototype { - tilebox.v1.ID id = 1; // Unique identifier for the trigger - string name = 2; // A human-readable name for the trigger - TaskSubmission prototype = 3; // The task submission to trigger - - repeated StorageEventTrigger storage_event_triggers = 4; // The storage event triggers that will trigger the task - repeated CronTrigger cron_triggers = 5; // The cron triggers that will trigger the task - - bool disabled = 6; // Whether the automation is disabled (paused) or not. + option (buf.validate.message).oneof = { + fields: [ + "storage_event_triggers", + "cron_triggers" + ] + required: true + }; + + // Unique identifier for the trigger + tilebox.v1.ID id = 1; + // A human-readable name for the trigger + string name = 2 [(buf.validate.field).string = { + min_bytes: 1 + max_bytes: 1024 + }]; + // The task submission to trigger + TaskSubmission prototype = 3; + + // The storage event triggers that will trigger the task + repeated StorageEventTrigger storage_event_triggers = 4 [(buf.validate.field).repeated.max_items = 32]; + // The cron triggers that will trigger the task + repeated CronTrigger cron_triggers = 5 [(buf.validate.field).repeated.max_items = 32]; + + // Whether the automation is disabled (paused) or not. // the field is named disabled rather than enabled (with the semantics flipped) to make sure not setting the field at // all results in the automation being enabled by default. + bool disabled = 6; } // Automations is a list of automations @@ -54,15 +84,20 @@ message Automations { // StorageEventTrigger is a trigger that will trigger a task submission when an object matching the glob pattern is // created in a storage location. message StorageEventTrigger { - tilebox.v1.ID id = 1; // Unique identifier for the trigger - StorageLocation storage_location = 2; // The storage location to watch for events - string glob_pattern = 3; // A glob pattern to match objects/files in the storage location + // Unique identifier for the trigger + tilebox.v1.ID id = 1; + // The storage location to watch for events + StorageLocation storage_location = 2; + // A glob pattern to match objects/files in the storage location + string glob_pattern = 3 [(buf.validate.field).string.min_bytes = 1]; } // CronTrigger is a trigger that will trigger a task submission on a schedule. message CronTrigger { - tilebox.v1.ID id = 1; // Unique identifier for the trigger - string schedule = 2; // A cron schedule for the trigger, e.g. "0 0 * * *" (every day at midnight) + // Unique identifier for the trigger + tilebox.v1.ID id = 1; + // A cron schedule for the trigger, e.g. "0 0 * * *" (every day at midnight) + string schedule = 2 [(buf.validate.field).string.min_bytes = 1]; } // Automation is an actual submitted task that was triggered by a automation prototype. @@ -71,7 +106,8 @@ message Automation { // Details of the event that triggered the task. This is a serialized protobuf message. The type of the message // depends on the type of the trigger, either StorageEventTriggerEvent or CronTriggerEvent. bytes trigger_event = 1; - bytes args = 2; // Additional, user-defined arguments for the task, to be deserialized by the task itself + // Additional, user-defined arguments for the task, to be deserialized by the task itself + bytes args = 2; } // StorageEventType specifies the type of event that triggered the task. @@ -82,21 +118,24 @@ enum StorageEventType { // TriggeredStorageEvent contains the details of the concrete event that triggered a storage event trigger. message TriggeredStorageEvent { - tilebox.v1.ID storage_location_id = 1; // The storage location that triggered the task - StorageEventType type = 2; // The type of the storage event, e.g. created + // The storage location that triggered the task + tilebox.v1.ID storage_location_id = 1; + // The type of the storage event, e.g. created + StorageEventType type = 2; // The object that triggered the task, e.g. a file name in a directory or object name in a bucket string location = 3; } // TriggeredCronEvent contains the details of a concrete event that triggered a cron trigger. message TriggeredCronEvent { - google.protobuf.Timestamp trigger_time = 1; // The time the cron trigger fired + // The time the cron trigger fired + google.protobuf.Timestamp trigger_time = 1; } // DeleteAutomationRequest requests the deletion of an automation. message DeleteAutomationRequest { // The ID of the automation to delete. - tilebox.v1.ID automation_id = 1; + tilebox.v1.ID automation_id = 1 [(buf.validate.field).required = true]; // Whether to cancel all jobs that have been created by this automation. bool cancel_jobs = 2; } diff --git a/apis/workflows/v1/core.proto b/apis/workflows/v1/core.proto index efb8f23..165a54b 100644 --- a/apis/workflows/v1/core.proto +++ b/apis/workflows/v1/core.proto @@ -4,6 +4,7 @@ edition = "2023"; package workflows.v1; +import "buf/validate/validate.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; import "tilebox/v1/id.proto"; @@ -13,9 +14,13 @@ option features.field_presence = IMPLICIT; // A cluster is a grouping of tasks that are related. message Cluster { // 1 is reserved for a potential id field in the future. - string slug = 2; // The unique slug of the cluster within the namespace. - string display_name = 3; // The display name of the cluster. - bool deletable = 4; // Where the cluster is deletable + + // The unique slug of the cluster within the namespace. + string slug = 2; + // The display name of the cluster. + string display_name = 3; + // Where the cluster is deletable + bool deletable = 4; } // A job is a logical grouping of tasks that are related. @@ -24,12 +29,18 @@ message Job { string name = 2; string trace_parent = 3; reserved 4; - bool canceled = 5; // Whether the job has been canceled. - JobState state = 6; // The current state of the job. - google.protobuf.Timestamp submitted_at = 7; // The time the job was submitted. - google.protobuf.Timestamp started_at = 8; // The time the job started running. - repeated TaskSummary task_summaries = 9; // The task' summaries of the job. - tilebox.v1.ID automation_id = 10; // The automation that submitted the job. + // Whether the job has been canceled. + bool canceled = 5; + // The current state of the job. + JobState state = 6; + // The time the job was submitted. + google.protobuf.Timestamp submitted_at = 7; + // The time the job started running. + google.protobuf.Timestamp started_at = 8; + // The task' summaries of the job. + repeated TaskSummary task_summaries = 9; + // The automation that submitted the job. + tilebox.v1.ID automation_id = 10; } // The state of a job. @@ -56,18 +67,26 @@ message TaskSummary { // A task is a single unit of work. message Task { - tilebox.v1.ID id = 1; // The id of the task instance. Contains the submission timestamp as the time part of the ULID. + // The id of the task instance. Contains the submission timestamp as the time part of the ULID. + tilebox.v1.ID id = 1; // Unique identifier for the task. Used by runners to match tasks to specific functions. TaskIdentifier identifier = 2; - TaskState state = 3; // The current state of the task. - bytes input = 4 [features.field_presence = EXPLICIT]; // The serialized input parameters for the task in the format that this task expects. + // The current state of the task. + TaskState state = 3; + // The serialized input parameters for the task in the format that this task expects. + bytes input = 4 [features.field_presence = EXPLICIT]; // Display is a human readable representation of the Task used for printing or visualizations string display = 5 [features.field_presence = EXPLICIT]; - Job job = 6; // The job that this task belongs to. - tilebox.v1.ID parent_id = 7; // The id of the parent task. - repeated tilebox.v1.ID depends_on = 8; // The ids of the tasks that this task depends on. - TaskLease lease = 9; // The lease of the task. - int64 retry_count = 10; // The number of times this task has been retried. + // The job that this task belongs to. + Job job = 6; + // The id of the parent task. + tilebox.v1.ID parent_id = 7; + // The ids of the tasks that this task depends on. + repeated tilebox.v1.ID depends_on = 8; + // The lease of the task. + TaskLease lease = 9; + // The number of times this task has been retried. + int64 retry_count = 10; } // The state of a task. @@ -88,8 +107,16 @@ enum TaskState { // An identifier for a task. message TaskIdentifier { - string name = 1; // A unique name of a task (unique within a namespace). - string version = 2; // Version of the task. + // A unique name of a task (unique within a namespace). + string name = 1 [(buf.validate.field).string = { + min_bytes: 1 + max_bytes: 256 + }]; + // Version of the task. + string version = 2 [(buf.validate.field).string = { + min_bytes: 1 + pattern: "^v(\\d+)\\.(\\d+)$" + }]; } // A list of tasks. @@ -99,13 +126,21 @@ message Tasks { // TaskSubmission is a message of a task that is just about to be submitted, either by submitting a job or as a subtask. message TaskSubmission { - string cluster_slug = 1; // The cluster that this task should be run on - TaskIdentifier identifier = 2; // The task identifier - bytes input = 3; // The serialized task instance - string display = 4; // A human-readable description of the task + // The cluster that this task should be run on + string cluster_slug = 1; + // The task identifier + TaskIdentifier identifier = 2; + // The serialized task instance + bytes input = 3 [(buf.validate.field).bytes.max_len = 1024]; + // A human-readable description of the task + string display = 4 [(buf.validate.field).string.min_bytes = 1]; // A list of indices, corresponding to tasks in the list of sub_tasks that this SubTask is part of. - repeated int64 dependencies = 5; - int64 max_retries = 6; // The maximum number of retries for this task. + repeated int64 dependencies = 5 [(buf.validate.field).repeated.items.int64 = { + gte: 0 + lte: 63 + }]; + // The maximum number of retries for this task. + int64 max_retries = 6 [(buf.validate.field).int64.gte = 0]; } // A lease for a task. diff --git a/apis/workflows/v1/diagram.proto b/apis/workflows/v1/diagram.proto index 2ccbd40..98d61fa 100644 --- a/apis/workflows/v1/diagram.proto +++ b/apis/workflows/v1/diagram.proto @@ -4,19 +4,28 @@ edition = "2023"; package workflows.v1; +import "buf/validate/validate.proto"; + option features.field_presence = IMPLICIT; // Request to render a diagram message RenderDiagramRequest { - string diagram = 1; // The diagram graph in the D2 syntax - RenderOptions render_options = 2; // The options for rendering the diagram + // The diagram graph in the D2 syntax + string diagram = 1; + // The options for rendering the diagram + RenderOptions render_options = 2; } // Options for rendering the diagram message RenderOptions { // The layout to use for rendering the diagram: https://d2lang.com/tour/layouts/. // "dagre" or "elk". Defaults to "dagre" - string layout = 1; + string layout = 1 [(buf.validate.field).string = { + in: [ + "dagre", + "elk" + ] + }]; // The D2 theme to use when rendering the diagram: https://d2lang.com/tour/themes/ int64 theme_id = 2 [features.field_presence = EXPLICIT]; @@ -28,8 +37,14 @@ message RenderOptions { int64 padding = 4; // Set explicitly the direction of the diagram: https://d2lang.com/tour/layouts/#direction. - // "up", "down", "right", "left". - string direction = 5; + string direction = 5 [(buf.validate.field).string = { + in: [ + "up", + "down", + "right", + "left" + ] + }]; } // A rendered diagram diff --git a/apis/workflows/v1/job.proto b/apis/workflows/v1/job.proto index 1c7427c..ee285c2 100644 --- a/apis/workflows/v1/job.proto +++ b/apis/workflows/v1/job.proto @@ -4,6 +4,7 @@ edition = "2023"; package workflows.v1; +import "buf/validate/validate.proto"; import "tilebox/v1/id.proto"; import "tilebox/v1/query.proto"; import "workflows/v1/core.proto"; @@ -14,11 +15,14 @@ option features.field_presence = IMPLICIT; // SubmitJobRequest submits and schedules a job for execution. The job can have multiple root tasks. message SubmitJobRequest { // The root tasks for the job. - repeated TaskSubmission tasks = 1; + repeated TaskSubmission tasks = 1 [(buf.validate.field).repeated = { + min_items: 1 + max_items: 64 + }]; // The name of the job. - string job_name = 2; + string job_name = 2 [(buf.validate.field).string.min_bytes = 1]; // Tracing information for the job. This is used to propagate tracing information to the workers that execute the job. - string trace_parent = 3; + string trace_parent = 3 [(buf.validate.field).string.min_bytes = 1]; // Optional. The ID of the automation that submits this job. tilebox.v1.ID automation_id = 4; } @@ -26,13 +30,13 @@ message SubmitJobRequest { // GetJobRequest requests details for a job. message GetJobRequest { // The ID of the job to get details for. - tilebox.v1.ID job_id = 1; + tilebox.v1.ID job_id = 1 [(buf.validate.field).required = true]; } // RetryJobRequest requests a retry of a job that has failed. message RetryJobRequest { // The job to retry. - tilebox.v1.ID job_id = 1; + tilebox.v1.ID job_id = 1 [(buf.validate.field).required = true]; } // RetryJobResponse is the response to a RetryJobRequest. @@ -44,7 +48,7 @@ message RetryJobResponse { // CancelJobRequest requests a cancel of a job. message CancelJobRequest { // The job to cancel. - tilebox.v1.ID job_id = 1; + tilebox.v1.ID job_id = 1 [(buf.validate.field).required = true]; } // CancelJobResponse is the response to a CancelJobRequest. @@ -64,7 +68,7 @@ enum WorkflowDiagramTheme { // VisualizeJobRequest requests a visualization of a job. message VisualizeJobRequest { // The job to visualize. - tilebox.v1.ID job_id = 1; + tilebox.v1.ID job_id = 1 [(buf.validate.field).required = true]; // The options for rendering the diagram RenderOptions render_options = 2; @@ -80,6 +84,7 @@ message VisualizeJobRequest { message QueryFilters { // Either a time interval or ID interval must be set, but not both. oneof temporal_extent { + option (buf.validate.oneof).required = true; tilebox.v1.TimeInterval time_interval = 1; tilebox.v1.IDInterval id_interval = 2; } @@ -107,7 +112,7 @@ message QueryJobsResponse { // GetJobPrototypeRequest requests a clone prototype of a job. message GetJobPrototypeRequest { // The ID of the job to get a clone prototype for. - tilebox.v1.ID job_id = 1; + tilebox.v1.ID job_id = 1 [(buf.validate.field).required = true]; } // A clone prototype of a job. @@ -121,11 +126,14 @@ message GetJobPrototypeResponse { // CloneJobRequest requests a clone of a job. message CloneJobRequest { // The ID of the job to clone. - tilebox.v1.ID job_id = 1; + tilebox.v1.ID job_id = 1 [(buf.validate.field).required = true]; // The updated root tasks of the job. - repeated TaskSubmission root_tasks_overrides = 2; + repeated TaskSubmission root_tasks_overrides = 2 [(buf.validate.field).repeated = { + min_items: 1 + max_items: 64 + }]; // The name of the job. - string job_name = 3; + string job_name = 3 [(buf.validate.field).string.min_bytes = 1]; } // A service for interacting with jobs. diff --git a/apis/workflows/v1/task.proto b/apis/workflows/v1/task.proto index 31f7fb0..99a0392 100644 --- a/apis/workflows/v1/task.proto +++ b/apis/workflows/v1/task.proto @@ -4,6 +4,7 @@ edition = "2023"; package workflows.v1; +import "buf/validate/validate.proto"; import "google/protobuf/duration.proto"; import "tilebox/v1/id.proto"; import "workflows/v1/core.proto"; @@ -21,17 +22,21 @@ message NextTaskRequest { // NextTaskToRun is a message specifying the capabilities of the task runner, and therefore the potential // tasks that can be run by that task runner. message NextTaskToRun { - string cluster_slug = 1; // The cluster that this task runner is running on. - repeated TaskIdentifier identifiers = 2; // The task identifiers that this task runner can run. + // The cluster that this task runner is running on. + string cluster_slug = 1 [(buf.validate.field).string.min_bytes = 1]; + // The task identifiers that this task runner can run. + repeated TaskIdentifier identifiers = 2; } // ComputedTask is a message specifying a task that has been computed by the task runner. message ComputedTask { - tilebox.v1.ID id = 1; // The id of the task that has been computed. + // The id of the task that has been computed. + tilebox.v1.ID id = 1 [(buf.validate.field).required = true]; // A display name for the task that has been computed for visualization purposes. // If not set, the display message specified upon task submission will be kept. string display = 2; - repeated TaskSubmission sub_tasks = 3; // A list of sub-tasks that the just computed task spawned. + // A list of sub-tasks that the just computed task spawned. + repeated TaskSubmission sub_tasks = 3 [(buf.validate.field).repeated.max_items = 64]; } // NextTaskResponse is the response to the NextTask request. @@ -42,7 +47,7 @@ message NextTaskResponse { // TaskFailedRequest is the request for marking a task as failed. message TaskFailedRequest { - tilebox.v1.ID task_id = 1; + tilebox.v1.ID task_id = 1 [(buf.validate.field).required = true]; string display = 2; bool cancel_job = 3; } @@ -55,7 +60,7 @@ message TaskStateResponse { // TaskLease is a message specifying the new lease expiration time of a task. message TaskLeaseRequest { - tilebox.v1.ID task_id = 1; + tilebox.v1.ID task_id = 1 [(buf.validate.field).required = true]; google.protobuf.Duration requested_lease = 2 [features.field_presence = EXPLICIT]; } diff --git a/apis/workflows/v1/workflows.proto b/apis/workflows/v1/workflows.proto index 0cc328d..3118607 100644 --- a/apis/workflows/v1/workflows.proto +++ b/apis/workflows/v1/workflows.proto @@ -4,6 +4,7 @@ edition = "2023"; package workflows.v1; +import "buf/validate/validate.proto"; import "workflows/v1/core.proto"; option features.field_presence = IMPLICIT; @@ -11,7 +12,7 @@ option features.field_presence = IMPLICIT; // CreateClusterRequest creates a new cluster. message CreateClusterRequest { // The name of the cluster. - string name = 1; + string name = 1 [(buf.validate.field).string.min_bytes = 1]; } // GetClusterRequest requests details for a cluster. @@ -23,7 +24,7 @@ message GetClusterRequest { // DeleteClusterRequest deletes an existing cluster. message DeleteClusterRequest { // The slug of the cluster to delete. - string cluster_slug = 1; + string cluster_slug = 1 [(buf.validate.field).string.min_bytes = 1]; } // DeleteClusterResponse is the response to DeleteClusterRequest. diff --git a/buf.lock b/buf.lock index 9a4fa6c..0622b83 100644 --- a/buf.lock +++ b/buf.lock @@ -1,6 +1,9 @@ # Generated by buf. DO NOT EDIT. version: v2 deps: + - name: buf.build/bufbuild/protovalidate + commit: c923a0c2a1324d8b9db81effea973b9c + digest: b5:ba54835683c74e87e751bdc482b7d1c157926024587f453ca3640d3348b846aba3244da145042226842e10876a856e19b6dffc96609167199a450455aef9acf3 - name: buf.build/googleapis/googleapis commit: 61b203b9a9164be9a834f58c37be6f62 digest: b5:7811a98b35bd2e4ae5c3ac73c8b3d9ae429f3a790da15de188dc98fc2b77d6bb10e45711f14903af9553fa9821dff256054f2e4b7795789265bc476bec2f088c diff --git a/buf.yaml b/buf.yaml index aa5a0a3..1867327 100644 --- a/buf.yaml +++ b/buf.yaml @@ -4,6 +4,7 @@ modules: name: buf.build/tilebox/api deps: - buf.build/googleapis/googleapis + - buf.build/bufbuild/protovalidate lint: # https://buf.build/docs/lint/rules/ use: # enable all linters - STANDARD