转换 JSON Schema



JSON Schema 得到了广泛的应用,但是结构和验证都很复杂。

CUE 可以导入任何 Json Schema,可以提高可读性和可维护性。

JSONSchema

JSON Schema 本身也是 JSON,区别是 Schema 是一种 DSL (领域特定语言),也就是说它有自己特定的字段和结构。 当我们将其导入 CUE 时,cue 命令行会识别并处理。

我们会用 这里docker-compose 规范。 你可以从 Schema Store 找到许多例子或者用一些你周围的例子。

用下面的命令下载 docker-compose 规范:

wget https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json
compose-spec.json (800+ lines)

compose-spec.json

{
  "$schema": "http://json-schema.org/draft/2019-09/schema#",
  "id": "compose_spec.json",
  "type": "object",
  "title": "Compose Specification",
  "description": "The Compose file is a YAML file defining a multi-containers based application.",

  "properties": {
    "version": {
      "type": "string",
      "description": "Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file."
    },

    "services": {
      "id": "#/properties/services",
      "type": "object",
      "patternProperties": {
        "^[a-zA-Z0-9._-]+$": {
          "$ref": "#/definitions/service"
        }
      },
      "additionalProperties": false
    },

    "networks": {
      "id": "#/properties/networks",
      "type": "object",
      "patternProperties": {
        "^[a-zA-Z0-9._-]+$": {
          "$ref": "#/definitions/network"
        }
      }
    },

    "volumes": {
      "id": "#/properties/volumes",
      "type": "object",
      "patternProperties": {
        "^[a-zA-Z0-9._-]+$": {
          "$ref": "#/definitions/volume"
        }
      },
      "additionalProperties": false
    },

    "secrets": {
      "id": "#/properties/secrets",
      "type": "object",
      "patternProperties": {
        "^[a-zA-Z0-9._-]+$": {
          "$ref": "#/definitions/secret"
        }
      },
      "additionalProperties": false
    },

    "configs": {
      "id": "#/properties/configs",
      "type": "object",
      "patternProperties": {
        "^[a-zA-Z0-9._-]+$": {
          "$ref": "#/definitions/config"
        }
      },
      "additionalProperties": false
    }
  },

  "patternProperties": {"^x-": {}},
  "additionalProperties": false,

  "definitions": {

    "service": {
      "id": "#/definitions/service",
      "type": "object",

      "properties": {
        "deploy": {"$ref": "#/definitions/deployment"},
        "build": {
          "oneOf": [
            {"type": "string"},
            {
              "type": "object",
              "properties": {
                "context": {"type": "string"},
                "dockerfile": {"type": "string"},
                "args": {"$ref": "#/definitions/list_or_dict"},
                "labels": {"$ref": "#/definitions/list_or_dict"},
                "cache_from": {"type": "array", "items": {"type": "string"}},
                "network": {"type": "string"},
                "target": {"type": "string"},
                "shm_size": {"type": ["integer", "string"]},
                "extra_hosts": {"$ref": "#/definitions/list_or_dict"},
                "isolation": {"type": "string"}
              },
              "additionalProperties": false,
              "patternProperties": {"^x-": {}}
            }
          ]
        },
        "blkio_config": {
          "type": "object",
          "properties": {
            "device_read_bps": {
              "type": "array",
              "items": {"$ref": "#/definitions/blkio_limit"}
            },
            "device_read_iops": {
              "type": "array",
              "items": {"$ref": "#/definitions/blkio_limit"}
            },
            "device_write_bps": {
              "type": "array",
              "items": {"$ref": "#/definitions/blkio_limit"}
            },
            "device_write_iops": {
              "type": "array",
              "items": {"$ref": "#/definitions/blkio_limit"}
            },
            "weight": {"type": "integer"},
            "weight_device": {
              "type": "array",
              "items": {"$ref": "#/definitions/blkio_weight"}
            }
          },
          "additionalProperties": false
        },
        "cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "cgroup_parent": {"type": "string"},
        "command": {
          "oneOf": [
            {"type": "string"},
            {"type": "array", "items": {"type": "string"}}
          ]
        },
        "configs": {
          "type": "array",
          "items": {
            "oneOf": [
              {"type": "string"},
              {
                "type": "object",
                "properties": {
                  "source": {"type": "string"},
                  "target": {"type": "string"},
                  "uid": {"type": "string"},
                  "gid": {"type": "string"},
                  "mode": {"type": "number"}
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            ]
          }
        },
        "container_name": {"type": "string"},
        "cpu_count": {"type": "integer", "minimum": 0},
        "cpu_percent": {"type": "integer", "minimum": 0, "maximum": 100},
        "cpu_shares": {"type": ["number", "string"]},
        "cpu_quota": {"type": ["number", "string"]},
        "cpu_period": {"type": ["number", "string"]},
        "cpu_rt_period": {"type": ["number", "string"]},
        "cpu_rt_runtime": {"type": ["number", "string"]},
        "cpus": {"type": ["number", "string"]},
        "cpuset": {"type": "string"},
        "credential_spec": {
          "type": "object",
          "properties": {
            "config": {"type": "string"},
            "file": {"type": "string"},
            "registry": {"type": "string"}
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "depends_on": {
          "oneOf": [
            {"$ref": "#/definitions/list_of_strings"},
            {
              "type": "object",
              "additionalProperties": false,
              "patternProperties": {
                "^[a-zA-Z0-9._-]+$": {
                  "type": "object",
                  "additionalProperties": false,
                  "properties": {
                    "condition": {
                      "type": "string",
                      "enum": ["service_started", "service_healthy", "service_completed_successfully"]
                    }
                  },
                  "required": ["condition"]
                }
              }
            }
          ]
        },
        "device_cgroup_rules": {"$ref": "#/definitions/list_of_strings"},
        "devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "dns": {"$ref": "#/definitions/string_or_list"},
        "dns_opt": {"type": "array","items": {"type": "string"}, "uniqueItems": true},
        "dns_search": {"$ref": "#/definitions/string_or_list"},
        "domainname": {"type": "string"},
        "entrypoint": {
          "oneOf": [
            {"type": "string"},
            {"type": "array", "items": {"type": "string"}}
          ]
        },
        "env_file": {"$ref": "#/definitions/string_or_list"},
        "environment": {"$ref": "#/definitions/list_or_dict"},

        "expose": {
          "type": "array",
          "items": {
            "type": ["string", "number"],
            "format": "expose"
          },
          "uniqueItems": true
        },
        "extends": {
          "oneOf": [
            {"type": "string"},
            {
              "type": "object",

              "properties": {
                "service": {"type": "string"},
                "file": {"type": "string"}
              },
              "required": ["service"],
              "additionalProperties": false
            }
          ]
        },
        "external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "extra_hosts": {"$ref": "#/definitions/list_or_dict"},
        "group_add": {
          "type": "array",
          "items": {
            "type": ["string", "number"]
          },
          "uniqueItems": true
        },
        "healthcheck": {"$ref": "#/definitions/healthcheck"},
        "hostname": {"type": "string"},
        "image": {"type": "string"},
        "init": {"type": "boolean"},
        "ipc": {"type": "string"},
        "isolation": {"type": "string"},
        "labels": {"$ref": "#/definitions/list_or_dict"},
        "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "logging": {
          "type": "object",

          "properties": {
            "driver": {"type": "string"},
            "options": {
              "type": "object",
              "patternProperties": {
                "^.+$": {"type": ["string", "number", "null"]}
              }
            }
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "mac_address": {"type": "string"},
        "mem_limit": {"type": ["number", "string"]},
        "mem_reservation": {"type": ["string", "integer"]},
        "mem_swappiness": {"type": "integer"},
        "memswap_limit": {"type": ["number", "string"]},
        "network_mode": {"type": "string"},
        "networks": {
          "oneOf": [
            {"$ref": "#/definitions/list_of_strings"},
            {
              "type": "object",
              "patternProperties": {
                "^[a-zA-Z0-9._-]+$": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "aliases": {"$ref": "#/definitions/list_of_strings"},
                        "ipv4_address": {"type": "string"},
                        "ipv6_address": {"type": "string"},
                        "link_local_ips": {"$ref": "#/definitions/list_of_strings"},
                        "priority": {"type": "number"}
                      },
                      "additionalProperties": false,
                      "patternProperties": {"^x-": {}}
                    },
                    {"type": "null"}
                  ]
                }
              },
              "additionalProperties": false
            }
          ]
        },
        "oom_kill_disable": {"type": "boolean"},
        "oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
        "pid": {"type": ["string", "null"]},
        "pids_limit": {"type": ["number", "string"]},
        "platform": {"type": "string"},
        "ports": {
          "type": "array",
          "items": {
            "oneOf": [
              {"type": "number", "format": "ports"},
              {"type": "string", "format": "ports"},
              {
                "type": "object",
                "properties": {
                  "mode": {"type": "string"},
                  "host_ip": {"type": "string"},
                  "target": {"type": "integer"},
                  "published": {"type": "integer"},
                  "protocol": {"type": "string"}
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            ]
          },
          "uniqueItems": true
        },
        "privileged": {"type": "boolean"},
        "profiles": {"$ref": "#/definitions/list_of_strings"},
        "pull_policy": {"type": "string", "enum": [
          "always", "never", "if_not_present", "build", "missing"
        ]},
        "read_only": {"type": "boolean"},
        "restart": {"type": "string"},
        "runtime": {
          "type": "string"
        },
        "scale": {
          "type": "integer"
        },
        "security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
        "shm_size": {"type": ["number", "string"]},
        "secrets": {
          "type": "array",
          "items": {
            "oneOf": [
              {"type": "string"},
              {
                "type": "object",
                "properties": {
                  "source": {"type": "string"},
                  "target": {"type": "string"},
                  "uid": {"type": "string"},
                  "gid": {"type": "string"},
                  "mode": {"type": "number"}
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            ]
          }
        },
        "sysctls": {"$ref": "#/definitions/list_or_dict"},
        "stdin_open": {"type": "boolean"},
        "stop_grace_period": {"type": "string", "format": "duration"},
        "stop_signal": {"type": "string"},
        "storage_opt": {"type": "object"},
        "tmpfs": {"$ref": "#/definitions/string_or_list"},
        "tty": {"type": "boolean"},
        "ulimits": {
          "type": "object",
          "patternProperties": {
            "^[a-z]+$": {
              "oneOf": [
                {"type": "integer"},
                {
                  "type": "object",
                  "properties": {
                    "hard": {"type": "integer"},
                    "soft": {"type": "integer"}
                  },
                  "required": ["soft", "hard"],
                  "additionalProperties": false,
                  "patternProperties": {"^x-": {}}
                }
              ]
            }
          }
        },
        "user": {"type": "string"},
        "userns_mode": {"type": "string"},
        "volumes": {
          "type": "array",
          "items": {
            "oneOf": [
              {"type": "string"},
              {
                "type": "object",
                "required": ["type"],
                "properties": {
                  "type": {"type": "string"},
                  "source": {"type": "string"},
                  "target": {"type": "string"},
                  "read_only": {"type": "boolean"},
                  "consistency": {"type": "string"},
                  "bind": {
                    "type": "object",
                    "properties": {
                      "propagation": {"type": "string"},
                      "create_host_path": {"type": "boolean"}
                    },
                    "additionalProperties": false,
                    "patternProperties": {"^x-": {}}
                  },
                  "volume": {
                    "type": "object",
                    "properties": {
                      "nocopy": {"type": "boolean"}
                    },
                    "additionalProperties": false,
                    "patternProperties": {"^x-": {}}
                  },
                  "tmpfs": {
                    "type": "object",
                    "properties": {
                      "size": {
                        "oneOf": [
                          {"type": "integer", "minimum": 0},
                          {"type": "string"}
                        ]
                      }
                    },
                    "additionalProperties": false,
                    "patternProperties": {"^x-": {}}
                  }
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            ]
          },
          "uniqueItems": true
        },
        "volumes_from": {
          "type": "array",
          "items": {"type": "string"},
          "uniqueItems": true
        },
        "working_dir": {"type": "string"}
      },
      "patternProperties": {"^x-": {}},
      "additionalProperties": false
    },

    "healthcheck": {
      "id": "#/definitions/healthcheck",
      "type": "object",
      "properties": {
        "disable": {"type": "boolean"},
        "interval": {"type": "string", "format": "duration"},
        "retries": {"type": "number"},
        "test": {
          "oneOf": [
            {"type": "string"},
            {"type": "array", "items": {"type": "string"}}
          ]
        },
        "timeout": {"type": "string", "format": "duration"},
        "start_period": {"type": "string", "format": "duration"}
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },
    "deployment": {
      "id": "#/definitions/deployment",
      "type": ["object", "null"],
      "properties": {
        "mode": {"type": "string"},
        "endpoint_mode": {"type": "string"},
        "replicas": {"type": "integer"},
        "labels": {"$ref": "#/definitions/list_or_dict"},
        "rollback_config": {
          "type": "object",
          "properties": {
            "parallelism": {"type": "integer"},
            "delay": {"type": "string", "format": "duration"},
            "failure_action": {"type": "string"},
            "monitor": {"type": "string", "format": "duration"},
            "max_failure_ratio": {"type": "number"},
            "order": {"type": "string", "enum": [
              "start-first", "stop-first"
            ]}
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "update_config": {
          "type": "object",
          "properties": {
            "parallelism": {"type": "integer"},
            "delay": {"type": "string", "format": "duration"},
            "failure_action": {"type": "string"},
            "monitor": {"type": "string", "format": "duration"},
            "max_failure_ratio": {"type": "number"},
            "order": {"type": "string", "enum": [
              "start-first", "stop-first"
            ]}
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "resources": {
          "type": "object",
          "properties": {
            "limits": {
              "type": "object",
              "properties": {
                "cpus": {"type": ["number", "string"]},
                "memory": {"type": "string"}
              },
              "additionalProperties": false,
              "patternProperties": {"^x-": {}}
            },
            "reservations": {
              "type": "object",
              "properties": {
                "cpus": {"type": ["number", "string"]},
                "memory": {"type": "string"},
                "generic_resources": {"$ref": "#/definitions/generic_resources"},
                "devices": {"$ref": "#/definitions/devices"}
              },
              "additionalProperties": false,
              "patternProperties": {"^x-": {}}
            }
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "restart_policy": {
          "type": "object",
          "properties": {
            "condition": {"type": "string"},
            "delay": {"type": "string", "format": "duration"},
            "max_attempts": {"type": "integer"},
            "window": {"type": "string", "format": "duration"}
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "placement": {
          "type": "object",
          "properties": {
            "constraints": {"type": "array", "items": {"type": "string"}},
            "preferences": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "spread": {"type": "string"}
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            },
            "max_replicas_per_node": {"type": "integer"}
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        }
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },

    "generic_resources": {
      "id": "#/definitions/generic_resources",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "discrete_resource_spec": {
            "type": "object",
            "properties": {
              "kind": {"type": "string"},
              "value": {"type": "number"}
            },
            "additionalProperties": false,
            "patternProperties": {"^x-": {}}
          }
        },
        "additionalProperties": false,
        "patternProperties": {"^x-": {}}
      }
    },

    "devices": {
      "id": "#/definitions/devices",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
            "capabilities": {"$ref": "#/definitions/list_of_strings"},
            "count": {"type": ["string", "integer"]},
            "device_ids": {"$ref": "#/definitions/list_of_strings"},
            "driver":{"type": "string"},
            "options":{"$ref": "#/definitions/list_or_dict"}
          },
        "additionalProperties": false,
        "patternProperties": {"^x-": {}}
      }
    },

    "network": {
      "id": "#/definitions/network",
      "type": ["object", "null"],
      "properties": {
        "name": {"type": "string"},
        "driver": {"type": "string"},
        "driver_opts": {
          "type": "object",
          "patternProperties": {
            "^.+$": {"type": ["string", "number"]}
          }
        },
        "ipam": {
          "type": "object",
          "properties": {
            "driver": {"type": "string"},
            "config": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "subnet": {"type": "string", "format": "subnet_ip_address"},
                  "ip_range": {"type": "string"},
                  "gateway": {"type": "string"},
                  "aux_addresses": {
                    "type": "object",
                    "additionalProperties": false,
                    "patternProperties": {"^.+$": {"type": "string"}}
                  }
                },
                "additionalProperties": false,
                "patternProperties": {"^x-": {}}
              }
            },
            "options": {
              "type": "object",
              "additionalProperties": false,
              "patternProperties": {"^.+$": {"type": "string"}}
            }
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "external": {
          "type": ["boolean", "object"],
          "properties": {
            "name": {
              "deprecated": true,
              "type": "string"
            }
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "internal": {"type": "boolean"},
        "enable_ipv6": {"type": "boolean"},
        "attachable": {"type": "boolean"},
        "labels": {"$ref": "#/definitions/list_or_dict"}
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },

    "volume": {
      "id": "#/definitions/volume",
      "type": ["object", "null"],
      "properties": {
        "name": {"type": "string"},
        "driver": {"type": "string"},
        "driver_opts": {
          "type": "object",
          "patternProperties": {
            "^.+$": {"type": ["string", "number"]}
          }
        },
        "external": {
          "type": ["boolean", "object"],
          "properties": {
            "name": {
              "deprecated": true,
              "type": "string"
            }
          },
          "additionalProperties": false,
          "patternProperties": {"^x-": {}}
        },
        "labels": {"$ref": "#/definitions/list_or_dict"}
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },

    "secret": {
      "id": "#/definitions/secret",
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "file": {"type": "string"},
        "external": {
          "type": ["boolean", "object"],
          "properties": {
            "name": {"type": "string"}
          }
        },
        "labels": {"$ref": "#/definitions/list_or_dict"},
        "driver": {"type": "string"},
        "driver_opts": {
          "type": "object",
          "patternProperties": {
            "^.+$": {"type": ["string", "number"]}
          }
        },
        "template_driver": {"type": "string"}
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },

    "config": {
      "id": "#/definitions/config",
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "file": {"type": "string"},
        "external": {
          "type": ["boolean", "object"],
          "properties": {
            "name": {
              "deprecated": true,
              "type": "string"
            }
          }
        },
        "labels": {"$ref": "#/definitions/list_or_dict"},
        "template_driver": {"type": "string"}
      },
      "additionalProperties": false,
      "patternProperties": {"^x-": {}}
    },

    "string_or_list": {
      "oneOf": [
        {"type": "string"},
        {"$ref": "#/definitions/list_of_strings"}
      ]
    },

    "list_of_strings": {
      "type": "array",
      "items": {"type": "string"},
      "uniqueItems": true
    },

    "list_or_dict": {
      "oneOf": [
        {
          "type": "object",
          "patternProperties": {
            ".+": {
              "type": ["string", "number", "boolean", "null"]
            }
          },
          "additionalProperties": false
        },
        {"type": "array", "items": {"type": "string"}, "uniqueItems": true}
      ]
    },

    "blkio_limit": {
      "type": "object",
      "properties": {
        "path": {"type": "string"},
        "rate": {"type": ["integer", "string"]}
      },
      "additionalProperties": false
    },
    "blkio_weight": {
      "type": "object",
      "properties": {
        "path": {"type": "string"},
        "weight": {"type": "integer"}
      },
      "additionalProperties": false
    },

    "constraints": {
      "service": {
        "id": "#/definitions/constraints/service",
        "anyOf": [
          {"required": ["build"]},
          {"required": ["image"]}
        ],
        "properties": {
          "build": {
            "required": ["context"]
          }
        }
      }
    }
  }
}

转换为 Cuelang

将 JSON Schema 转为 CUE 非常简单。

cue import -f -p compose -l '#ComposeSpec:' compose-spec.json
  • -f 强制覆盖输出文件 (compose-spec.cue)
  • -p 设置输出文件的 package
  • -l 是放置结构的标签
  • 最后一个参数是输入文件

因为 docker-compose.yaml 有非常多的字段,所以 CUE 最终也有 400+ 行,但是 CUE 会更容易阅读和理解。

compose-spec.json (400+ lines)

compose-spec.cue

package compose

import "list"

#ComposeSpec: {
	// Compose Specification
	//
	// The Compose file is a YAML file defining a multi-containers
	// based application.
	@jsonschema(schema="http://json-schema.org/draft/2019-09/schema#")

	// Version of the Compose specification used. Tools not
	// implementing required version MUST reject the configuration
	// file.
	version?: string
	services?: {
		{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #service}
	}
	networks?: {
		{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #network}
		...
	}
	volumes?: {
		{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #volume}
	}
	secrets?: {
		{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #secret}
	}
	configs?: {
		{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: #config}
	}

	{[=~"^x-" & !~"^(version|services|networks|volumes|secrets|configs)$"]: _}

	#service: {
		deploy?: #deployment
		build?:  string | {
			context?:    string
			dockerfile?: string
			args?:       #list_or_dict
			labels?:     #list_or_dict
			cache_from?: [...string]
			network?:     string
			target?:      string
			shm_size?:    int | string
			extra_hosts?: #list_or_dict
			isolation?:   string

			{[=~"^x-" & !~"^(context|dockerfile|args|labels|cache_from|network|target|shm_size|extra_hosts|isolation)$"]: _}
		}
		blkio_config?: {
			device_read_bps?: [...#blkio_limit]
			device_read_iops?: [...#blkio_limit]
			device_write_bps?: [...#blkio_limit]
			device_write_iops?: [...#blkio_limit]
			weight?: int
			weight_device?: [...#blkio_weight]
		}
		cap_add?:       list.UniqueItems() & [...string]
		cap_drop?:      list.UniqueItems() & [...string]
		cgroup_parent?: string
		command?:       string | [...string]
		configs?: [...string | {
			source?: string
			target?: string
			uid?:    string
			gid?:    string
			mode?:   number

			{[=~"^x-" & !~"^(source|target|uid|gid|mode)$"]: _}
		}]
		container_name?: string
		cpu_count?:      int & >=0
		cpu_percent?:    int & >=0 & <=100
		cpu_shares?:     number | string
		cpu_quota?:      number | string
		cpu_period?:     number | string
		cpu_rt_period?:  number | string
		cpu_rt_runtime?: number | string
		cpus?:           number | string
		cpuset?:         string
		credential_spec?: {
			config?:   string
			file?:     string
			registry?: string

			{[=~"^x-" & !~"^(config|file|registry)$"]: _}
		}
		depends_on?: #list_of_strings | {
			{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: condition: "service_started" | "service_healthy" | "service_completed_successfully"}
		}
		device_cgroup_rules?: #list_of_strings
		devices?:             list.UniqueItems() & [...string]
		dns?:                 #string_or_list
		dns_opt?:             list.UniqueItems() & [...string]
		dns_search?:          #string_or_list
		domainname?:          string
		entrypoint?:          string | [...string]
		env_file?:            #string_or_list
		environment?:         #list_or_dict
		expose?:              list.UniqueItems() & [...number | string]
		extends?:             string | {
			service: string
			file?:   string
		}
		external_links?: list.UniqueItems() & [...string]
		extra_hosts?:    #list_or_dict
		group_add?:      list.UniqueItems() & [...number | string]
		healthcheck?:    #healthcheck
		hostname?:       string
		image?:          string
		init?:           bool
		ipc?:            string
		isolation?:      string
		labels?:         #list_or_dict
		links?:          list.UniqueItems() & [...string]
		logging?: {
			driver?: string
			options?: {
				{[=~"^.+$" & !~"^()$"]: null | number | string}
				...
			}

			{[=~"^x-" & !~"^(driver|options)$"]: _}
		}
		mac_address?:     string
		mem_limit?:       number | string
		mem_reservation?: int | string
		mem_swappiness?:  int
		memswap_limit?:   number | string
		network_mode?:    string
		networks?:        #list_of_strings | {
			{[=~"^[a-zA-Z0-9._-]+$" & !~"^()$"]: {
				aliases?: #list_of_strings, ipv4_address?: string, ipv6_address?: string, link_local_ips?: #list_of_strings, priority?: number

				{[=~"^x-" & !~"^(aliases|ipv4_address|ipv6_address|link_local_ips|priority)$"]: _}
			} | null
			}
		}
		oom_kill_disable?: bool
		oom_score_adj?:    int & >=-1000 & <=1000
		pid?:              null | string
		pids_limit?:       number | string
		platform?:         string
		ports?:            list.UniqueItems() & [...number | string | {
			mode?:      string
			host_ip?:   string
			target?:    int
			published?: int
			protocol?:  string

			{[=~"^x-" & !~"^(mode|host_ip|target|published|protocol)$"]: _}
		}]
		privileged?:   bool
		profiles?:     #list_of_strings
		pull_policy?:  "always" | "never" | "if_not_present" | "build" | "missing"
		read_only?:    bool
		restart?:      string
		runtime?:      string
		scale?:        int
		security_opt?: list.UniqueItems() & [...string]
		shm_size?:     number | string
		secrets?: [...string | {
			source?: string
			target?: string
			uid?:    string
			gid?:    string
			mode?:   number

			{[=~"^x-" & !~"^(source|target|uid|gid|mode)$"]: _}
		}]
		sysctls?:           #list_or_dict
		stdin_open?:        bool
		stop_grace_period?: string
		stop_signal?:       string
		storage_opt?: {
			...
		}
		tmpfs?: #string_or_list
		tty?:   bool
		ulimits?: {
			{[=~"^[a-z]+$" & !~"^()$"]: int | {
				hard: int, soft: int

				{[=~"^x-" & !~"^(hard|soft)$"]: _}
			}}
			...
		}
		user?:        string
		userns_mode?: string
		volumes?:     list.UniqueItems() & [...string | {
			type:         string
			source?:      string
			target?:      string
			read_only?:   bool
			consistency?: string
			bind?: {
				propagation?:      string
				create_host_path?: bool

				{[=~"^x-" & !~"^(propagation|create_host_path)$"]: _}
			}
			volume?: {
				nocopy?: bool

				{[=~"^x-" & !~"^(nocopy)$"]: _}
			}
			tmpfs?: {
				size?: int & >=0 | string

				{[=~"^x-" & !~"^(size)$"]: _}
			}

			{[=~"^x-" & !~"^(type|source|target|read_only|consistency|bind|volume|tmpfs)$"]: _}
		}]
		volumes_from?: list.UniqueItems() & [...string]
		working_dir?:  string

		{[=~"^x-" & !~"^(deploy|build|blkio_config|cap_add|cap_drop|cgroup_parent|command|configs|container_name|cpu_count|cpu_percent|cpu_shares|cpu_quota|cpu_period|cpu_rt_period|cpu_rt_runtime|cpus|cpuset|credential_spec|depends_on|device_cgroup_rules|devices|dns|dns_opt|dns_search|domainname|entrypoint|env_file|environment|expose|extends|external_links|extra_hosts|group_add|healthcheck|hostname|image|init|ipc|isolation|labels|links|logging|mac_address|mem_limit|mem_reservation|mem_swappiness|memswap_limit|network_mode|networks|oom_kill_disable|oom_score_adj|pid|pids_limit|platform|ports|privileged|profiles|pull_policy|read_only|restart|runtime|scale|security_opt|shm_size|secrets|sysctls|stdin_open|stop_grace_period|stop_signal|storage_opt|tmpfs|tty|ulimits|user|userns_mode|volumes|volumes_from|working_dir)$"]: _}
	}

	#healthcheck: {
		disable?:      bool
		interval?:     string
		retries?:      number
		test?:         string | [...string]
		timeout?:      string
		start_period?: string

		{[=~"^x-" & !~"^(disable|interval|retries|test|timeout|start_period)$"]: _}
	}

	#deployment: null | {
		mode?:          string
		endpoint_mode?: string
		replicas?:      int
		labels?:        #list_or_dict
		rollback_config?: {
			parallelism?:       int
			delay?:             string
			failure_action?:    string
			monitor?:           string
			max_failure_ratio?: number
			order?:             "start-first" | "stop-first"

			{[=~"^x-" & !~"^(parallelism|delay|failure_action|monitor|max_failure_ratio|order)$"]: _}
		}
		update_config?: {
			parallelism?:       int
			delay?:             string
			failure_action?:    string
			monitor?:           string
			max_failure_ratio?: number
			order?:             "start-first" | "stop-first"

			{[=~"^x-" & !~"^(parallelism|delay|failure_action|monitor|max_failure_ratio|order)$"]: _}
		}
		resources?: {
			limits?: {
				cpus?:   number | string
				memory?: string

				{[=~"^x-" & !~"^(cpus|memory)$"]: _}
			}
			reservations?: {
				cpus?:              number | string
				memory?:            string
				generic_resources?: #generic_resources
				devices?:           #devices

				{[=~"^x-" & !~"^(cpus|memory|generic_resources|devices)$"]: _}
			}

			{[=~"^x-" & !~"^(limits|reservations)$"]: _}
		}
		restart_policy?: {
			condition?:    string
			delay?:        string
			max_attempts?: int
			window?:       string

			{[=~"^x-" & !~"^(condition|delay|max_attempts|window)$"]: _}
		}
		placement?: {
			constraints?: [...string]
			preferences?: [...{
				spread?: string

				{[=~"^x-" & !~"^(spread)$"]: _}
			}]
			max_replicas_per_node?: int

			{[=~"^x-" & !~"^(constraints|preferences|max_replicas_per_node)$"]: _}
		}

		{[=~"^x-" & !~"^(mode|endpoint_mode|replicas|labels|rollback_config|update_config|resources|restart_policy|placement)$"]: _}
	}

	#generic_resources: [...{
		discrete_resource_spec?: {
			kind?:  string
			value?: number

			{[=~"^x-" & !~"^(kind|value)$"]: _}
		}

		{[=~"^x-" & !~"^(discrete_resource_spec)$"]: _}
	}]

	#devices: [...{
		capabilities?: #list_of_strings
		count?:        int | string
		device_ids?:   #list_of_strings
		driver?:       string
		options?:      #list_or_dict

		{[=~"^x-" & !~"^(capabilities|count|device_ids|driver|options)$"]: _}
	}]

	#network: null | {
		name?:   string
		driver?: string
		driver_opts?: {
			{[=~"^.+$" & !~"^()$"]: number | string}
			...
		}
		ipam?: {
			driver?: string
			config?: [...{
				subnet?:   string
				ip_range?: string
				gateway?:  string
				aux_addresses?: {
					{[=~"^.+$" & !~"^()$"]: string}
				}

				{[=~"^x-" & !~"^(subnet|ip_range|gateway|aux_addresses)$"]: _}
			}]
			options?: {
				{[=~"^.+$" & !~"^()$"]: string}
			}

			{[=~"^x-" & !~"^(driver|config|options)$"]: _}
		}
		external?: bool | {
			name?: string @deprecated()

			{[=~"^x-" & !~"^(name)$"]: _}
		}
		internal?:    bool
		enable_ipv6?: bool
		attachable?:  bool
		labels?:      #list_or_dict

		{[=~"^x-" & !~"^(name|driver|driver_opts|ipam|external|internal|enable_ipv6|attachable|labels)$"]: _}
	}

	#volume: null | {
		name?:   string
		driver?: string
		driver_opts?: {
			{[=~"^.+$" & !~"^()$"]: number | string}
			...
		}
		external?: bool | {
			name?: string @deprecated()

			{[=~"^x-" & !~"^(name)$"]: _}
		}
		labels?: #list_or_dict

		{[=~"^x-" & !~"^(name|driver|driver_opts|external|labels)$"]: _}
	}

	#secret: {
		name?:     string
		file?:     string
		external?: bool | {
			name?: string
			...
		}
		labels?: #list_or_dict
		driver?: string
		driver_opts?: {
			{[=~"^.+$" & !~"^()$"]: number | string}
			...
		}
		template_driver?: string

		{[=~"^x-" & !~"^(name|file|external|labels|driver|driver_opts|template_driver)$"]: _}
	}

	#config: {
		name?:     string
		file?:     string
		external?: bool | {
			name?: string @deprecated()
			...
		}
		labels?:          #list_or_dict
		template_driver?: string

		{[=~"^x-" & !~"^(name|file|external|labels|template_driver)$"]: _}
	}

	#string_or_list: string | #list_of_strings

	#list_of_strings: list.UniqueItems() & [...string]

	#list_or_dict: {
		{[=~".+" & !~"^()$"]: null | bool | number | string}
	} | list.UniqueItems() & [...string]

	#blkio_limit: {
		path?: string
		rate?: int | string
	}

	#blkio_weight: {
		path?:   string
		weight?: int
	}

	#constraints: _
}

用 CUE 进行验证

现在可以像在 验证配置 中一样验证 docker-compose.yaml

在这里,我们要用 -d 参数指定验证的结构。

cue vet -d '#ComposeSpec' docker-compose.yaml compose-spec.cue

缺点

通过 JSON Schema 生成 CUE 非常简单,然而,生成的 CUE 并不是有总是创建和验证数据的最佳结构。

这主要是因为原始结构未指定或有不寻常的类型。

因此,你可能会发现自己在不断地构建和完善导入的结构定义。

以下内容来自上面生成的 docker-compose:

缺少规范

有的时候结构定义里面没有增加验证,我们知道对于 docker-compose 有固定的的格式和有效版本,但在代码中并没有:

#version?: string
任意字段类型

对于更多复杂的字段,我们可以使用 _top),但是这样对字段没有任何的校验。

当 Go 有自定义编码函数类型,进行导入的时候经常会遇到。

#constraints: _
重复

如果原始定义中没有,CUE 很难做到检测代码重复和可复用的结构。

下面的例子中 CUE 有很多冗余的代码,这也会发生在一些比较顶级使用了 regexp 的字段,比如 service 和 volume。

duplication.cue

#secret: {
	name?:     string
	file?:     string
	external?: bool | {
		name?: string
		...
	}
	labels?: #list_or_dict
	driver?: string
	driver_opts?: {
		{[=~"^.+$" & !~"^()$"]: number | string}
		...
	}
	template_driver?: string

	{[=~"^x-" & !~"^(name|file|external|labels|driver|driver_opts|template_driver)$"]: _}
}

#config: {
	name?:     string
	file?:     string
	external?: bool | {
		name?: string @deprecated()
		...
	}
	labels?:          #list_or_dict
	template_driver?: string

	{[=~"^x-" & !~"^(name|file|external|labels|template_driver)$"]: _}
}

shared.cue

#shared: {
	name?:     string
	file?:     string
	external?: bool | {
		name?: string
		...
	}
	labels?:          #list_or_dict
	template_driver?: string
}

improved.cue

#secret: {
	#shared

	driver?: string
	driver_opts?: {
		{[=~"^.+$" & !~"^()$"]: number | string}
		...
	}

	{[=~"^x-" & !~"^(name|file|external|labels|driver|driver_opts|template_driver)$"]: _}
}

#config: {
	#shared

	{[=~"^x-" & !~"^(name|file|external|labels|template_driver)$"]: _}
}
我们绝不会将你的邮箱分享给任何人。
2022 Hofstadter, Inc