Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

508 lines
18 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
syntax = "proto3";
// For more information, see: https://github.com/hashicorp/consul/tree/main/docs/resources
package hashicorp.consul.resource;
import "annotations/ratelimit/ratelimit.proto";
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
// Type describes a resource's type. It follows the GVK (Group Version Kind)
// [pattern](https://book.kubebuilder.io/cronjob-tutorial/gvks.html) established
// by Kubernetes.
message Type {
// Group describes the area of functionality to which this resource type
// relates (e.g. "catalog", "authorization").
string group = 1;
// GroupVersion is incremented when sweeping or backward-incompatible changes
// are made to the group's resource types.
string group_version = 2;
// Kind identifies the specific resource type within the group.
string kind = 3;
}
// Tenancy describes the tenancy units in which the resource resides.
message Tenancy {
// Partition is the topmost administrative boundary within a cluster.
// https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions
//
// When using the List and WatchList endpoints, provide the wildcard value "*"
// to list resources across all partitions.
string partition = 1;
// Namespace further isolates resources within a partition.
// https://developer.hashicorp.com/consul/docs/enterprise/namespaces
//
// When using the List and WatchList endpoints, provide the wildcard value "*"
// to list resources across all namespaces.
string namespace = 2;
}
// ID uniquely identifies a resource.
message ID {
// Uid is the unique internal identifier we gave to the resource.
//
// It is primarily used to tell the difference between the current resource
// and previous deleted resources with the same user-given name.
//
// Concretely, Uid is a [ULID](https://github.com/ulid/spec) and you can treat
// its timestamp component as the resource's creation time.
string uid = 1;
// Name is the user-given name of the resource (e.g. the "billing" service).
string name = 2;
// Type identifies the resource's type.
Type type = 3;
// Tenancy identifies the tenancy units (i.e. partition, namespace) in which
// the resource resides.
Tenancy tenancy = 4;
}
// Resource describes a resource of a known type managed by Consul.
message Resource {
// ID uniquely identifies the resource.
ID id = 1;
// Owner (optionally) describes which resource "owns" this resource, it is
// immutable and can only be set on resource creation. Owned resources will
// be automatically deleted when their owner is deleted.
ID owner = 2;
// Version is the low-level version identifier used by the storage backend
// in CAS (Compare-And-Swap) operations. It will change when the resource is
// modified in any way, including status updates.
//
// When calling the Write endpoint, providing a non-blank version will perform
// a CAS (Compare-And-Swap) write, which will result in an Aborted error code
// if the given version doesn't match what is stored.
string version = 3;
// Generation is incremented whenever the resource's content (i.e. not its
// status) is modified. You can think of it as being the "user version".
//
// Concretely, Generation is a [ULID](https://github.com/ulid/spec) and you
// can treat its timestamp component as the resource's modification time.
string generation = 4;
// Metadata contains key/value pairs of arbitrary metadata about the resource.
// "deletionTimestamp" and "finalizers" keys are reserved for internal use.
map<string, string> metadata = 5;
// Status is used by controllers to communicate the result of attempting to
// reconcile and apply the resource (e.g. surface semantic validation errors)
// with users and other controllers. Each status is identified by a unique key
// and should only ever be updated by one controller.
//
// Status can only be updated via the WriteStatus endpoint. Attempting to do
// so via the Write endpoint will result in an InvalidArgument error code.
map<string, Status> status = 6;
// Data contains the resource's type-specific content.
google.protobuf.Any data = 7;
}
// Status is used by controllers to communicate the result of attempting to
// reconcile and apply a resource (e.g. surface semantic validation errors)
// with users and other controllers.
message Status {
// ObservedGeneration identifies which generation of a resource this status
// related to. It can be used to determine whether the current generation of
// a resource has been reconciled.
string observed_generation = 1;
// Conditions contains a set of discreet observations about the resource in
// relation to the current state of the system (e.g. it is semantically valid).
repeated Condition conditions = 2;
// UpdatedAt is the time at which the status was last written.
google.protobuf.Timestamp updated_at = 3;
}
// Condition represents a discreet observation about a resource in relation to
// the current state of the system.
//
// It is heavily inspired by Kubernetes' [conditions](https://bit.ly/3H9Y6IK)
// and the Gateway API [types and reasons](https://bit.ly/3n2PPiP).
message Condition {
// State represents the state of the condition (i.e. true/false/unknown).
enum State {
// STATE_UNKNOWN means that the state of the condition is unknown.
//
// buf:lint:ignore ENUM_ZERO_VALUE_SUFFIX
STATE_UNKNOWN = 0;
// STATE_TRUE means that the state of the condition is true.
STATE_TRUE = 1;
// STATE_FALSE means that the state of the condition is false.
STATE_FALSE = 2;
}
// Type identifies the type of condition (e.g. "Invalid", "ResolvedRefs").
string type = 1;
// State represents the state of the condition (i.e. true/false/unknown).
State state = 2;
// Reason provides more machine-readable details about the condition (e.g.
// "InvalidProtocol").
string reason = 3;
// Message contains a human-friendly description of the status.
string message = 4;
// Resource identifies which resource this condition relates to, when it is
// not the core resource itself.
Reference resource = 5;
}
// Reference identifies which resource a condition relates to, when it is not
// the core resource itself.
message Reference {
// Type identifies the resource's type.
Type type = 1;
// Tenancy identifies the tenancy units (i.e. partition, namespace) in which
// the resource resides.
Tenancy tenancy = 2;
// Name is the user-given name of the resource (e.g. the "billing" service).
string name = 3;
// Section identifies which part of the resource the condition relates to.
string section = 4;
}
// Tombstone represents a promise to delete all of a resource's immediately
// owned (child) resources, if any.
message Tombstone {
// Owner resource identifier.
ID owner = 1;
}
// ResourceService provides the shared primitives for storing, querying, and
// watching resources of different types.
//
// It is exposed on our external gRPC port and used internally by controllers.
//
// # Consistency Guarentees
//
// All reads are eventually consistent by default. Concretely, we guarantee
// [monotonic reads](https://jepsen.io/consistency/models/monotonic-reads).
//
// That is, a read will always return results that are as up-to-date as an
// earlier read, provided both happen on the same Consul server. But we do
// not make any such guarantee about writes. In other words, reads won't
// necessarily reflect earlier writes, even when made against the same server.
//
// This guarantee also holds between the Read and WatchList endpoints such that
// you'll never receive an event about a resource that you cannot immediately
// read, provided both the Read and WatchList happen on the same server.
//
// The Read endpoint also supports a strong consistency mode that guarantees
// [linearizability](https://jepsen.io/consistency/models/linearizable), such
// that a read will always return the most up-to-date version of a resource,
// without caveat.
//
// This is much more expensive than eventual consistency and when using the Raft
// storage backend, will increase load on the cluster leader, so should be used
// sparingly.
//
// To opt-in to strongly consistent reads set the `x-consul-consistency-mode`
// gRPC metadata field to "consistent".
service ResourceService {
// Read a resource by ID.
//
// By default, reads are eventually consistent, but you can opt-in to strong
// consistency via the x-consul-consistency-mode metadata (see ResourceService
// docs for more info).
//
// Errors with NotFound if the resource is not found.
//
// Errors with InvalidArgument if the request fails validation or the resource
// is stored as a type with a different GroupVersion than was requested.
//
// Errors with PermissionDenied if the caller is not authorized to read
// the resource.
rpc Read(ReadRequest) returns (ReadResponse) {
option (hashicorp.consul.internal.ratelimit.spec) = {
operation_type: OPERATION_TYPE_READ,
operation_category: OPERATION_CATEGORY_RESOURCE
};
}
// Write a resource.
//
// To perform a CAS (Compare-And-Swap) write, provide the current resource
// version in the Resource.Version field. If the given version doesn't match
// what is currently stored, an Aborted error code will be returned.
//
// To perform a blanket write (update regardless of the stored version),
// provide an empty Version in the Resource.Version field. Note that the
// write may still fail due to not being able to internally do a CAS write
// and return an Aborted error code.
//
// Resource.Id.Uid can (and by controllers, should) be provided to avoid
// accidentally modifying a resource if it has been deleted and recreated.
// If the given Uid doesn't match what is stored, a FailedPrecondition error
// code will be returned.
//
// It is not possible to modify the resource's status using Write. You must
// use WriteStatus instead.
rpc Write(WriteRequest) returns (WriteResponse) {
option (hashicorp.consul.internal.ratelimit.spec) = {
operation_type: OPERATION_TYPE_WRITE,
operation_category: OPERATION_CATEGORY_RESOURCE
};
}
// WriteStatus updates one of the resource's statuses. It should only be used
// by controllers.
//
// To perform a CAS (Compare-And-Swap) write, provide the current resource
// version in the Version field. If the given version doesn't match what is
// currently stored, an Aborted error code will be returned.
//
// Note: in most cases, CAS status updates are not necessary because updates
// are scoped to a specific status key and controllers are leader-elected so
// there is no chance of a conflict.
//
// Id.Uid must be provided to avoid accidentally modifying a resource if it has
// been deleted and recreated. If the given Uid doesn't match what is stored,
// a FailedPrecondition error code will be returned.
rpc WriteStatus(WriteStatusRequest) returns (WriteStatusResponse) {
option (hashicorp.consul.internal.ratelimit.spec) = {
operation_type: OPERATION_TYPE_WRITE,
operation_category: OPERATION_CATEGORY_RESOURCE
};
}
// List resources of a given type, tenancy, and optionally name prefix.
//
// To list resources across all tenancy units, provide the wildcard "*" value.
//
// Results are eventually consistent (see ResourceService docs for more info).
rpc List(ListRequest) returns (ListResponse) {
option (hashicorp.consul.internal.ratelimit.spec) = {
operation_type: OPERATION_TYPE_READ,
operation_category: OPERATION_CATEGORY_RESOURCE
};
}
// List resources of a given owner.
//
// Results are eventually consistent (see ResourceService docs for more info).
rpc ListByOwner(ListByOwnerRequest) returns (ListByOwnerResponse) {
option (hashicorp.consul.internal.ratelimit.spec) = {
operation_type: OPERATION_TYPE_READ,
operation_category: OPERATION_CATEGORY_RESOURCE
};
}
// Delete a resource by ID.
//
// Deleting a non-existent resource will return a successful response for
// idempotency.
//
// To perform a CAS (Compare-And-Swap) deletion, provide the current resource
// version in the Version field. If the given version doesn't match what is
// currently stored, an Aborted error code will be returned.
//
// Resource.Id.Uid can (and by controllers, should) be provided to avoid
// accidentally modifying a resource if it has been deleted and recreated.
// If the given Uid doesn't match what is stored, a FailedPrecondition error
// code will be returned.
rpc Delete(DeleteRequest) returns (DeleteResponse) {
option (hashicorp.consul.internal.ratelimit.spec) = {
operation_type: OPERATION_TYPE_WRITE,
operation_category: OPERATION_CATEGORY_RESOURCE
};
}
// WatchList watches resources of the given type, tenancy, and optionally name
// prefix. It returns results for the current state-of-the-world at the start
// of the stream, and delta events whenever resources are written or deleted.
//
// To watch resources across all tenancy units, provide the wildcard "*" value.
//
// WatchList makes no guarantees about event timeliness (e.g. an event for a
// write may not be received immediately), but it does guarantee that events
// will be emitted in the correct order. See ResourceService docs for more
// info about consistency guarentees.
//
// buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
rpc WatchList(WatchListRequest) returns (stream WatchEvent) {
option (hashicorp.consul.internal.ratelimit.spec) = {
operation_type: OPERATION_TYPE_READ,
operation_category: OPERATION_CATEGORY_RESOURCE
};
}
// MutateAndValidate a resource.
//
// Applies a resource type's registered mutation and validation hooks to
// a resource. This is useful for filling in defaults and validating inputs before
// doing a Write. It's not a pre-requisite since the Write endpoint will also apply
// the hooks.
rpc MutateAndValidate(MutateAndValidateRequest) returns (MutateAndValidateResponse) {
option (hashicorp.consul.internal.ratelimit.spec) = {
operation_type: OPERATION_TYPE_READ,
operation_category: OPERATION_CATEGORY_RESOURCE
};
}
}
// ReadRequest contains the parameters to the Read endpoint.
message ReadRequest {
// ID of the resource.
ID id = 1;
}
// ReadResponse contains the results of calling the Read endpoint.
message ReadResponse {
// Resource that was read.
Resource resource = 1;
}
// ListRequest contains the parameters to the List endpoint.
message ListRequest {
// Type of resource to list.
Type type = 1;
// Tenancy units in which to list resources. To list resources in all units,
// provide the wildcard "*" value.
Tenancy tenancy = 2;
// NamePrefix filters the results to those with a name beginning with the
// given prefix.
string name_prefix = 3;
}
// ListResponse contains the results of calling the List endpoint.
message ListResponse {
// Resources that were listed.
repeated Resource resources = 1;
}
// ListByOwnerRequest contains the parameters to the ListByOwner endpoint.
message ListByOwnerRequest {
ID owner = 1;
}
// ListByOwnerResponse contains the results of calling the ListByOwner endpoint.
message ListByOwnerResponse {
// Resources that were listed.
repeated Resource resources = 1;
}
// WriteRequest contains the parameters to the Write endpoint.
message WriteRequest {
// Resource to write.
Resource resource = 1;
}
// WriteResponse contains the results of calling the Write endpoint.
message WriteResponse {
// Resource that was written.
Resource resource = 1;
}
// WriteStatusRequest contains the parameters to the WriteStatus endpoint.
message WriteStatusRequest {
// ID of the resource to which the status will be written. Must contain a Uid.
ID id = 1;
// Version may be provided to perform a CAS (Compare-And-Swap) update of the
// status. If the given version doesn't match what is currently stored, an
// Aborted error code will be returned.
//
// Note: in most cases, CAS status updates are not necessary because updates
// are scoped to a specific status key and controllers are leader-elected so
// there is no chance of a conflict.
string version = 2;
// Key identifies which status will be written. Generally, each controller
// should write 1 status which it owns exclusively (i.e. no other controller
// updates it).
string key = 3;
// Status that will be written to the resource.
Status status = 4;
}
// WriteStatusResponse contains the results of calling the WriteStatus endpoint.
message WriteStatusResponse {
// Resource to which the status was written.
Resource resource = 1;
}
// DeleteRequest contains the parameters to the Delete endpoint.
message DeleteRequest {
// ID of the resource that will be deleted.
ID id = 1;
// Version may be provided to perform a CAS (Compare-And-Swap) deletion of the
// resource. If the given version doesn't match what is currently stored, an
// Aborted error code will be returned.
string version = 2;
}
// DeleteResponse contains the results of calling the Delete endpoint.
message DeleteResponse {}
// WatchListRequest contains the parameters to the WatchList endpoint.
message WatchListRequest {
// Type of resource to watch.
Type type = 1;
// Tenancy units in which to watch resources. To list resources in all units,
// provide the wildcard "*" value.
Tenancy tenancy = 2;
// NamePrefix filters the results to those with a name beginning with the
// given prefix.
string name_prefix = 3;
}
// WatchEvent is emitted on the WatchList stream when a resource changes.
message WatchEvent {
v2: ensure the controller caches are fully populated before first use (#20421) The new controller caches are initialized before the DependencyMappers or the Reconciler run, but importantly they are not populated. The expectation is that when the WatchList call is made to the resource service it will send an initial snapshot of all resources matching a single type, and then perpetually send UPSERT/DELETE events afterward. This initial snapshot will cycle through the caching layer and will catch it up to reflect the stored data. Critically the dependency mappers and reconcilers will race against the restoration of the caches on server startup or leader election. During this time it is possible a mapper or reconciler will use the cache to lookup a specific relationship and not find it. That very same reconciler may choose to then recompute some persisted resource and in effect rewind it to a prior computed state. Change - Since we are updating the behavior of the WatchList RPC, it was aligned to match that of pbsubscribe and pbpeerstream using a protobuf oneof instead of the enum+fields option. - The WatchList rpc now has 3 alternating response events: Upsert, Delete, EndOfSnapshot. When set the initial batch of "snapshot" Upserts sent on a new watch, those operations will be followed by an EndOfSnapshot event before beginning the never-ending sequence of Upsert/Delete events. - Within the Controller startup code we will launch N+1 goroutines to execute WatchList queries for the watched types. The UPSERTs will be applied to the nascent cache only (no mappers will execute). - Upon witnessing the END operation, those goroutines will terminate. - When all cache priming routines complete, then the normal set of N+1 long lived watch routines will launch to officially witness all events in the system using the primed cached.
10 months ago
// Upsert indicates that the resource was written (i.e. created or
// updated). All events from the initial state-of-the-world will be upsert
// events.
message Upsert {
Resource resource = 1;
}
v2: ensure the controller caches are fully populated before first use (#20421) The new controller caches are initialized before the DependencyMappers or the Reconciler run, but importantly they are not populated. The expectation is that when the WatchList call is made to the resource service it will send an initial snapshot of all resources matching a single type, and then perpetually send UPSERT/DELETE events afterward. This initial snapshot will cycle through the caching layer and will catch it up to reflect the stored data. Critically the dependency mappers and reconcilers will race against the restoration of the caches on server startup or leader election. During this time it is possible a mapper or reconciler will use the cache to lookup a specific relationship and not find it. That very same reconciler may choose to then recompute some persisted resource and in effect rewind it to a prior computed state. Change - Since we are updating the behavior of the WatchList RPC, it was aligned to match that of pbsubscribe and pbpeerstream using a protobuf oneof instead of the enum+fields option. - The WatchList rpc now has 3 alternating response events: Upsert, Delete, EndOfSnapshot. When set the initial batch of "snapshot" Upserts sent on a new watch, those operations will be followed by an EndOfSnapshot event before beginning the never-ending sequence of Upsert/Delete events. - Within the Controller startup code we will launch N+1 goroutines to execute WatchList queries for the watched types. The UPSERTs will be applied to the nascent cache only (no mappers will execute). - Upon witnessing the END operation, those goroutines will terminate. - When all cache priming routines complete, then the normal set of N+1 long lived watch routines will launch to officially witness all events in the system using the primed cached.
10 months ago
// Delete indicates that the resource was deleted.
message Delete {
Resource resource = 1;
}
// EndOfSnapshot is sent to indicate that the initial snapshot events have
// been sent and future events will modify that set.
message EndOfSnapshot {}
v2: ensure the controller caches are fully populated before first use (#20421) The new controller caches are initialized before the DependencyMappers or the Reconciler run, but importantly they are not populated. The expectation is that when the WatchList call is made to the resource service it will send an initial snapshot of all resources matching a single type, and then perpetually send UPSERT/DELETE events afterward. This initial snapshot will cycle through the caching layer and will catch it up to reflect the stored data. Critically the dependency mappers and reconcilers will race against the restoration of the caches on server startup or leader election. During this time it is possible a mapper or reconciler will use the cache to lookup a specific relationship and not find it. That very same reconciler may choose to then recompute some persisted resource and in effect rewind it to a prior computed state. Change - Since we are updating the behavior of the WatchList RPC, it was aligned to match that of pbsubscribe and pbpeerstream using a protobuf oneof instead of the enum+fields option. - The WatchList rpc now has 3 alternating response events: Upsert, Delete, EndOfSnapshot. When set the initial batch of "snapshot" Upserts sent on a new watch, those operations will be followed by an EndOfSnapshot event before beginning the never-ending sequence of Upsert/Delete events. - Within the Controller startup code we will launch N+1 goroutines to execute WatchList queries for the watched types. The UPSERTs will be applied to the nascent cache only (no mappers will execute). - Upon witnessing the END operation, those goroutines will terminate. - When all cache priming routines complete, then the normal set of N+1 long lived watch routines will launch to officially witness all events in the system using the primed cached.
10 months ago
oneof event {
Upsert upsert = 1;
Delete delete = 2;
EndOfSnapshot end_of_snapshot = 3;
}
}
// MutateAndValidateRequest contains the parameters to the MutateAndValidate endpoint.
message MutateAndValidateRequest {
Resource resource = 1;
}
// MutateAndValidateResponse contains the results of calling the MutateAndValidate endpoint.
message MutateAndValidateResponse {
Resource resource = 1;
}