179 lines
6.7 KiB
Go
179 lines
6.7 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var idPattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]*$`)
|
|
|
|
type ValidationErrors []string
|
|
|
|
func (e ValidationErrors) Error() string {
|
|
if len(e) == 1 {
|
|
return e[0]
|
|
}
|
|
return strings.Join(e, "; ")
|
|
}
|
|
|
|
func Validate(cfg Config) error {
|
|
var errs ValidationErrors
|
|
|
|
if len(cfg.Pipelines) == 0 {
|
|
errs = append(errs, "pipelines is required")
|
|
}
|
|
|
|
pipelineIDs := make(map[string]struct{}, len(cfg.Pipelines))
|
|
for pipelineIndex, pipeline := range cfg.Pipelines {
|
|
pipelineContext := fmt.Sprintf("pipelines[%d]", pipelineIndex)
|
|
if pipeline.ID == "" {
|
|
errs = append(errs, pipelineContext+".id is required")
|
|
} else if !idPattern.MatchString(pipeline.ID) {
|
|
errs = append(errs, pipelineContext+".id must be a slug-like identifier")
|
|
} else if _, exists := pipelineIDs[pipeline.ID]; exists {
|
|
errs = append(errs, "pipeline id "+pipeline.ID+" is duplicated")
|
|
} else {
|
|
pipelineIDs[pipeline.ID] = struct{}{}
|
|
}
|
|
|
|
errs = validateSourceBackend(errs, pipelineContext+".source", pipeline.Source)
|
|
errs = validateValidationPolicy(errs, pipelineContext+".validation", pipeline.Validation)
|
|
if len(pipeline.Destinations) == 0 {
|
|
errs = append(errs, pipelineContext+".destinations is required")
|
|
}
|
|
|
|
destinationIDs := make(map[string]struct{}, len(pipeline.Destinations))
|
|
for destinationIndex, destination := range pipeline.Destinations {
|
|
destinationContext := fmt.Sprintf("%s.destinations[%d]", pipelineContext, destinationIndex)
|
|
if destination.ID == "" {
|
|
errs = append(errs, destinationContext+".id is required")
|
|
} else if !idPattern.MatchString(destination.ID) {
|
|
errs = append(errs, destinationContext+".id must be a slug-like identifier")
|
|
} else if _, exists := destinationIDs[destination.ID]; exists {
|
|
errs = append(errs, "destination id "+destination.ID+" is duplicated in pipeline "+pipeline.ID)
|
|
} else {
|
|
destinationIDs[destination.ID] = struct{}{}
|
|
}
|
|
|
|
errs = validateDestinationBackend(errs, destinationContext, destination)
|
|
errs = validatePublishTransformPolicy(errs, destinationContext, destination.Publish, destination.Transform)
|
|
errs = validateTransferPolicy(errs, destinationContext+".transfer", destination.Transfer)
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errs
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateSourceBackend(errs ValidationErrors, context string, backend Backend) ValidationErrors {
|
|
return validateBackend(errs, context, backend.Backend, backend.Host, backend.Port, backend.Path, backend.URI, backend.Endpoint, backend.Bucket, backend.SSH.HostKeyPolicy)
|
|
}
|
|
|
|
func validateDestinationBackend(errs ValidationErrors, context string, destination Destination) ValidationErrors {
|
|
return validateBackend(errs, context, destination.Backend, destination.Host, destination.Port, destination.Path, destination.URI, destination.Endpoint, destination.Bucket, destination.SSH.HostKeyPolicy)
|
|
}
|
|
|
|
func validateBackend(errs ValidationErrors, context, backend, host string, port int, path, uri, endpoint, bucket string, hostKeyPolicy HostKeyPolicy) ValidationErrors {
|
|
switch backend {
|
|
case "":
|
|
errs = append(errs, context+".backend is required")
|
|
case BackendLocal:
|
|
if path == "" {
|
|
errs = append(errs, context+".path is required for local backend")
|
|
}
|
|
case BackendSSH:
|
|
if host == "" {
|
|
errs = append(errs, context+".host is required for ssh backend")
|
|
}
|
|
if path == "" {
|
|
errs = append(errs, context+".path is required for ssh backend")
|
|
}
|
|
if uri != "" {
|
|
errs = append(errs, context+".uri is not supported for ssh backend; use host, user, port, and path")
|
|
}
|
|
if port < 0 || port > 65535 {
|
|
errs = append(errs, context+".port must be between 1 and 65535")
|
|
}
|
|
if port == 0 {
|
|
errs = append(errs, context+".port is required for ssh backend after defaults are applied")
|
|
}
|
|
if hostKeyPolicy != "" {
|
|
if _, ok := NormalizeHostKeyPolicy(string(hostKeyPolicy)); !ok {
|
|
errs = append(errs, context+".host_key_policy must be strict, true, accept-new, off, or false")
|
|
}
|
|
}
|
|
case BackendS3:
|
|
if endpoint == "" {
|
|
errs = append(errs, context+".endpoint is required for s3 backend")
|
|
}
|
|
if bucket == "" {
|
|
errs = append(errs, context+".bucket is required for s3 backend")
|
|
}
|
|
default:
|
|
errs = append(errs, context+".backend "+backend+" is unsupported")
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func validateValidationPolicy(errs ValidationErrors, context string, policy ValidationPolicy) ValidationErrors {
|
|
if policy.OnDigestMismatch != ValidationActionFail {
|
|
errs = append(errs, context+".on_digest_mismatch must be "+ValidationActionFail)
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func validatePublishTransformPolicy(errs ValidationErrors, context string, policy *PublishPolicy, transform Transform) ValidationErrors {
|
|
if policy == nil {
|
|
errs = append(errs, context+".publish is required")
|
|
return errs
|
|
}
|
|
if err := ValidatePublishTransformPolicy(*policy, transform); err != nil {
|
|
errs = append(errs, context+"."+err.Error())
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func ValidatePublishTransformPolicy(publish PublishPolicy, transform Transform) error {
|
|
if !publish.Source && !publish.HTML {
|
|
return fmt.Errorf("publish must enable source or html")
|
|
}
|
|
if publish.HTML && transform.MarkdownToHTML == nil {
|
|
return fmt.Errorf("transform.markdown_to_html is required when publish.html is true")
|
|
}
|
|
if transform.MarkdownToHTML == nil {
|
|
return nil
|
|
}
|
|
if publish.HTML && !transform.MarkdownToHTML.Enabled {
|
|
return fmt.Errorf("transform.markdown_to_html.enabled must be true when publish.html is true")
|
|
}
|
|
if publish.HTML && transform.MarkdownToHTML.Mode != TransformModeSidecar {
|
|
return fmt.Errorf("transform.markdown_to_html.mode must be %s", TransformModeSidecar)
|
|
}
|
|
if transform.MarkdownToHTML.Enabled && transform.MarkdownToHTML.Mode != TransformModeSidecar {
|
|
return fmt.Errorf("transform.markdown_to_html.mode must be %s", TransformModeSidecar)
|
|
}
|
|
if !transform.MarkdownToHTML.Enabled && transform.MarkdownToHTML.Mode != "" && transform.MarkdownToHTML.Mode != TransformModeSidecar {
|
|
return fmt.Errorf("transform.markdown_to_html.mode must be %s", TransformModeSidecar)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateTransferPolicy(errs ValidationErrors, context string, policy TransferPolicy) ValidationErrors {
|
|
if policy.OnDestinationSame != TransferActionSkip && policy.OnDestinationSame != TransferActionFail {
|
|
errs = append(errs, context+".on_destination_same must be skip or fail")
|
|
}
|
|
if policy.OnDestinationOlder != TransferActionReplace && policy.OnDestinationOlder != TransferActionFail {
|
|
errs = append(errs, context+".on_destination_older must be replace or fail")
|
|
}
|
|
if policy.OnDestinationNewer != TransferActionSkip && policy.OnDestinationNewer != TransferActionFail {
|
|
errs = append(errs, context+".on_destination_newer must be skip or fail")
|
|
}
|
|
if policy.OnConflict != TransferActionFail {
|
|
errs = append(errs, context+".on_conflict must be fail")
|
|
}
|
|
return errs
|
|
}
|