Depends on: -
RFCs: RFC-018
Abstract
This spec defines the declarative Infrastructure-as-Code framework used to configure ODF nodes. It defines the common manifest format of , how resource types are identified and versioned, how resources are identified, owned, labeled, and reference one another, and how apply manifests and reconcile the actual state of the system with the desired state they describe. Individual resource types (datasets, sources, flows, accounts, etc.) are defined by the specs of their respective domains.Terminology
This spec builds on the , , and concepts defined in the glossary, and introduces the following terms:- Controller - a process that tracks resources of certain types and reconciles them with the state of entities under its control, continuously moving the actual state towards the desired state described by manifests
- Applying a
Datasetmanifest creates aDatasetresource, whose controller creates and maintains an ODF - Applying a high-level resource manifest may result in its controller generating and applying lower-level manifests, which in turn result in provisioning of corresponding entities
Resource Manifests
All resources share a common manifest structure:$schema- identifies the type of the resourceheaders- contains identity, ownership, labels, annotations, and lifecycle informationspec- defines the desired state of the resourcestatus- contains information about the current state and the reconciliation process (see Status)
status section never appears in user-authored manifests - it is maintained by the node and is writable only by controllers.
Input and Canonical Representations
Every resource has two representations:- Input () - the user-authored manifest. It contains only the fields users can modify, and allows convenient alternatives, e.g. referencing other resources by name or using short forms.
- Canonical () - the representation stored and returned by the node. It includes system-populated fields (e.g.
id,generation,status), and all references are fully resolved.
canonicalType schema property. Canonical types must be trivially convertible into input types, while converting input types into canonical requires identity and reference resolution.
Type Identification
The$schema field identifies the resource type using a resolvable URL that points to the JSON Schema of the resource:
{base-url}/{context}/{version}/{Name} and carries:
- Controlling organization domain (e.g.
opendatafabric.org) - Bounded context (e.g.
config) - Version (e.g.
v1alpha1) - Resource type name (e.g.
SecretSet)
SecretSet) that can be used instead of the full URL, e.g. in references.
Resource schemas are validated against the meta/Resource and meta/ResourceInput metaschemas.
Versioning
The version is part of the$schema URL in the form of e.g. v1 or v1alpha1:
- A version captures both how a resource is defined and the semantics of how it behaves, i.e. the version may be incremented if the behavior of a resource changes significantly even when its schema stays the same.
- Versions apply to an entire bounded context, not an individual resource type - a version bump applies to all resource types within the context.
Identity
A manifest usually only defines the resourcename:
(account, type, name) uniquely identifies a resource within a node. Resources of different types under one account may have the same name.
Upon creation the node assigns the resource a unique id (UUID v4), which is a stable identifier of the resource within one node:
id and name, the resource is matched by id and its name is updated to the one in the manifest, allowing resources to be renamed. Including id ensures that the manifest applies to the exact intended resource, but sacrifices portability of the manifest across nodes.
Resources and Entities
Resources describe the desired state of the system and are separate from the created to fulfill that state, e.g. aDataset resource is not the same thing as an ODF dataset stored in some S3 bucket.
This is why resources like Dataset or User may have both:
id- identifies the resource within a nodedid- a W3C DID that identifies the corresponding entity (e.g. a dataset or a person) on a global network
did:odf:xxyy and another node replicates it, the two nodes have two Dataset resources with the same did but different ids.
Ownership
Every resource belongs to an specified in theaccount header:
account is omitted in a manifest, the resource is created under the account of the current .
ODF uses an account-centric model of multi-tenancy, where ownership and access control are defined by the Authorization spec.
Labels and Annotations
Resources can specify labels and annotations. Both are maps of keys to arbitrary JSON values, but only labels are indexed and can be used for querying and in selectors:meta/ResourceLabel and meta/ResourceAnnotation metaschemas, so their values can be type-checked. Schemas may restrict which resource types they are valid for via resourceTypes, and labels can be marked as required for these types via isRequired.
Controllers may contribute their own labels to simplify common filtering scenarios, e.g. a Dataset resource may automatically get the DatasetKind label.
Labels can also carry information used in access control - see Auth Attributes.
References
Resources can link to other resources using references (). A reference can identify the target by:id- unique within a nodedid- unique globallytype,name, and optionalaccount- whenaccountis not specified, the name is resolved within the account of the current principal
Typed References
Domains may define extended versions of resource references that:- Restrict target resources to a specific type (e.g.
DatasetRef) - Provide additional features (e.g. referencing a sub-path of a secret via
Secret:postgres#password)
meta/ResourceRef metaschema.
Reference Resolution
Referencing resources by name is convenient when setting up resource graphs, especially when target resources are not created yet. Over the long term, however, as resources get renamed and deleted, name-based references become unreliable and can pose security risks when old names get reused. Nodes therefore resolve all references to stable IDs upon first apply. Resolved references are represented as handles () that always include theid, while type, name, and account remain for human readability:
- It must be possible to create a resource that references a non-existing resource and have the target created later
- It must be possible to delete a resource referenced by others
Selectors
Multiple resources can be referenced at once using selectors () that match resources by:- Owning account
- Resource type
- Name pattern in SQL
LIKEformat - Label filters ()
id, did, or type and name without wildcards.
Unlike references, selectors are not resolved during apply. Whether a resource matches a selector is determined repeatedly during controller operations, so resources can start and stop matching a selector after the resource containing it was applied.
Owner References
When a resource is created by a controller of another higher-level resource, the association is recorded in theownerReferences header. This creation provenance can be used for lineage tracking, cascading deletion, and garbage collection.
Generations
Resource reconciliation is an eventually-consistent process - while a controller is reconciling one version of a resource, its desired state may be changed by the user. To reflect this, the node maintains the following headers:generation- incremented every time the resource headers or spec are updated, signifying changes to the desired state. Does not increment on status changes.statusGeneration- incremented every time the resource status is updated.createdAt,updatedAt- time when the resource was first applied and last updated (including status updates)
1. Together they tell whether anything changed in a resource since it was last read, without comparing its contents.
Status may be updated by several controllers concurrently. A controller can pass the statusGeneration it read as a precondition of a status update, which is rejected as a conflict if the status was modified in the meantime. This allows compare-and-swap updates without the check and the update racing with other controllers.
Status
Thestatus section provides information about the reconciliation of the resource:
observedGeneration,observedAt- the generation last seen by the main controller of the resource, and whenreconciledGeneration,reconciledAt- the generation last successfully reconciled by the main controller, and whenphase- see Phasesconditions- see Conditions
generation: 2 failed because it links to a non-existing secret, leaving the previous generation running:
Phases
Thephase field () is populated as follows:
Deletion takes precedence over the other phases. Deleting a resource is a two-step process, so that controllers can clean up the entities they created or finish work in progress before the resource disappears.
Conditions
Theconditions field provides extended information about the state of a specific resource type. Conditions can be contributed by multiple controllers and are keyed by schema URIs to avoid name collisions and allow schema checking. Condition schemas are defined using the meta/ResourceCondition metaschema.
When reconciliation fails, the controller reports the cause using the condition, which carries a machine-readable code and a human-readable message.
Conditions contributed by controllers other than the main controller of the resource should carry their own observedGeneration and reconciledGeneration fields, as they may lag behind or be ahead of the main controller.
Resource Application
When a batch of manifests is applied, the node performs the following steps:- Validate each manifest against its schema
- Look up whether target resources already exist (by
idorname) - Assign
ids to resources that don’t yet exist - Resolve all references to
ids of existing resources or of those about to be created - Order resources in depth-first dependency order
- Pass every resource to the pre-apply step of its main controller, which may:
- Perform additional validation
- Contribute additional labels
- Rewrite parts of the spec (e.g. to encrypt raw secrets before the spec is stored)
- Increment the resource
generation - Store the resource
- Initiate reconciliation asynchronously
Schemas
Example manifests of various resource types are available in the
examples directory, and the relationships between core resource types are shown in the entity-relationship diagram.