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  }