cli/bpmetadata/validate.go (67 lines of code) (raw):
package bpmetadata
import (
_ "embed"
"fmt"
"os"
"path"
"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/cli/util"
"github.com/xeipuuv/gojsonschema"
"sigs.k8s.io/yaml"
)
//go:embed schema/gcp-blueprint-metadata.json
var s []byte
// validateMetadata validates the metadata files for the provided
// blueprint path. This validation occurs for top-level blueprint
// metadata and blueprints in the modules/ folder, if present
func validateMetadata(bpPath, wdPath string) error {
// load schema from the binary
schemaLoader := gojsonschema.NewStringLoader(string(s))
// check if the provided output path is relative
if !path.IsAbs(bpPath) {
bpPath = path.Join(wdPath, bpPath)
}
// We don't need to validate metadata under .terraform folders
skipDirsToValidate := []string{".terraform/"}
metadataFiles, err := util.FindFilesWithPattern(bpPath, `^metadata(?:.display)?.yaml$`, skipDirsToValidate)
if err != nil {
Log.Error("unable to read at: %s", bpPath, "err", err)
}
var vErrs []error
for _, f := range metadataFiles {
err = validateMetadataYaml(f, schemaLoader)
if err != nil {
vErrs = append(vErrs, err)
Log.Error("core metadata validation failed", "err", err)
}
}
if len(vErrs) > 0 {
return fmt.Errorf("metadata validation failed for at least one blueprint")
}
return nil
}
// validateMetadata validates an individual yaml file present at path "m"
func validateMetadataYaml(m string, schema gojsonschema.JSONLoader) error {
// prepare metadata for validation by converting it from YAML to JSON
mBytes, err := convertYamlToJson(m)
if err != nil {
return fmt.Errorf("yaml to json conversion failed for metadata at path %s. error: %w", m, err)
}
// load metadata from the path
yamlLoader := gojsonschema.NewStringLoader(string(mBytes))
// validate metadata against the schema
result, err := gojsonschema.Validate(schema, yamlLoader)
if err != nil {
return fmt.Errorf("metadata validation failed for %s. error: %w", m, err)
}
if !result.Valid() {
for _, e := range result.Errors() {
Log.Error("validation error", "err", e)
}
return fmt.Errorf("metdata validation failed for: %s", m)
}
Log.Info("metadata is valid", "path", m)
return nil
}
// prepares metadata bytes for validation since direct
// validation of YAML is not possible
func convertYamlToJson(m string) ([]byte, error) {
// read metadata for validation
b, err := os.ReadFile(m)
if err != nil {
return nil, fmt.Errorf("unable to read metadata at path %s. error: %w", m, err)
}
if len(b) == 0 {
return nil, fmt.Errorf("metadata contents can not be empty")
}
json, err := yaml.YAMLToJSON(b)
if err != nil {
return nil, fmt.Errorf("metadata contents are invalid: %s", err.Error())
}
return json, nil
}