- package config
-
- import (
- "bytes"
- "context"
- "fmt"
- "path"
- "reflect"
- "regexp"
- "strings"
- "text/template"
-
- "github.com/docker/distribution/reference"
- "gopkg.in/go-playground/validator.v9"
- )
-
- var (
- // See Debian Policy
- // https://www.debian.org/doc/debian-policy/#s-f-source
- // https://www.debian.org/doc/debian-policy/#s-f-version
- debianPackageName = `[a-z0-9][a-z0-9+.-]+`
- debianVersionSpec = `(?:[0-9]+:)?[0-9]+[a-zA-Z0-9\.\+\-~]*`
- debianReleaseName = `[a-zA-Z](?:[a-zA-Z0-9\-]*[a-zA-Z0-9]+)?`
- debianPackageRegexp = regexp.MustCompile(fmt.Sprintf(
- `^%s(?:=%s|/%s)?$`, debianPackageName, debianVersionSpec, debianReleaseName))
-
- // See IEEE Std 1003.1-2008 (http://pubs.opengroup.org/onlinepubs/9699919799/)
- environmentVariableRegexp = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]+$`)
-
- // Pattern for valid variant names
- variantNameRegexp = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$`)
-
- humanizedErrors = map[string]string{
- "abspath": `{{.Field}}: "{{.Value}}" is not a valid absolute non-root path`,
- "baseimage": `{{.Field}}: "{{.Value}}" is not a valid base image reference`,
- "currentversion": `{{.Field}}: config version "{{.Value}}" is unsupported`,
- "debianpackage": `{{.Field}}: "{{.Value}}" is not a valid Debian package name`,
- "envvars": `{{.Field}}: contains invalid environment variable names`,
- "nodeenv": `{{.Field}}: "{{.Value}}" is not a valid Node environment name`,
- "relativelocal": `{{.Field}}: path must be relative when "from" is "local"`,
- "required": `{{.Field}}: is required`,
- "requiredwith": `{{.Field}}: is required if "{{.Param}}" is also set`,
- "unique": `{{.Field}}: cannot contain duplicates`,
- "username": `{{.Field}}: "{{.Value}}" is not a valid user name`,
- "variantref": `{{.Field}}: references an unknown variant "{{.Value}}"`,
- "variants": `{{.Field}}: contains a bad variant name`,
- }
-
- validatorAliases = map[string]string{
- "currentversion": "eq=" + CurrentVersion,
- "nodeenv": "alphanum",
- "username": "hostname,ne=root",
- }
-
- validatorFuncs = map[string]validator.FuncCtx{
- "abspath": isAbsNonRootPath,
- "baseimage": isBaseImage,
- "debianpackage": isDebianPackage,
- "envvars": isEnvironmentVariables,
- "isfalse": isFalse,
- "istrue": isTrue,
- "relativelocal": isRelativePathForLocalArtifact,
- "requiredwith": isSetIfOtherFieldIsSet,
- "variantref": isVariantReference,
- "variants": hasVariantNames,
- }
- )
-
- type ctxKey uint8
-
- const rootCfgCtx ctxKey = iota
-
- // newValidator returns a validator instance for which our custom aliases and
- // functions are registered.
- //
- func newValidator() *validator.Validate {
- validate := validator.New()
-
- validate.RegisterTagNameFunc(resolveJSONTagName)
-
- for name, tags := range validatorAliases {
- validate.RegisterAlias(name, tags)
- }
-
- for name, f := range validatorFuncs {
- validate.RegisterValidationCtx(name, f)
- }
-
- return validate
- }
-
- // Validate runs all validations defined for config fields against the given
- // Config value. If the returned error is not nil, it will contain a
- // user-friendly message describing all invalid field values.
- //
- func Validate(config interface{}) error {
- validate := newValidator()
-
- ctx := context.WithValue(context.Background(), rootCfgCtx, config)
-
- return validate.StructCtx(ctx, config)
- }
-
- // HumanizeValidationError transforms the given validator.ValidationErrors
- // into messages more likely to be understood by human beings.
- //
- func HumanizeValidationError(err error) string {
- var message bytes.Buffer
-
- if err == nil {
- return ""
- } else if !IsValidationError(err) {
- return err.Error()
- }
-
- templates := map[string]*template.Template{}
-
- for name, tmplString := range humanizedErrors {
- if tmpl, err := template.New(name).Parse(tmplString); err == nil {
- templates[name] = tmpl
- }
- }
-
- for _, ferr := range err.(validator.ValidationErrors) {
- if tmpl, ok := templates[ferr.Tag()]; ok {
- tmpl.Execute(&message, ferr)
- } else if trueErr, ok := err.(error); ok {
- message.WriteString(trueErr.Error())
- }
-
- message.WriteString("\n")
- }
-
- return strings.TrimSpace(message.String())
- }
-
- // IsValidationError tests whether the given error is a
- // validator.ValidationErrors and can be safely iterated over as such.
- //
- func IsValidationError(err error) bool {
- if err == nil {
- return false
- } else if _, ok := err.(*validator.InvalidValidationError); ok {
- return false
- } else if _, ok := err.(validator.ValidationErrors); ok {
- return true
- }
-
- return false
- }
-
- func hasVariantNames(_ context.Context, fl validator.FieldLevel) bool {
- for _, name := range fl.Field().MapKeys() {
- if !variantNameRegexp.MatchString(name.String()) {
- return false
- }
- }
-
- return true
- }
-
- func isAbsNonRootPath(_ context.Context, fl validator.FieldLevel) bool {
- value := fl.Field().String()
-
- return path.IsAbs(value) && path.Base(path.Clean(value)) != "/"
- }
-
- func isBaseImage(_ context.Context, fl validator.FieldLevel) bool {
- value := fl.Field().String()
-
- return reference.ReferenceRegexp.MatchString(value)
- }
-
- func isDebianPackage(_ context.Context, fl validator.FieldLevel) bool {
- value := fl.Field().String()
-
- return debianPackageRegexp.MatchString(value)
- }
-
- func isEnvironmentVariables(_ context.Context, fl validator.FieldLevel) bool {
- for _, key := range fl.Field().MapKeys() {
- if !environmentVariableRegexp.MatchString(key.String()) {
- return false
- }
- }
-
- return true
- }
-
- func isFalse(_ context.Context, fl validator.FieldLevel) bool {
- val, ok := fl.Field().Interface().(bool)
-
- return ok && val == false
- }
-
- func isRelativePathForLocalArtifact(_ context.Context, fl validator.FieldLevel) bool {
- value := fl.Field().String()
- from := fl.Parent().FieldByName("From").String()
-
- if value == "" || from != LocalArtifactKeyword {
- return true
- }
-
- // path must be relative and do no "../" funny business
- return !(path.IsAbs(value) || strings.HasPrefix(path.Clean(value), ".."))
- }
-
- func isSetIfOtherFieldIsSet(_ context.Context, fl validator.FieldLevel) bool {
- if otherField, err := ResolveJSONPath(fl.Param(), fl.Parent().Interface()); err == nil {
- return isZeroValue(reflect.ValueOf(otherField)) || !isZeroValue(fl.Field())
- }
-
- return false
- }
-
- func isTrue(_ context.Context, fl validator.FieldLevel) bool {
- val, ok := fl.Field().Interface().(bool)
-
- return ok && val == true
- }
-
- func isVariantReference(ctx context.Context, fl validator.FieldLevel) bool {
- cfg := ctx.Value(rootCfgCtx).(Config)
- ref := fl.Field().String()
-
- if ref == LocalArtifactKeyword {
- return true
- }
-
- for name := range cfg.Variants {
- if name == ref {
- return true
- }
- }
-
- return false
- }
-
- func isZeroValue(v reflect.Value) bool {
- return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
- }
-
- func resolveJSONTagName(field reflect.StructField) string {
- return strings.SplitN(field.Tag.Get("json"), ",", 2)[0]
- }
|