690 | #@ '''
691 | #@schema/doc documentation
692 | system_registry:
693 | ```
694 |
695 | __
696 |
697 | _Example 3: HTML-formatted description_
698 |
699 | When the primary consumption of documentation will be in a formatted form, the Configuration Author
700 | can use that format.
701 |
702 | ```yaml
703 | #@ documentation = \
704 | #@ '''Optional configuration if using a private registry that has all cf-for-k8s images relocated to
705 | #@ see docs/platform_operators/system-registry.md
706 | #@ '''
707 | #@schema/doc documentation
708 | system_registry:
709 | ```
710 |
711 |
712 |
713 | ### @schema/example
714 |
715 | Captures an exemplary value for the annotated node.
716 |
717 | ```yaml
718 | @schema/example sample_value
719 | ```
720 |
721 | - `sample_value` (_type matching the node_) — a sample value that illustrates a common use. The value must be of the
722 | effective type of the node; to specify a value of another type is an error.
723 | - due to current limitations within `ytt`'s annotation system, only one example can be given: this annotation cannot
724 | be repeated. To supply multiple examples, use the [`@schema/examples`](#schemaexamples) annotation.
725 |
726 | _Example:_
727 |
728 | ```yaml
729 | #@schema/example "my_domain.example.com"
730 | system_domain: ""
731 | ```
732 |
733 | __
734 |
735 |
736 | ### @schema/examples
737 |
738 | Captures multiple exemplary values for the annotated node.
739 |
740 | ```yaml
741 | @schema/examples (exampleA_description, exampleA_value)[, (exampleB_description, exampleB_value)]
742 | ```
743 |
744 | - `(exampleX_description, exampleX_value)` ([`tuple`](https://github.com/google/starlark-go/blob/master/doc/spec.md#tuples)) —
745 | the example pair:
746 | - `exampleX_description` (`string`) — naming and describing the purpose or impact of the example value.
747 | - `exampleX_value` (_type matching the node_) — a sample value that illustrates a common use. The value must be of the
748 | effective type of the node; to specify a value of another type is an error.
749 |
750 |
751 | _Example 1: Simple examples_
752 |
753 | ```yaml
754 | #@schema/examples ("One domain across the foundation", ["apps.example.com"]), ("Multi-tenant foundation", ["pets.com", "nile.com"] )
755 | apps_domain: []
756 | ```
757 |
758 | __
759 |
760 | _Example 2: Complex examples_
761 |
762 | Illustrates how different databases require different amounts of configuration.
763 |
764 | ```yaml
765 | #@ def postgres_example():
766 | name: uaa
767 | host: uaa-db.svc.cluster.local
768 | user: postgres
769 | secretRef:
770 | name: uaa-db-creds
771 | #@ end
772 |
773 | #@ def mysql_example():
774 | name: uaa
775 | adapter: mysql
776 | host: uaa-db.svc.cluster.local
777 | port: 3306
778 | user: root
779 | secretRef:
780 | name: uaa-db-creds
781 | #@ end
782 |
783 | #@schema/examples ("Postgres", postgres_example()), ("MySQL", mysql_example())
784 | databases: []
785 | ```
786 |
787 |
788 | ### @schema/deprecated
789 |
790 | Marks a node as targeted for removal while still remaining defined and used.
791 |
792 | ```yaml
793 | @schema/deprecated notice
794 | ```
795 |
796 | - `notice` (`string`) — a message elaborating on the reason for the deprication, how to prepare for
797 | the removal (e.g. migrate to using a newly introduced value), and possibly a version-based timeline
798 | for when the removal will happen.
799 |
800 | Notes:
801 | - when a value is specified for the node outside of the schema, a standardized warning that includes
802 | `notice` will be output to standard error, and evaluation continues.
803 |
804 | _Example:_
805 |
806 | ```yaml
807 | load_balancer:
808 | #@schema/deprecated "Will be removed in 2.0.0; in that version, specify `null` for `load_balancer` to disable."
809 | enable: true
810 | static_ip: ""
811 | ```
812 |
813 | __
814 |
815 | ### @schema/removed
816 |
817 | ```yaml
818 | @schema/removed remedy
819 | ```
820 |
821 | - `remedy` (`string`) — a message providing next step instructions on how to repair the document/configuration.
822 |
823 | Notes:
824 | - when a value is specified for the node outside of the schema, a standardized warning that includes
825 | `remedy` will be output to standard error, and evaluation stops.
826 |
827 | _Example:_
828 |
829 | ```yaml
830 | load_balancer:
831 | #@schema/removed "Was removed as of 2.0.0; specify `null` for `load_balancer` to disable."
832 | enable: true
833 | static_ip: ""
834 | ```
835 |
836 |
837 | ## Part 7: Validating Documents
838 |
839 | Beyond specifying a type for a value, one can specify more dynamic constraints on the value via validations.
840 |
841 | ### @schema/validate
842 |
843 | ```yaml
844 | @schema/validate [...user-provided-funcs,] [...builtin-kwargs]
845 | ```
846 |
847 | This annotation specifies how to validate value. `@schema/validate` will provide a set of keyword arguments which map to built-in validations, such as `max`, `max_len`, etc., but will also accept user-defined validation functions. Less common validators will also be provided via a `validations` library. For example,
848 |
849 | ```yaml
850 | #@ def number_is_even(num): return (num % 2 == 0, "Number is not even")
851 |
852 | #@schema/validate number_is_even, min=2
853 | replicas: 6
854 | ```
855 |
856 | will use the `min` predefined validator as well as the user-provided `number_is_even` function for validations. Function arguments should have a signature which matches the signature of custom functions passed to [`@overlay/assert`](https://github.com/k14s/ytt/blob/develop/docs/lang-ref-ytt-overlay.md).
857 |
858 | Full list of builtin validations exposed via kwargs (all have 2nd form: a tuple of value and error message):
859 |
860 | - min=int
861 | - max=int
862 | - min_len=int
863 | - max_len=int
864 | - enum=[string|int|...]: any set of values
865 | - regexp=string (TBD or pattern?) -> golang regexp
866 | - format=string (mostly copied from JSON Schema)
867 | - date-time: Date and time together, for example, 2018-11-13T20:20:39+00:00.
868 | - time: New in draft 7 Time, for example, 20:20:39+00:00
869 | - date: New in draft 7 Date, for example, 2018-11-13.
870 | - email: Internet email address, see RFC 5322, section 3.4.1.
871 | - hostname: Internet host name, see RFC 1034, section 3.1.
872 | - ip: IPv4 or IPv6
873 | - ipv4: IPv4 address, according to dotted-quad ABNF syntax as defined in RFC 2673, section 3.2.
874 | - ipv6: IPv6 address, as defined in RFC 2373, section 2.2.
875 | - uri: A universal resource identifier (URI), according to RFC3986.
876 | - port: >= 0 && <= 65535
877 | - percent: k8s's IsValidPercent e.g. "10%"
878 | - duration: Golang's duration e.g. "10h"
879 | - not_null=bool to verify value is not null
880 | - unique=bool to verify value contains unique elements (TBD?)
881 | - starts_with=string to verify string has prefix
882 | - ends_with=string to verify string has suffix
883 | - TBD
884 |
885 | Full list of builtin validations included in a library:
886 |
887 | - ...kwargs validations as functions...
888 | - base64.decodable()
889 | - json.decodable()
890 | - yaml.decodable()
891 | - regexp.is_valid()
892 |
893 | String values, by default, have `@schema/validate min_len=1` validation added.
894 |
895 | #### Validation Documentation
896 |
897 | Configuration Consumers are more likely to provide valid values if they are aware
898 | of what defines valid.
899 |
900 | For this reason, validations should provide user-friendly documentation that makes
901 | this clear:
902 | - concise phrase that describes a valid value;
903 | - a short list of common example valid values;
904 | - citation to canonical definition.
905 |
906 | TBD.
907 |
908 | Sketch:
909 | - invoke the validation function with no arguments
910 | - if it returns a `string` that is documentation (including the name)
911 | - if it returns an error, discard and replace with default documentation (e.g. "Validation: "+str(validation_func))
912 | - first class `ytt` Schema functions can include this behavior
913 | - 3rd party functions can be wrapped in a function (or lambda expression) to decorate this behavior:
914 | ```python
915 | def is_valid_ip(val="__yttdoc__"):
916 | if val != "__yttdoc__":
917 | return thirdPartyLib.valid_ip(val)
918 | else:
919 | return "Valid IP: a well-formed IPv4 (RFC 2673, section 3.2) or IPv6 address (RFC 2373, section 2.2)."
920 | end
921 | ```
922 |
923 | **Implementation considerations:**
924 |
925 |
926 | As part of the implementation of the validation feature we should use some of the knowledge from the Type Checking
927 | implemented before.
928 | - Isolation of packages like yamlmeta from external dependencies
929 |
930 | This package is intended as a place for the yaml internal structures
931 | - Eventually it make sense to implement the validation near the Schemas and also move the Type Check to the same package
932 |
933 | When implementing we should consider to create a walker that iterate over the YAML document to do the Check or the Validation depending on the action we want to perform in a given moment
934 |
935 | ## Part 8: Command-Line Interface Interactions
936 |
937 | :::warning
938 | There is active User Research into how to more effectively communicate with `ytt` users. Findings from that research
939 | should trump whatever "requirements" are specified below.
940 | :::
941 |
942 | Schema violation messages should be:
943 | - readable — easy/quick to determine the nature and details of the violation.
944 | - actionable — clear what step would need to be taken to fix the violation.
945 | - complete — no other context is required to fix the violation.
946 | - supportive — informs, without blame.
947 |
948 | To get there, consider these principles:
949 | - convey concisely — the fewer words written, the more words read.
950 | - be consistent — use the same word for the same concept; the same layout for the same kind of message.
951 | - prioritize position of information — place more important information in a more prominent spot.
952 | - be empathetic — use words the user must already know; when you cannot, define it or point to a definition.
953 |
954 | ### Type Violations
955 |
956 | When a value given is malformed (wrong structure/type) `ytt` will report these violations and stop processing.
957 |
958 | Type violations can come from:
959 | - an incorrectly formed explicit default value (i.e. the value of `@schema/default`)
960 | - a data value (noted below: all `@data/values` documents are implied overlays)
961 |
962 | `schema.yml`
963 | ```yaml=
964 | #@schema/definition name="data/values"
965 | ---
966 | system_domain: ""
967 |
968 | load_balancer:
969 | enabled: true
970 | external_ip: ""
971 | ```
972 |
973 | `values.yml`
974 | ```yaml=
975 | #@data/values
976 | ---
977 | system_domain: false
978 | load_balancer: true
979 | ```
980 |
981 | ```
982 | $ ytt -f schema.yml -f values.yml
983 | ytt: Error: ...
984 |
985 | Result of values.yml did not pass "data/values" schema:
986 |
987 | values.yml:3 | system_domain:
988 | | ^^^ found: boolean
989 | | expected: string (by schema.yml:3)
990 | ```
991 |
992 | **Note:**
993 | - the document at `values.yml:2` implies `@overlay/match by=overlay.all` and `@overlay/match-child-defaults missing_ok=True`
994 | - `values.yml` has two overlay operations: merge of `system_domain` and merge of `load_balancer`.
995 | - there are, in fact, two type violations. However, `ytt` stops after the first overlay operation fails.
996 |
997 | ### Validation Violations
998 |
999 | ## Part 9: Interactions with Existing Features
1000 |
1001 | ### Changes to Data Values
1002 |
1003 |
1004 | #### Data Value Overlays Become More Permissive
1005 |
1006 | Prior to Schemas, Data Values protected against mistakes like typo'ed key names through the strict merge of additional data values documents: to successfully merge a new key, it must be marked as `@overlay/match missing_ok=True`.
1007 |
1008 | The responsibility to ensure key names are valid are shifted from Data Values to Schema.
1009 |
1010 | When schema is present, Data Values documents imply an automatic merge of new keys. This is equivalent to all `@data/values` documents being annotated with `@overlay/match-child-defaults missing_ok=True`.
1011 |
1012 | This behavior should not result in an error if a Data Values document _already_ is annotated this way.
1013 |
1014 |
1015 | #### All Data Values Become Overlays
1016 |
1017 | Before Schema, the first YAML document annotated with `@data/values` effectively defined both an implied schema and defaults.
1018 |
1019 | With Schema, the base data values document is produced by collecting all the net default values from the schema, itself.
1020 |
1021 | `ytt` will automatically convert _all_ data values into overlays.
1022 |
1023 |
1024 | ### Overlays
1025 |
1026 | Schema aims to provide the earliest possible and actionable feedback so that Configuration Consumers can successfully complete their task in the shortest possible duration.
1027 |
1028 | When one or more schema violation(s) occur after a Data Value overlay operation, `ytt` should:
1029 | 1. display the current state of the violating portion of the document; (it's quite possible, this data does not appear anywhere else)
1030 | 2. report the violation(s); and
1031 | 3. stop processing.
1032 |
1033 |
1034 | ### Overlays of Schema
1035 |
1036 | TBD
1037 |
1038 | - It must be possible to extend Schema (i.e. add new items)
1039 | - When do Configuration Consumers need to modify Schema?
1040 | - they add templates that require additional data values
1041 | - soften or remove overzealous validations
1042 | - when overlays on templates change the type of the expected data values
1043 |
1044 | ## Open Questions
1045 |
1046 | - Given that Data Values all become overlays, now all array items require a `@overlay/append`
1047 | - this will be a negative UX for brand new users as it lights a number of tool features just so that I can specify an array for my library. :(
1048 | - this has been a pain
1049 | - How could we assign line numbers to YAML nodes that are generated from Starlark code?
1050 | - how common of a situation is this?
1051 | - becomes more important when referencing nodes in schema because our violation messages depend on naming the type/structure declaration by line number.
1052 | - Overlaying Schema implies the need for meta to be copied along
1053 | - Documentation could be enhanced if validation rules were included. How could validation authors provide documentation for their validation?
1054 | - provide a magic string for the input and the function returns documentation?
1055 | - function always returns documentation as part of return value?
1056 | - How should schema work with ytt libraries?
1057 | - How do we support data values that are targeting a dependency library?
1058 | - How will code defined in a schema file make its way into the execution context of the target "template"?
1059 | - e.g. function supporting validation
1060 | - idea: could we capture the "globals" resulting from the execution of the schema "template" and feed those into the target "template"'s execution context?
1061 | - there are expressions/statements to consume during the schema "template" construction... but other expression/statements that need to be made available to target "templates".
1062 | - Should we support generating `ytt` Schema from other popular sources (e.g. JSON Schema, OpenAPI v3 schema)?
1063 | - how to add more things to schema? (via overlays similar to data values)
1064 | - provide programmatic schema.apply() (similar to overlay.apply)
1065 | - respect schema for data values set via cmd line flags/env vars
1066 | - add --data-values-schema-inspect (similar to --data-values-inspect)
1067 | - a component team introduces breaking change… how do we discover that happened? (is that a use case that's in scope for ytt?)
1068 | - schema diff
1069 | - as a Configuration Consumer, what drives me asking for a diff? what am I looking for?
1070 | - what do we get from YAML diffing tools?
1071 | - what is the gap between the need and what we get with diffing tools?
1072 | - using extensions for ytt types.
1073 | - https://github.com/k14s/ytt/issues/51
1074 |
1075 |
1076 | # Sample Usage
1077 |
1078 | Vetting of User eXperience by way of examples.
1079 |
1080 | ## Common Use Cases
1081 |
1082 | Expectedly common situations that invoke more than one feature, illustrating proper interactions.
1083 |
1084 | ### Mutually Exclusive Map Configuration
1085 |
1086 | ## Edge Cases
1087 |
1088 | Situations that stretch the capabilities of the feature set.
1089 |
1090 | ### Prometheus Label Set Rewriting
1091 |
1092 | Prometheus includes a feature allowing operators to articulate label rewritting rules for metrics.
1093 | This feature is used throughout Prometheus' configuration. It is centered around a structure
1094 | known as a "relabeling".
1095 |
1096 | Defining edits that operate on runtime text is frought with errors.
1097 | - a minor typo of a keyword,
1098 | - a regular expression that fails only in certain cases, or
1099 | - a sometimes required configuration being omitted
1100 |
1101 | This example illustrates how Schema would enhance the Prometheus User's configuration experience.
1102 |
1103 | Sources:
1104 | - https://github.com/prometheus-community/helm-charts/blob/451448b53/charts/kube-prometheus-stack/values.yaml
1105 | - https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config
1106 |
1107 | ```yaml=
1108 | #! ==- Relabel Config -==
1109 | #!
1110 |
1111 | #@ def replace_has_target_label(relabel):
1112 | #@ if relabel.action == "replace" and "targetLabel" not in relabel:
1113 | #@ return (False, "'targetLabel' not found on replace relabel rule")
1114 | #@ end
1115 | #@ end
1116 |
1117 | #@ def non_hashmod_has_regex(relabel):
1118 | #@ if relabel.action in ["replace", "keep", "drop", "labelmap", "labeldrop", "labelkeep"] and \
1119 | #@ "regex" not in relabel:
1120 | #@ return (False, "'regex' not found on {} relabel rule".format(relabel.action))
1121 | #@ end
1122 | #@ end
1123 |
1124 | #! As of Prometheus 2.22, there are 200+ meta labels
1125 | #@ known_meta_labels = {
1126 | #@ "__meta_azure_machine_id": True,
1127 | #@ "__meta_azure_machine_location": True,
1128 | #@ "__meta_azure_machine_name": True,
1129 | #@ "__meta_azure_machine_os_type": True,
1130 | #@ "__meta_azure_machine_private_ip": True,
1131 | #! ... and many more
1132 | #@ }
1133 | #@ def known_meta_label(label):
1134 | #@ if label.startswith("__meta_"):
1135 | #@ if known_meta_labels[label] == None:
1136 | #@ return (False,
1137 | #@ "{} is not a known meta label (did you mean {}?)"\
1138 | #@ .format(label, spelling.nearest(label, known_meta_labels.keys())))
1139 | #@ end
1140 | #@ end
1141 | #@ end
1142 |
1143 | ---
1144 | #@ def relabeling():
1145 | #@schema/title "Source labels"
1146 | #@ doc = '''
1147 | #@ The source labels select values from existing labels. Their content is concatenated
1148 | #@ using the configured separator and matched against the configured regular expression
1149 | #@ for the replace, keep, and drop actions.
1150 | #@ '''
1151 | #@schema/doc doc
1152 | sourceLabels:
1153 | #@schema/validate known_meta_label, regexp=("[a-zA-Z_][a-zA-Z0-9_]*", "letters and numbers, starting with a letter")
1154 | - ""
1155 |
1156 | #@schema/title "Source Label Separator"
1157 | #@schema/doc "Separator placed between concatenated source label values."
1158 | separator: ;
1159 |
1160 | #@schema/title "Target Label"
1161 | #@ doc = '''
1162 | #@ Label to which the resulting value is written in a replace action.
1163 | #@ It is mandatory for replace actions. Regex capture groups are available.
1164 | #@ '''
1165 | #@schema/doc
1166 | #@schema/key missing_ok=True
1167 | #@schema/validate regexp=("[a-zA-Z_][a-zA-Z0-9_]*", "letters and numbers, starting with a letter")
1168 | targetLabel: ""
1169 |
1170 | #@schema/title "Extraction regex"
1171 | #@schema/doc "Regular expression against which the extracted value is matched."
1172 | #@schema/key missing_ok=True
1173 | #@schema/validate regexp.is_valid
1174 | regex: ^(.*)$
1175 |
1176 | #@schema.title ""
1177 | #@schema/key missing_ok=True
1178 | modulus: 0
1179 |
1180 | replacement: $1
1181 |
1182 | #@schema/validate enum=["replace", "keep", "drop", "hashmod", "labelmap", "labeldrop", "labelkeep"]
1183 | action: replace
1184 | #@ end
1185 |
1186 | #@ def relabelings():
1187 | #@schema/validate replace_has_target_label, non_hashmod_has_regex
1188 | - #@ relabeling()
1189 | #@ end
1190 |
1191 | #! metrics_path is required to match upstream rules and charts
1192 | #@ def metrics_path_relabel():
1193 | sourceLabels: [__metrics_path__]
1194 | targetLabel: metrics_path
1195 | #@ end
1196 |
1197 | ---
1198 | kubelet:
1199 | serviceMonitor:
1200 |
1201 | #@schema/title "Scrape interval"
1202 | #@schema/doc "If not set, the Prometheus default scrape interval is used."
1203 | #@schema/validate format="duration"
1204 | #@schema/key missing_ok=True
1205 | interval: ""
1206 |
1207 | #@schema/doc "relabel configs to apply to cAdvisor metric samples before ingestion."
1208 | cAdvisorMetricRelabelings: #@ relabelings()
1209 |
1210 | #@schema/doc "relabel configs to apply to probe metric samples before ingestion."
1211 | probesMetricRelabelings: #@ relabelings()
1212 |
1213 | #@schema/doc "relabel configs to apply to cAdvisor samples before ingestion."
1214 | #@schema/default [metrics_path_relabel()]
1215 | cAdvisorRelabelings: #@ relabelings()
1216 |
1217 | #@schema/doc "relabel configs to apply to probe samples before ingestion."
1218 | #@schema/default [metrics_path_relabel()]
1219 | probesRelabelings: #@ relabelings()
1220 |
1221 | #@schema/doc "relabel configs to apply to resource samples before ingestion."
1222 | #@schema/default [metrics_path_relabel()]
1223 | resourceRelabelings: #@ relabelings()
1224 |
1225 | #@schema/doc "relabel configs to apply to metric samples before ingestion."
1226 | metricRelabelings: #@ relabelings()
1227 |
1228 | #@schema/doc "relabel configs to apply to samples before ingestion."
1229 | #@schema/default [metrics_path_relabel()]
1230 | relabelings: #@ relabelings()
1231 | ```
1232 |
1233 | Pros:
1234 | - demonstrates the leverage functions bring: to extract the concept of a "relabeling", attach defaults and validations,
1235 | and use widely.
1236 | - trickier configuration (e.g. regular expressions, exact meta label names, string formats) are not just caught earlier,
1237 | but with context for more actionable error messages.
1238 | - ...
1239 |
1240 | Cons:
1241 | - validation rules across the map + the map is an array item ==> the definition of "relabeling" had to be extracted and scoped to a separate document.
1242 | - ...
1243 |
1244 |
1245 | ## Migration of Existing Configuration
1246 | ### Cloud Foundry for Kubernetes
1247 |
1248 | Cloud Foundry for Kubernetes — at the time of writing — is the most sophisticated use of `ytt` known.
1249 | In the absence of the Schema feature specified in this document, the CF for K8s project has written
1250 | their own custom validation logic.
1251 |
1252 | With `ytt` Schemas, _all_ existing validation logic ought to be absorbed into the schema definition.
1253 |
1254 | Specifically:
1255 | - "required" parameters : all string values imply a `@schema/validate min_len=1` by default
1256 | - a "non empty array" is `@schema/validate min_len=1`
1257 | - _(others ?)_
1258 |
1259 | ```yaml=
1260 | #@schema/definition name="data/values"
1261 | ---
1262 | #@schema/doc "your system domain"
1263 | #@schema/example "system.cf.example.com"
1264 | #@schema/validate format="hostname"
1265 | system_domain: ""
1266 |
1267 | #@schema/title "Application Domains"
1268 | #@schema/example ["apps.cf.example.com"]
1269 | #@schema/validate min_len=1
1270 | app_domains:
1271 | -
1272 | #@schema/validate format="hostname"
1273 | ""
1274 |
1275 | #@schema/title "Application Log Distinations"
1276 | app_log_destinations: []
1277 |
1278 | #@schema/title "Cloud Foundry Administrator Password"
1279 | #@schema/doc "password for admin user (in plain text)"
1280 | cf_admin_password:
1281 |
1282 | load_balancer:
1283 | enable: true
1284 | #@schema/title "Load Balancer Static IP"
1285 | #@schema/doc "Reserved static IP for Load Balancer"
1286 | #@schema/validation format="ip"
1287 | static_ip: ""
1288 |
1289 | system_certificate:
1290 | #@schema/title "System Certificate"
1291 | #@ doc = '''\
1292 | #@ Certificate for the wildcard subdomain of the system domain.
1293 | #@ For example, CN=*.system.cf.example.com
1294 | #@ '''
1295 | #@schema/doc doc
1296 | #@schema/validate base64.decodable
1297 | crt: ""
1298 | #@schema/title "Private Key"
1299 | #@schema/doc "Private key for the system certificate"
1300 | #@schema/validate base64.decodable
1301 | key: ""
1302 | #@schema/title "CA Certificate"
1303 | #@schema/doc "CA certificate used to sign the system certificate"
1304 | #@schema/validate base64.decodable
1305 | ca: ""
1306 |
1307 | workloads_certificate:
1308 | #@schema/title "Applications Certificate"
1309 | #@ doc = '''\
1310 | #@ Certificate for the wildcard subdomain of the applications domain.
1311 | #@ For example, CN=*.apps.cf.example.co
1312 | #@ '''
1313 | #@schema/doc doc
1314 | #@schema/validate base64.decodable
1315 | crt: ""
1316 | #@schema/title "Private Key"
1317 | #@schema/doc "Private key for the applications certificate"
1318 | #@schema/validate base64.decodable
1319 | key: ""
1320 | #@schema/title "CA Certificate"
1321 | #@schema/doc "CA certificate used to sign the applications certificate"
1322 | #@schema/validate base64.decodable
1323 | ca: ""
1324 |
1325 | gateway:
1326 | #@schema/doc "When true, automatically upgrades incoming HTTP connections to HTTPS"
1327 | https_only: true
1328 |
1329 | capi:
1330 | #@schema/title "CAPI DB"
1331 | #@schema/doc "Database for Cloud API"
1332 | database:
1333 | #@schema/validation enum=["postgres", "mysql2"]
1334 | adapter: postgres
1335 | encryption_key: ""
1336 | host: "cf-db-postgresql.cf-db.svc.cluster.local"
1337 | #@schema/examples [("Postgres", 5432), ("MySQL", 3306)]
1338 | port: 5432
1339 | user: cloud_controller
1340 | #@schema/doc "DB user password in plain text"
1341 | password: ""
1342 | name: cloud_controller
1343 | #@schema/doc "Plain text ca certificate if tls should be used"
1344 | ca_cert: ""
1345 | log_level: info
1346 |
1347 | uaa:
1348 | #@schema/title "UAA DB"
1349 | #@schema/doc "Database for User Authentication and Authorization service"
1350 | database:
1351 | #@schema/validation enum=["postgres", "mysql2"]
1352 | adapter: postgres
1353 | encryption_key: ""
1354 | host: "cf-db-postgresql.cf-db.svc.cluster.local"
1355 | #@schema/examples [("Postgres", 5432), ("MySQL", 3306)]
1356 | port: 5432
1357 | user: uaa
1358 | #@schema/doc "DB user password in plain text"
1359 | password: ""
1360 | name: uaa
1361 | #@schema/doc "Plain text ca certificate if tls should be used"
1362 | ca_cert: ""
1363 |
1364 | app_registry:
1365 | #@schema/validate format="hostname"
1366 | hostname: ""
1367 | repository_prefix: ""
1368 | username: ""
1369 | password: ""
1370 |
1371 | remove_resource_requirements: false
1372 | add_metrics_server_components: false
1373 | allow_prometheus_metrics_access: false
1374 | use_external_dns_for_wildcard: false
1375 | enable_automount_service_account_token: false
1376 | metrics_server_prefer_internal_kubelet_address: false
1377 | use_first_party_jwt_tokens: false
1378 |
1379 | #@schema/title "CF Blobstore"
1380 | #@schema/doc "configuration for the blobstore holding buildpacks, droplets, packages, etc."
1381 | blobstore:
1382 | endpoint: "http://cf-blobstore-minio.cf-blobstore.svc.cluster.local:9000"
1383 | region: "''"
1384 | access_key_id: "admin"
1385 | secret_access_key: ""
1386 | package_directory_key: "cc-packages"
1387 | droplet_directory_key: "cc-droplets"
1388 | resource_directory_key: "cc-resources"
1389 | buildpack_directory_key: "cc-buildpacks"
1390 | aws_signature_version: "2"
1391 |
1392 | #@ doc = '''\
1393 | #@ optional configuration if using a private registry that has all cf-for-k8s images relocated to
1394 | #@ see
1395 | #@ '''
1396 | #@schema/doc doc
1397 | system_registry:
1398 | add_image_pull_secrets: false
1399 | hostname: ""
1400 | username: ""
1401 | password: ""
1402 | ```
1403 |
1404 | ### Minio
1405 |
1406 | Partial minio config (based on https://github.com/helm/charts/blob/21e2e1b1f2656c785ece0ec741b047b21539d7b1/stable/minio/values.yaml#L244), shows how to have one-of available options.
1407 |
1408 | ```yaml
1409 | #@ load("@ytt:json", "json")
1410 |
1411 | #@schema/definition name="data/values"
1412 | ---
1413 | #@schema/validate lambda g: (g.s3 or g.azure or g.gcs or g.oss, "Must specify one gateway: s3, azure, gcs, oss")
1414 | gateway:
1415 | #@schema/nullable
1416 | s3:
1417 | #@schema/validate min=1
1418 | replicas: 4
1419 | #@schema/validate prefix="https://"
1420 | serviceEndpoint: ""
1421 | accessKey: ""
1422 | secretKey: ""
1423 |
1424 | #@schema/doc "Use minio as an azure blob gateway, you should disable data persistence so no volume claim are created. https://docs.minio.io/docs/minio-gateway-for-azure"
1425 | #@schema/nullable
1426 | azure:
1427 | #@schema/doc "Number of parallel instances"
1428 | #@schema/validate min=1
1429 | replicas: 4
1430 |
1431 | #@schema/doc "Use minio as GCS (Google Cloud Storage) gateway, you should disable data persistence so no volume claim are created. https://docs.minio.io/docs/minio-gateway-for-gcs"
1432 | #@schema/nullable
1433 | gcs:
1434 | #@schema/doc "Number of parallel instances"
1435 | #@schema/validate min=1
1436 | replicas: 4
1437 | #@schema/doc "credential json file of service account key"
1438 | #@schema/validate lambda val: (json.decodable(val), "key must be valid JSON")
1439 | gcsKeyJson: ""
1440 | #@schema/doc "Google cloud project-id"
1441 | projectId: ""
1442 |
1443 | #@schema/nullable
1444 | oss:
1445 | #@schema/doc "Number of parallel instances"
1446 | #@schema/validate min=1
1447 | replicas: 4
1448 | #@schema/validate prefix="https://"
1449 | endpointURL: ""
1450 | ```
1451 |
1452 |
1453 |
1454 | ### Dex
1455 |
1456 | TODO: review and inline [Dex](example-partial-dex.yaml).
1457 |
1458 |
1459 | ## Complex Examples
1460 |
1461 | This snippet will result in an array with a single element, type `bucket`, with `versioning` defaulted to `Enabled`.
1462 |
1463 | ```yaml
1464 | #@ def default_bucket():
1465 | versioning: "Enabled"
1466 | #@ end
1467 |
1468 | #@schema/default [default_bucket()]
1469 | bucket:
1470 | - name: ""
1471 | versioning: ""
1472 | access: ""
1473 | ```
1474 |
1475 |
1476 | Array examples:
1477 |
1478 | ```yaml
1479 | service:
1480 | #@schema/doc "List of IP addresses at which the Prometheus server service is available. Ref: https://kubernetes.io/docs/user-guide/services/#external-ips"
1481 | externalIPs:
1482 | #@schema/validate format="ip"
1483 | - ""
1484 |
1485 | imagePullSecrets:
1486 | - name: ""
1487 |
1488 | ingress:
1489 | #@schema/default ["chart-example.local"]
1490 | hosts:
1491 | - ""
1492 |
1493 | tls:
1494 | - secretName: ""
1495 | #@schema/validate min=1
1496 | hosts:
1497 | - ""
1498 |
1499 | tolerations:
1500 | #@schema/type "any"
1501 | - null
1502 |
1503 | #@schema/doc "Create multiple buckets after minio install. Enabling `defaultBucket` will take priority over this list"
1504 | #@schema/example [{"name": "bucket1"}]
1505 | buckets:
1506 | - name: ""
1507 | #@schema/validate enum=["none"]
1508 | policy: none
1509 | purge: false
1510 | ```
1511 |
1512 | map[string]string examples:
1513 |
1514 | ```yaml
1515 | #@ def validate_map_str_to_str(vals):
1516 | #@ type(vals) == "dict" or assert.fail("Expected key to be map")
1517 | #@ for key in vals:
1518 | #@ type(key) == "string" or assert.fail("Expected key to be string")
1519 | #@ type(vals[key]) == "string" or assert.fail("Expected val to be string")
1520 | #@ end
1521 | #@ end
1522 |
1523 | #@schema/type "any"
1524 | #@schema/validate validate_map_str_to_str
1525 | #@schema/example {"prometheus.io/scrape": "true", "prometheus.io/path": "/minio/prometheus/metrics", "prometheus.io/port": "9000"}
1526 | annotations: {}
1527 |
1528 | #! ...
1529 |
1530 | #@schema/example {"prometheus.io/scrape": "true", "prometheus.io/path": "/minio/prometheus/metrics", "prometheus.io/port": "9000"}
1531 | annotations:
1532 | #@schema/any-key
1533 | _: ""
1534 | ```
1535 |
1536 | Type any with default (TBD, should we force schema/default?):
1537 |
1538 | ```yaml
1539 | #@schema/type "any"
1540 | annotations:
1541 | foo: bar
1542 | blah: 123
1543 | ```
1544 |
1545 | Example of additional free-form properties:
1546 |
1547 | ```yaml
1548 | annotations:
1549 | #@schema/any-key
1550 | _: ""
1551 |
1552 | db_opts:
1553 | username: ""
1554 | password: ""
1555 | #@schema/any-key
1556 | _:
1557 | #@schema/type "any"
1558 | value:
1559 | ```
1560 |
1561 | #### Asserting on higher level structure with optional lower key with #@schema/key-may-be-present
1562 |
1563 | ```yaml
1564 | #@schema
1565 | ---
1566 | foo: ""
1567 |
1568 | #@data/values
1569 | ---
1570 | foo: "val"
1571 |
1572 | config.yml:
1573 | ---
1574 | foo: #@ data.values.foo #! never errors with: no member 'foo' on data.values
1575 | ```
1576 |
1577 | ```yaml
1578 | #@schema
1579 | ---
1580 | #@schema/key missing_ok=True
1581 | foo:
1582 |
1583 | #@data/values
1584 | ---
1585 | foo: "val"
1586 |
1587 | config.yml:
1588 | ---
1589 | foo: #@ yaml.encode(data.values) #! => {}, {"foo": "val"}
1590 | ```
1591 |
1592 | ```yaml
1593 | #@schema
1594 | ---
1595 | #@schema/key missing_ok=True
1596 | foo:
1597 | max_connections: 100
1598 | username: ""
1599 |
1600 | #@data/values
1601 | ---
1602 | foo:
1603 | username: val
1604 |
1605 | config.yml:
1606 | ---
1607 | foo: #@ yaml.encode(data.values) #! => {}, {"foo": {"username": "val", "max_connections":100}}
1608 | ```
1609 |
1610 |
1611 | # Design Notes
1612 |
1613 | ## Observations/Guesses
1614 |
1615 | _(Collection of "insights" that might bring cohesion to the overall design.)_
1616 |
1617 | - In effect, we're implementing a dynamic type checking so that we can perform a sort of "late binding" — attaching defaulting and validation behavior to individual nodes.
1618 | - [Node](https://github.com/k14s/ytt/blob/cbd0c3959/pkg/yamlmeta/ast.go#L10-L33)s contain "coarse-grain" type information (i.e. what kind of YAML node is it?);
1619 | - this proposal effectively extends that abstraction to include "fine-grain" type information
1620 | - Parser _yields_ Nodes
1621 | - Schema determines Node Type
1622 | - Types _check_ Nodes
1623 |
1624 | ## Core Features
1625 |
1626 | There are six core operations:
1627 | 1. [Parsing](#Parsing) — from text file (in YAML format) to tree of yamlmeta.Node(s).
1628 | 2. [Compilation](#Compilation) — compiling YAML template into Starlark.
1629 | 4. [Typing](#Typing) — calculating "Type" for each yamlmeta.Node
1630 | 5. [Checking](#Type-Checking) — perform type check
1631 | 6. [Validating](#Validating) — execute validations
1632 | 7. [Documenting](#DocumentingSchema) — generate metadata suitable as input for templatized documentation.
1633 |
1634 |
1635 | ### Parsing
1636 |
1637 | ```graphviz
1638 | digraph graphname {
1639 |
1640 | yaml_parser [label="yaml Parser (v2)"]
1641 | yaml_node [label="yaml.v2 Node" color=blue fontcolor=blue]
1642 | ytt_yaml_node [label="yamlmeta.Node" color=purple fontcolor=purple]
1643 | ytt_parser [label="ytt 'Parser'"]
1644 |
1645 | yaml_parser -> yaml_node;
1646 | yaml_node -> ytt_parser;
1647 | ytt_parser -> ytt_yaml_node [label="«maps to»"];
1648 |
1649 | }
1650 | ```
1651 | _([graphviz reference](https://www.tonyballantyne.com/graphs.html))_
1652 |
1653 |
1654 | ### Typing
1655 |
1656 | Involves:
1657 | 1. identifying the concrete type for a given node
1658 | 2. copy in default values where missing
1659 | 3. attach "machinery" required to support validation.
1660 |
1661 | Notes:
1662 | - we do not check for violations while typing.
1663 | - typing is made on "best-effort" basis
1664 | - for Data Values, there ought to be one choice of structural types, and sparse choices of leaf values (e.g. `stringOrInt`)
1665 | - for other Document Types, we _might_ want to support multiple possible types for a given node.
1666 | - Schema maps from yamlmeta.Node(s) to yamlmeta.Node(s)
1667 |
1668 |
1669 | ```graphviz
1670 | digraph graphname {
1671 | ytt_yaml_node [label="yamlmeta.Node" color=purple fontcolor=purple]
1672 | schema [label="Schema"]
1673 | type [label="Type"]
1674 |
1675 | schema -> ytt_yaml_node [label="«finds type for»"]
1676 | ytt_yaml_node -> type [label="«has a»" color=gray];
1677 | schema -> type [label="«has list of allowed»"]
1678 | }
1679 | ```
1680 | _([graphviz reference](https://www.tonyballantyne.com/graphs.html))_
1681 |
1682 |
1683 | ### Type-Checking
1684 |
1685 | ```graphviz
1686 | digraph graphname {
1687 | rankdir=LR
1688 |
1689 | ytt_yaml_node [label="yamlmeta.Node" color=purple fontcolor=purple]
1690 |
1691 |
1692 | ytt_yaml_node -> Type [label="«has a»" color=gray fontcolor=gray];
1693 |
1694 | Value -> Type [label="«is checked against»"];
1695 | Type -> "Type Check" [label="«results in a»"];
1696 | }
1697 | ```
1698 | _([graphviz reference](https://www.tonyballantyne.com/graphs.html))_
1699 |
1700 |
1701 | ### Validation
1702 |
1703 |
1704 | ### Schema Documentation
1705 |
1706 | - users generate documentation through the `--schema-inspect` flag (this mirrors other `--...-inspect` flags that also produce information).
1707 | - this can be rendered in HTML when user couples this with `--output html`
1708 |
1709 | `schema.yml`
1710 | ```yaml=
1711 |
1712 | #@schema/title "Application Domains"
1713 | #@schema/doc "List of DNS domains used for deployed applications."
1714 | #@schema/example [“apps.cf.example.com”]
1715 | app_domains:
1716 | - ""
1717 |
1718 | uaa:
1719 | #@schema/title "UAA Login Secret"
1720 | #@schema/doc "Secret for an external login server to authenticate to UAA"
1721 | login_secret: ""
1722 | login:
1723 | service_provider:
1724 | #@schema/title "UAA Service Provider Certificate"
1725 | #@schema/doc "X.509 Certificate for UAA's SAML provider"
1726 | certificate: ""
1727 | #@schema/title "UAA Service Provider Private Key"
1728 | #@schema/doc "Private key paired with the X.509 Certificate for UAA's SAML provider"
1729 | key: ""
1730 |
1731 |
1732 | app_registry:
1733 | hostname:
1734 | username:
1735 | password:
1736 |
1737 |
1738 | ```
1739 |
1740 | ```shell
1741 | $ ytt -f schema.yml --schema-inspect
1742 | schema:
1743 | name: "data/values"
1744 | fields:
1745 | - name: username
1746 | title: Database username
1747 | documentation: The account in the database system to authenticate as.
1748 | example:
1749 | - value: admin
1750 | description: null
1751 | - name: db_url
1752 | title: Full URL to the external database.
1753 | ```
1754 |
1755 | ```shell
1756 | $ ytt -f schema.yml --schema-inspect --output html
1757 | Schema for "data/values"
1758 |
1759 |
1760 |
1761 | | Name | Description | Details | Example |
1762 |
1763 |
1764 |
1765 |
1766 | | username |
1767 | Database username |
1768 | |
1769 | |
1770 |
1771 |
1772 | |
1773 | |
1774 | |
1775 | |
1776 |
1777 |
1778 |
1779 |
1780 | | username |
1781 | User Name used to log into the database. |
1782 |
1783 | The account in the database system to authenticate as.
1784 |
1785 | Example:
1786 |
1787 | admin
1788 |
1789 |
1790 | |
1791 |
1792 | (@ end -@)
1793 |
1794 |
1795 | fields:
1796 | - name: username
1797 | title:
1798 | example:
1799 | - value: admin
1800 | description: null
1801 | - name: db_url
1802 | title: Full URL to the external database.
1803 | ```
1804 |
1805 | `docs.html.txt`
1806 | ```htmlmixed=
1807 | (@ load("@ytt:data", "data") @)
1808 |
1809 |
1810 | ```
1811 |
1812 | ```shell
1813 | $ ytt --schema-inspect -f schema.yml --output html
1814 | ```
1815 |
1816 |
1817 | # Additional References
1818 |
1819 | - 🔒 [Cloud Foundry for Kubernetes Configuration and Versioning Scheme](https://docs.google.com/document/d/1q4QyaElEX3KtIeGjwPmZpxsJpgsxin9sx7M0T4qBiW0)
1820 | - Sorbet (a typing system in Ruby): https://sorbet.org/docs/type-assertions
1821 | - Kubernetes users requesting JSON Schema: https://github.com/kubernetes/kubernetes/issues/14987
1822 | - JSON Schema Validation: https://json-schema.org/draft/2019-09/json-schema-validation.html
1823 |
1824 | # Closed Questions
1825 |
1826 | - _Is there a feasible way for a validation rule to get access to its siblings?_ **not now.** [details](https://github.com/k14s/design-docs/pull/1#discussion_r521425869)
1827 | - _Are there needs around versioning schema?_ **Not for the foreseable future.** [details](https://github.com/k14s/design-docs/pull/1#discussion_r521426706)
1828 | - _With the addition of a schema, the first data values file no longer serves that role. Could we improve the UX of adding multiple data value files by not requiring subsequent data values to be overlays?_ **Yes, which should be covered in [Part 8: Interactions with Existing Features](#Part-8-Interactions-with-Existing-Features).
1829 | - _Could a user include both a data/value + schema/amend in the same file?_ **For now, no.... but maybe** [details](https://github.com/k14s/design-docs/pull/1#discussion_r521433218)
1830 |
1831 |
--------------------------------------------------------------------------------