Golang中如何验证YAML文件的Schema

Golang中如何验证YAML文件的Schema 我正在尝试验证一个YAML模式,它看起来很像Kubernetes部署的YAML文件,但并不完全相同。

version: v1
kind: Extension
metadata:
  name: 
  vendor-id:
  version:
artifacts:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - name: http
      containerPort: 80 
    volumeMounts:
    - mountPath: /test-ebs
      name: http-volume
  bundle:
  - name: my-bundle
    path: 
  web:
  - name: 
    path:

字段 containersbundleweb 是可选的,但其中至少一个必须存在。我在Stack Overflow上找到了这个讨论,但它看起来已经是三年半以前的了。我想知道现在是否有任何新的或更好的方法来实现这个功能。

谢谢


更多关于Golang中如何验证YAML文件的Schema的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

你链接的帖子在我看来是正确的:

  • 将你的模型定义为一个结构体
  • 将数据反序列化到该模型中
  • 编写代码来检查反序列化的结果,以验证其是否有效。

更多关于Golang中如何验证YAML文件的Schema的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在此期间,我查看了 compose-gokubeval。看起来它们是在读取 YAML 文件,将其转换为 JSON,然后再使用 JSON 验证器。

补充一下 @skillian 所说的内容,一个很好的起点可能是将你的 YAML 输入到 https://zhwt.github.io/yaml-to-go/,这会生成以下代码:

type AutoGenerated struct {
	Version  string `yaml:"version"`
	Kind     string `yaml:"kind"`
	Metadata struct {
		Name     interface{} `yaml:"name"`
		VendorID interface{} `yaml:"vendor-id"`
		Version  interface{} `yaml:"version"`
	} `yaml:"metadata"`
	Artifacts struct {
		Containers []struct {
			Name  string `yaml:"name"`
			Image string `yaml:"image"`
			Ports []struct {
				Name          string `yaml:"name"`
				ContainerPort int    `yaml:"containerPort"`
			} `yaml:"ports"`
			VolumeMounts []struct {
				MountPath string `yaml:"mountPath"`
				Name      string `yaml:"name"`
			} `yaml:"volumeMounts"`
		} `yaml:"containers"`
		Bundle []struct {
			Name string      `yaml:"name"`
			Path interface{} `yaml:"path"`
		} `yaml:"bundle"`
		Web []struct {
			Name interface{} `yaml:"name"`
			Path interface{} `yaml:"path"`
		} `yaml:"web"`
	} `yaml:"artifacts"`
}

以此作为起点,并硬编码 YAML:

myYaml := []byte(`version: v1
kind: Extension
metadata:
  name: 
  vendor-id:
  version:
artifacts:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - name: http
      containerPort: 80 
    volumeMounts:
    - mountPath: /test-ebs
      name: http-volume
  bundle:
  - name: my-bundle
    path: 
  web:
  - name: 
    path:
`)

你可以使用你 StackOverflow 链接中提到的库来解析你的 YAML,然后对其进行任何你想要的检查:

var myConfig AutoGenerated
err := yaml.Unmarshal(myYaml, &myConfig)
if err != nil {
	panic(err)
}

// 进行任何你想要的检查
fmt.Println(myConfig.Artifacts.Containers[0].Name, "Is a valid config option for container name")

这显然是人为设计的,并且不太安全。但它应该能让你开始。你可以在 Go Playground 上运行这个示例,并编辑它以满足你的实际需求。

在Go中验证YAML文件的Schema,目前有几种主流方法。以下是针对你具体需求的解决方案:

1. 使用go-playground/validator(推荐)

这是当前最流行的验证库,支持结构体标签验证:

package main

import (
	"fmt"
	"gopkg.in/yaml.v3"
	"github.com/go-playground/validator/v10"
)

type Extension struct {
	Version  string     `yaml:"version" validate:"required,eq=v1"`
	Kind     string     `yaml:"kind" validate:"required,eq=Extension"`
	Metadata Metadata   `yaml:"metadata" validate:"required"`
	Artifacts *Artifacts `yaml:"artifacts,omitempty" validate:"required"`
}

type Metadata struct {
	Name      string `yaml:"name" validate:"required"`
	VendorID  string `yaml:"vendor-id" validate:"required"`
	Version   string `yaml:"version" validate:"required"`
}

type Artifacts struct {
	Containers []Container `yaml:"containers,omitempty"`
	Bundle     []Bundle    `yaml:"bundle,omitempty"`
	Web        []Web       `yaml:"web,omitempty"`
}

type Container struct {
	Name         string        `yaml:"name" validate:"required"`
	Image        string        `yaml:"image" validate:"required"`
	Ports        []Port        `yaml:"ports,omitempty"`
	VolumeMounts []VolumeMount `yaml:"volumeMounts,omitempty"`
}

type Bundle struct {
	Name string `yaml:"name" validate:"required"`
	Path string `yaml:"path" validate:"required"`
}

type Web struct {
	Name string `yaml:"name" validate:"required"`
	Path string `yaml:"path" validate:"required"`
}

type Port struct {
	Name          string `yaml:"name" validate:"required"`
	ContainerPort int    `yaml:"containerPort" validate:"required,min=1,max=65535"`
}

type VolumeMount struct {
	MountPath string `yaml:"mountPath" validate:"required"`
	Name      string `yaml:"name" validate:"required"`
}

// 自定义验证函数:至少一个字段存在
func validateAtLeastOne(fl validator.FieldLevel) bool {
	artifacts, ok := fl.Field().Interface().(Artifacts)
	if !ok {
		return false
	}
	
	return len(artifacts.Containers) > 0 || 
	       len(artifacts.Bundle) > 0 || 
	       len(artifacts.Web) > 0
}

func main() {
	validate := validator.New()
	validate.RegisterValidation("atleastone", validateAtLeastOne)
	
	yamlData := `
version: v1
kind: Extension
metadata:
  name: my-extension
  vendor-id: acme
  version: 1.0.0
artifacts:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - name: http
      containerPort: 80
    volumeMounts:
    - mountPath: /test-ebs
      name: http-volume
`
	
	var ext Extension
	err := yaml.Unmarshal([]byte(yamlData), &ext)
	if err != nil {
		fmt.Printf("YAML解析错误: %v\n", err)
		return
	}
	
	err = validate.Struct(ext)
	if err != nil {
		fmt.Printf("验证失败: %v\n", err)
		return
	}
	
	fmt.Println("YAML Schema验证通过")
}

2. 使用自定义验证方法

如果你需要更复杂的逻辑,可以实现自定义验证:

package main

import (
	"errors"
	"fmt"
	"gopkg.in/yaml.v3"
)

func (a *Artifacts) Validate() error {
	if len(a.Containers) == 0 && len(a.Bundle) == 0 && len(a.Web) == 0 {
		return errors.New("artifacts中必须至少包含containers、bundle或web中的一个")
	}
	
	// 验证containers
	for i, container := range a.Containers {
		if container.Name == "" {
			return fmt.Errorf("containers[%d].name不能为空", i)
		}
		if container.Image == "" {
			return fmt.Errorf("containers[%d].image不能为空", i)
		}
	}
	
	// 验证bundle
	for i, bundle := range a.Bundle {
		if bundle.Name == "" {
			return fmt.Errorf("bundle[%d].name不能为空", i)
		}
		if bundle.Path == "" {
			return fmt.Errorf("bundle[%d].path不能为空", i)
		}
	}
	
	// 验证web
	for i, web := range a.Web {
		if web.Name == "" {
			return fmt.Errorf("web[%d].name不能为空", i)
		}
		if web.Path == "" {
			return fmt.Errorf("web[%d].path不能为空", i)
		}
	}
	
	return nil
}

func (e *Extension) Validate() error {
	if e.Version != "v1" {
		return errors.New("version必须是v1")
	}
	if e.Kind != "Extension" {
		return errors.New("kind必须是Extension")
	}
	if e.Metadata.Name == "" {
		return errors.New("metadata.name不能为空")
	}
	if e.Metadata.VendorID == "" {
		return errors.New("metadata.vendor-id不能为空")
	}
	if e.Metadata.Version == "" {
		return errors.New("metadata.version不能为空")
	}
	
	if e.Artifacts == nil {
		return errors.New("artifacts不能为空")
	}
	
	return e.Artifacts.Validate()
}

func main() {
	yamlData := `
version: v1
kind: Extension
metadata:
  name: test
  vendor-id: vendor
  version: 1.0
artifacts:
  web:
  - name: my-web
    path: /path/to/web
`
	
	var ext Extension
	err := yaml.Unmarshal([]byte(yamlData), &ext)
	if err != nil {
		fmt.Printf("YAML解析错误: %v\n", err)
		return
	}
	
	err = ext.Validate()
	if err != nil {
		fmt.Printf("验证失败: %v\n", err)
		return
	}
	
	fmt.Println("验证通过")
}

3. 使用JSON Schema验证(通过YAML转换)

package main

import (
	"fmt"
	"github.com/santhosh-tekuri/jsonschema/v5"
	"gopkg.in/yaml.v3"
)

func main() {
	// JSON Schema定义
	schemaJSON := `{
		"$schema": "http://json-schema.org/draft-07/schema#",
		"type": "object",
		"required": ["version", "kind", "metadata", "artifacts"],
		"properties": {
			"version": {"type": "string", "const": "v1"},
			"kind": {"type": "string", "const": "Extension"},
			"metadata": {
				"type": "object",
				"required": ["name", "vendor-id", "version"],
				"properties": {
					"name": {"type": "string"},
					"vendor-id": {"type": "string"},
					"version": {"type": "string"}
				}
			},
			"artifacts": {
				"type": "object",
				"oneOf": [
					{"required": ["containers"]},
					{"required": ["bundle"]},
					{"required": ["web"]}
				],
				"properties": {
					"containers": {
						"type": "array",
						"items": {
							"type": "object",
							"required": ["name", "image"],
							"properties": {
								"name": {"type": "string"},
								"image": {"type": "string"}
							}
						}
					},
					"bundle": {
						"type": "array",
						"items": {
							"type": "object",
							"required": ["name", "path"],
							"properties": {
								"name": {"type": "string"},
								"path": {"type": "string"}
							}
						}
					},
					"web": {
						"type": "array",
						"items": {
							"type": "object",
							"required": ["name", "path"],
							"properties": {
								"name": {"type": "string"},
								"path": {"type": "string"}
							}
						}
					}
				}
			}
		}
	}`

	// 编译schema
	compiler := jsonschema.NewCompiler()
	if err := compiler.AddResource("schema.json", []byte(schemaJSON)); err != nil {
		panic(err)
	}
	schema, err := compiler.Compile("schema.json")
	if err != nil {
		panic(err)
	}

	// YAML数据
	yamlData := `
version: v1
kind: Extension
metadata:
  name: test
  vendor-id: vendor
  version: 1.0
artifacts:
  containers:
  - name: nginx
    image: nginx:1.14.2
`

	// 将YAML转换为interface{}用于验证
	var data interface{}
	err = yaml.Unmarshal([]byte(yamlData), &data)
	if err != nil {
		fmt.Printf("YAML解析错误: %v\n", err)
		return
	}

	// 验证
	if err := schema.Validate(data); err != nil {
		fmt.Printf("Schema验证失败: %v\n", err)
		return
	}

	fmt.Println("YAML Schema验证通过")
}

总结

  1. go-playground/validator:当前最流行的选择,支持丰富的验证标签和自定义验证函数
  2. 自定义验证方法:提供最大的灵活性,适合复杂业务逻辑
  3. JSON Schema:适合已有JSON Schema或需要跨语言验证的场景

对于你的需求(至少一个字段存在),推荐使用第一种方法配合自定义验证函数,或者第二种自定义验证方法。这些方案都比三年前Stack Overflow讨论中的方法更现代和强大。

回到顶部