github.com/zntrio/harp/v2@v2.0.9/pkg/tasks/lint/validate.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package lint 19 20 import ( 21 "bytes" 22 "context" 23 "fmt" 24 "io" 25 26 "github.com/xeipuuv/gojsonschema" 27 "sigs.k8s.io/yaml" 28 29 "github.com/zntrio/harp/v2/pkg/bundle" 30 "github.com/zntrio/harp/v2/pkg/bundle/patch" 31 "github.com/zntrio/harp/v2/pkg/bundle/ruleset" 32 "github.com/zntrio/harp/v2/pkg/bundle/template" 33 "github.com/zntrio/harp/v2/pkg/tasks" 34 ) 35 36 // ValidateTask implements input linter task. 37 type ValidateTask struct { 38 SourceReader tasks.ReaderProvider 39 OutputWriter tasks.WriterProvider 40 Schema string 41 SchemaOnly bool 42 } 43 44 type fileSpec struct { 45 APIVersion string `json:"apiVersion"` 46 Kind string `json:"kind"` 47 } 48 49 var schemaRegistry = map[string]struct { 50 Definition []byte 51 LintFunc func(io.Reader) ([]gojsonschema.ResultError, error) 52 }{ 53 "Bundle": {Definition: bundle.JSONSchema(), LintFunc: bundle.Lint}, 54 "BundlePatch": {Definition: patch.JSONSchema(), LintFunc: patch.Lint}, 55 "RuleSet": {Definition: ruleset.JSONSchema(), LintFunc: ruleset.Lint}, 56 "BundleTemplate": {Definition: template.JSONSchema(), LintFunc: template.Lint}, 57 } 58 59 // Run the task. 60 func (t *ValidateTask) Run(ctx context.Context) error { 61 var ( 62 reader io.Reader 63 err error 64 ) 65 66 // Create input reader 67 reader, err = t.SourceReader(ctx) 68 if err != nil { 69 return fmt.Errorf("unable to read input reader: %w", err) 70 } 71 72 // Drain input reader 73 payload, err := io.ReadAll(reader) 74 if err != nil { 75 return fmt.Errorf("unable to drain input reader: %w", err) 76 } 77 78 // Detect the appropriate schema 79 if t.Schema == "" { 80 // Decode as YAML any object 81 var specBody fileSpec 82 if errYaml := yaml.Unmarshal(payload, &specBody); errYaml != nil { 83 return fmt.Errorf("unable to decode spec as YAML: %w", err) 84 } 85 86 // Check API Version 87 if specBody.APIVersion != "harp.elastic.co/v1" { 88 return fmt.Errorf("unsupported YAML file format %q", specBody.APIVersion) 89 } 90 91 // Assign detected kind 92 t.Schema = specBody.Kind 93 } 94 95 // Select lint strategy 96 s, ok := schemaRegistry[t.Schema] 97 if !ok { 98 return fmt.Errorf("unsupported schema definition for %q", t.Schema) 99 } 100 101 // Create output writer 102 writer, err := t.OutputWriter(ctx) 103 if err != nil { 104 return fmt.Errorf("unable to open output bundle: %w", err) 105 } 106 107 // Display jsonschema 108 if t.SchemaOnly { 109 fmt.Fprintln(writer, string(s.Definition)) 110 return nil 111 } 112 113 // Execute the lint evaluation 114 validationErrors, err := s.LintFunc(bytes.NewReader(payload)) 115 switch { 116 case len(validationErrors) > 0: 117 for _, e := range validationErrors { 118 fmt.Fprintf(writer, " - %s\n", e.String()) 119 } 120 return err 121 case err != nil: 122 return fmt.Errorf("unexpected validation error occurred: %w", err) 123 default: 124 } 125 126 // No error 127 return nil 128 }