--- title: Tech Forum ytt schema Annotations Scratchpad tags: ytt --- Existing annotations -- `@overlay` - `@overlay/match-child-defaults` - `@overlay/merge` - `@overlay/remove` - `@overlay/replace` - `@overlay/insert` - `@overlay/append` - `@overlay/assert` - `@overlay/match by="x"` - `@overlay/match missing_ok=True` - `@overlay/match expects=2` - `@overlay/match when=2` - `@schema` - `@schema/type any=True` - `@schema/nullable` - (future) - (future) `@schema/default value` - (future) `@schema/key [allowed=Regexp] [(expects=Int|String|List|Function | missing_ok=Bool)]` - (future) `@schema/validate func()` - (future) `@schema/title "string"` - (future) `@schema/doc "string"` - (future) `@schema/example example` - (future) `@schema/examples [examples]` - (future) `@schema/deprecated "string"` - (future) `@schema/removed` `@data` - `@data/values` `@library` - `@library/ref "@app"` - `#@library/ref "@lib1@~foo"` `@ load("x.star", "x")` Considerations --- * We want to avoid breaking changes * We want a simple UX * Can we utilize patterns in our current annotations? * Does it make sense to have one schema definition for inputs _and_ outputs? Current schema example --- ```yaml #@schema/match data_values=True --- #@schema/nullable nothing: string: "" #@schema/type any=True any: ``` Alternatives --- ```yaml #@schema/match "data_values" --- #@schema/nullable nothing: string: "" #@schema/type any=True any: ``` ```yaml #@schema/data_values --- #@schema/nullable nothing: string: "" #@schema/type any=True any: ``` ```yaml #@data/values #@schema/values #@schema/k8s #@schema/concourse #@schema/definition name="data/values" #@schema/def name="data/values" #@schema/def data_values=True #@schema/ #@data/schema #@template/schema #@output/schema ``` ```yaml= #@data/schema #@library/ref "@libby" --- #@schema/validate min=1 bananas: 1 #@data/schema name="schema2" --- #@schema/validate min=1 bananas: 1 ``` ```yaml= #@template/schema name="" --- ``` ```yaml #@schema/use name="schema1" --- bananas: "wrong" ``` 1. in input bananas is an int and >0 2. in output ```yaml #@schema/use name="" --- a: somevalue: #@ data.values.bananas ``` :banana: :banana: :banana: :monkey: ## Schema functions? ``` # current data module function: vals = data.list() # future schema functions template.validate() data.validate() #@ s = schema.get("schema.yml") #@ violations = s.check(yaml_docset) #@ violations = s.validate(yaml_docset) ``` Schema and template in the same file ``` --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 1 #@schema/def --- apiVersion: apps/v1 kind: Deployment metadata: name: default-deployment labels: app: default spec: replicas: 3 selector: matchLabels: app: default template: metadata: labels: app: default spec: containers: - name: default image: default:1.14.2 ports: - containerPort: 80 ``` ``` #@schema/values #@schema/def name="k8s" #@schema/def name="concourse" #@schema/def name="deployment" group="apps" version="v1" #@schema/def name="" match_by=matcher_func() ``` Future state? --- `schema.yml` ```yaml= #@schema/def name="k8/deployment" --- apiVersion: apps/v1 kind: Deployment metadata: name: default-deployment labels: app: default spec: replicas: 3 selector: matchLabels: app: default template: metadata: labels: app: default spec: containers: - name: default image: default:1.14.2 ports: - containerPort: 80 ``` `config.yml` ```yaml= #@schema/use name="k8/deployment" version="v1" --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 1 ``` ## Additional Context - the "Data module" while serving lots of value has room to grow: - today there are two channels of inputs: Data Values and Data files. - e.g. perhaps having other named sources of data (like some subset of Kubernetes Cluster state) In terms of considering different syntax - practical test: how likely will the user remember one syntax over another? - what are the range of mental models (low fidelity ~ copy-and-paste <=== ===> high fidelity ~ deep conceptual understanding of individual features) We'd maximize value/flexibility if we solve for as much of that range as we can. ### Additional Option: `@data/values-schema` ```yaml= #@data/values-schema --- #@schema/nullable optional_bits: ``` - if we look deeper at it, `@data/values` is actually an overlay... one could say the full name of that annotation is `@data/values-overlay` - today, there's only one schema, but given notes above about potential future growth, it's not a limitation we'd be wise to commit to (which makes `@data/schema` a bit "too" terse) - we've got what seems to be a conceptually balanced thing going with `@(module-name)/(annotation-name)`, so adding another `/` into the mix seems like its going to make that whole thing more complicated. - the schema we're declaring is specifically for Data's Values. So, `values-schema` seems like a fitting noun. ### Exploring a Programmatic API for Schema - draws in bottom-up design forces: what _ought_ to be fundamentally true about a given concept (like schemas)? - can help maintain orthogonality between features (the stronger the identity/definition/set of responsibilities, the less likely we'll accidentally misplace one) sketching... ``` #@ blah = library.get("blah") #@ child_schema = blah.data_values_schema() #@ blah = blah.with_data_values_schema(my_add_ons()) #@ s = schema.get("data_values") #@ schema.get_data_values() #@ s.check(doc|docset) #@ s.validate(config_map) ``` ... also, if we build up programmatic primitives, we might be able to realize higher-level features through composition. For example, what if schema could be `load()`'ed and then applied via an overlay? ``` #@overlay/match by=overlay.all #@overlay/assert via=lambda left, right : k8s_schema.find(left).validate(left) --- ``` or used directly in a template? ``` #@ load("k8s_schemas", "k8s") #@schema/type via=k8s.apps.v1.Deployment #@schema/type k8s.apps.v1.Deployment --- foo : #@ ... spec: #@schema/type k8s.core.v1.PodSpec template: containers: #@schema/type k8s.core.v1.PodSpec.Container, default_omitted=True - name: image: ```