package validate import ( "fmt" "net/url" "regexp" "strings" "time" "go.uber.org/multierr" wh "edge-infra.dev/pkg/f8n/warehouse" "edge-infra.dev/pkg/f8n/warehouse/pallet" "edge-infra.dev/pkg/lib/build/semver" ) var revisionHashRegex = "^[a-f0-9]{7,40}$" // Validators for required Pallet annotations var palletAnnotationValidators = map[string]AnnotationValidator{ wh.AnnotationKind: validatePalletKind, wh.AnnotationTeam: validateNotEmpty, wh.AnnotationRevision: validateRevision, wh.AnnotationSource: validateSource, wh.AnnotationVersion: validateVersion, wh.AnnotationCreated: validateCreated, wh.AnnotationVendor: validateNotEmpty, } // Validators for optional Pallet annotations var palletOptionalAnnotationValidators = map[string]AnnotationValidator{ wh.AnnotationTitle: validateNotEmpty, wh.AnnotationDescription: validateNotEmpty, wh.AnnotationDocumentation: validateDocumentation, wh.AnnotationRender: validateRender, wh.AnnotationCapabilities: validateCapabilities, } // Validators for required Pallet manifest annotations var palletManifestAnnotationValidators = map[string]AnnotationValidator{ wh.AnnotationKind: validateNotEmpty, wh.AnnotationRefName: validateNotEmpty, wh.AnnotationClusterProviders: validateClusterProviders, } // BuildInfo validates the components of pallet.BuildInfo prior to packer instantiation. func BuildInfo(bi pallet.BuildInfo) error { return multierr.Combine( validateSource(bi.Source), validateVersion(bi.Version), validateRevision(bi.Revision), validateCreated(bi.Created), ) } // com.ncr.warehouse.kind should be "pallet". func validatePalletKind(kind string) error { if kind != wh.PalletKind { return errInvalidPalletKindAnnotation } return nil } // org.opencontainers.image.revision should be the hash of the artifact. func validateRevision(revision string) error { match, _ := regexp.MatchString(revisionHashRegex, revision) if !match { return errInvalidRevisionAnnotation } return nil } // org.opencontainers.image.source should be the URL path to the relevant source code. func validateSource(sourceURL string) error { _, err := url.ParseRequestURI(sourceURL) if err != nil { return fmt.Errorf("%v: %v", errInvalidURLAnnotation, err) } return nil } // org.opencontainers.image.version should be a valid SemVer version. func validateVersion(version string) error { if err := semver.IsValidSemver(version); err != nil { return fmt.Errorf("%v: %v", errInvalidVersionAnnotation, err) } return nil } // org.opencontainers.image.created should be timestamp as defined by RFC3339. func validateCreated(created string) error { _, err := time.Parse(time.RFC3339, created) if err != nil { return fmt.Errorf("%v: %v", errInvalidTimestampAnnotation, err) } return nil } // org.opencontainers.image.documentation should be the URL path to the relevant documentation. func validateDocumentation(documentationURL string) error { _, err := url.ParseRequestURI(documentationURL) if err != nil { return fmt.Errorf("%v: %v", errInvalidURLAnnotation, err) } return nil } // com.ncr.warehouse.pallet.render should be either "true" or "false". func validateRender(render string) error { if !(render == "true" || render == "false") { return errInvalidBinaryAnnotation } return nil } // com.ncr.warehouse.pallet.capabilities should be a comma separated list of runtime capabilities. func validateCapabilities(capabilities string) error { match, _ := regexp.MatchString(csvListRegex, capabilities) if !match { return errInvalidListAnnotation } return nil } // Checks the annotation values for capabilities, providers and parameters are all present in the root node. func validatePalletAgainstRoot(annotations, rootAnnotations map[string]string) error { // validate all descriptor runtime capabilities are specified by root capabilities := strings.Split(annotations[wh.AnnotationCapabilities], ",") rootCapabilities := strings.Split(rootAnnotations[wh.AnnotationCapabilities], ",") for _, capability := range capabilities { if !isValueInRoot(capability, rootCapabilities) { return NewDescriptorError(wh.AnnotationCapabilities, capability, rootCapabilities) } } // validate all descriptor cluster providers are specified by root providers := strings.Split(annotations[wh.AnnotationClusterProviders], ",") rootProviders := strings.Split(rootAnnotations[wh.AnnotationClusterProviders], ",") for _, provider := range providers { if !isValueInRoot(provider, rootProviders) { return NewDescriptorError(wh.AnnotationClusterProviders, provider, rootProviders) } } // validate all descriptor parameters are specified by root parameters := strings.Split(annotations[wh.AnnotationParameters], ",") rootParameters := strings.Split(rootAnnotations[wh.AnnotationParameters], ",") for _, parameter := range parameters { if !isValueInRoot(parameter, rootParameters) { return NewDescriptorError(wh.AnnotationParameters, parameter, rootParameters) } } return nil } // Returns an error if a given argument is not one of the root arguments or root arguments are empty. func isValueInRoot(value string, rootValues []string) bool { if value == "" { return true } if len(rootValues) == 0 || rootValues[0] == "" { return false } for _, rootValue := range rootValues { if value == rootValue { return true } } return false }