github.com/Jeffail/benthos/v3@v3.65.0/internal/config/stream_reader.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 "time" 10 11 "github.com/Jeffail/benthos/v3/internal/bundle" 12 "github.com/Jeffail/benthos/v3/internal/docs" 13 ifilepath "github.com/Jeffail/benthos/v3/internal/filepath" 14 "github.com/Jeffail/benthos/v3/lib/config" 15 "github.com/Jeffail/benthos/v3/lib/stream" 16 "gopkg.in/yaml.v3" 17 ) 18 19 // InferStreamID attempts to infer a stream identifier from a file path and 20 // containing directory. If the dir field is non-empty then the identifier will 21 // include all sub-directories in the path as an id prefix, this means loading 22 // streams with the same file name from different branches are still given 23 // unique names. 24 func InferStreamID(dir, path string) (string, error) { 25 var id string 26 if len(dir) > 0 { 27 var err error 28 if id, err = filepath.Rel(dir, path); err != nil { 29 return "", err 30 } 31 } else { 32 id = filepath.Base(path) 33 } 34 35 id = strings.Trim(id, string(filepath.Separator)) 36 id = strings.TrimSuffix(id, ".yaml") 37 id = strings.TrimSuffix(id, ".yml") 38 id = strings.ReplaceAll(id, string(filepath.Separator), "_") 39 40 return id, nil 41 } 42 43 // ReadStreamFile attempts to read a stream config and returns the result 44 func ReadStreamFile(path string) (conf stream.Config, lints []string, err error) { 45 conf = stream.NewConfig() 46 47 var confBytes []byte 48 if confBytes, lints, err = config.ReadWithJSONPointersLinted(path, true); err != nil { 49 return 50 } 51 52 var rawNode yaml.Node 53 if err = yaml.Unmarshal(confBytes, &rawNode); err != nil { 54 return 55 } 56 57 confSpec := stream.Spec() 58 confSpec = append(confSpec, config.TestsField) 59 60 if !bytes.HasPrefix(confBytes, []byte("# BENTHOS LINT DISABLE")) { 61 for _, lint := range confSpec.LintYAML(docs.NewLintContext(), &rawNode) { 62 lints = append(lints, fmt.Sprintf("%v: line %v: %v", path, lint.Line, lint.What)) 63 } 64 } 65 66 err = rawNode.Decode(&conf) 67 return 68 } 69 70 func (r *Reader) readStreamFile(dir, path string, confs map[string]stream.Config) ([]string, error) { 71 id, err := InferStreamID(dir, path) 72 if err != nil { 73 return nil, err 74 } 75 76 // Do not run unit test files 77 if len(r.testSuffix) > 0 && strings.HasSuffix(id, r.testSuffix) { 78 return nil, nil 79 } 80 81 if _, exists := confs[id]; exists { 82 return nil, fmt.Errorf("stream id (%v) collision from file: %v", id, path) 83 } 84 85 conf, lints, err := ReadStreamFile(path) 86 if err != nil { 87 return nil, err 88 } 89 90 strmInfo := streamFileInfo{id: id} 91 // This is an unlikely race condition, see readMain for more info. 92 strmInfo.updatedAt = time.Now() 93 94 r.streamFileInfo[path] = strmInfo 95 96 confs[id] = conf 97 return lints, nil 98 } 99 100 func (r *Reader) readStreamFiles(streamMap map[string]stream.Config) ([]string, error) { 101 streamsPaths, err := ifilepath.Globs(r.streamsPaths) 102 if err != nil { 103 return nil, fmt.Errorf("failed to resolve stream glob pattern: %w", err) 104 } 105 106 pathLints := []string{} 107 for _, target := range streamsPaths { 108 target = filepath.Clean(target) 109 110 if info, err := os.Stat(target); err != nil { 111 return nil, err 112 } else if !info.IsDir() { 113 tmpPathLints, err := r.readStreamFile("", target, streamMap) 114 if err != nil { 115 return nil, fmt.Errorf("failed to load config '%v': %v", target, err) 116 } 117 pathLints = append(pathLints, tmpPathLints...) 118 continue 119 } 120 121 if err := filepath.Walk(target, func(path string, info os.FileInfo, werr error) error { 122 if werr != nil { 123 return werr 124 } 125 if info.IsDir() || 126 (!strings.HasSuffix(info.Name(), ".yaml") && 127 !strings.HasSuffix(info.Name(), ".yml")) { 128 return nil 129 } 130 131 var lints []string 132 if lints, werr = r.readStreamFile(target, path, streamMap); werr != nil { 133 return fmt.Errorf("failed to load config '%v': %v", path, werr) 134 } 135 136 pathLints = append(pathLints, lints...) 137 return nil 138 }); err != nil { 139 return nil, err 140 } 141 } 142 return pathLints, nil 143 } 144 145 func (r *Reader) reactStreamUpdate(mgr bundle.NewManagement, strict bool, path string) bool { 146 if r.streamUpdateFn == nil { 147 return true 148 } 149 150 info, exists := r.streamFileInfo[path] 151 if !exists { 152 mgr.Logger().Warnf("Skipping resource update for unknown path: %v", path) 153 return true 154 } 155 156 mgr.Logger().Infof("Stream %v config updated, attempting to update stream.", info.id) 157 158 conf, lints, err := ReadStreamFile(path) 159 if err != nil { 160 mgr.Logger().Errorf("Failed to read updated stream config: %v", err) 161 return true 162 } 163 164 lintlog := mgr.Logger().NewModule(".linter") 165 for _, lint := range lints { 166 lintlog.Infoln(lint) 167 } 168 if strict && len(lints) > 0 { 169 mgr.Logger().Errorf("Rejecting updated stream %v config due to linter errors, to allow linting errors run Benthos with --chilled", info.id) 170 return true 171 } 172 173 return r.streamUpdateFn(info.id, conf) 174 }