diff options
| author | Dave Henderson <dhenderson@gmail.com> | 2024-05-29 20:36:24 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-05-29 20:36:24 -0400 |
| commit | 95a06b9fc94c44af51588379c4e443e986b7e4d5 (patch) | |
| tree | c17f08a12c020ef45302800d778d02ee18bfcc24 /data/datasource.go | |
| parent | 362f058f0c93900c7bd7462aac200597e70ebfb7 (diff) | |
chore!: Replacing the data.Data type with a datasource registry (#2083)
* chore!: Replacing the data.Data type with a datasource registry
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
* chore(lint): fix lint warning
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
---------
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
Diffstat (limited to 'data/datasource.go')
| -rw-r--r-- | data/datasource.go | 364 |
1 files changed, 0 insertions, 364 deletions
diff --git a/data/datasource.go b/data/datasource.go deleted file mode 100644 index 86e3101c..00000000 --- a/data/datasource.go +++ /dev/null @@ -1,364 +0,0 @@ -package data - -import ( - "context" - "encoding/json" - "fmt" - "io" - "io/fs" - "net/http" - "net/url" - "runtime" - "sort" - "strings" - - "github.com/hairyhenderson/go-fsimpl" - "github.com/hairyhenderson/gomplate/v4/internal/config" - "github.com/hairyhenderson/gomplate/v4/internal/datafs" - "github.com/hairyhenderson/gomplate/v4/internal/parsers" - "github.com/hairyhenderson/gomplate/v4/internal/urlhelpers" -) - -// Data - -// -// Deprecated: will be replaced in future -type Data struct { - Ctx context.Context - - // TODO: remove this before 4.0 - Sources map[string]config.DataSource - - cache map[string]*fileContent - - // headers from the --datasource-header/-H option that don't reference datasources from the commandline - ExtraHeaders map[string]http.Header -} - -type fileContent struct { - contentType string - b []byte -} - -// NewData - constructor for Data -// -// Deprecated: will be replaced in future -func NewData(datasourceArgs, headerArgs []string) (*Data, error) { - cfg := &config.Config{} - err := cfg.ParseDataSourceFlags(datasourceArgs, nil, nil, headerArgs) - if err != nil { - return nil, err - } - data := FromConfig(context.Background(), cfg) - return data, nil -} - -// FromConfig - internal use only! -func FromConfig(ctx context.Context, cfg *config.Config) *Data { - // XXX: This is temporary, and will be replaced with something a bit cleaner - // when datasources are refactored - ctx = datafs.ContextWithStdin(ctx, cfg.Stdin) - - sources := map[string]config.DataSource{} - for alias, d := range cfg.DataSources { - sources[alias] = d - } - for alias, d := range cfg.Context { - sources[alias] = d - } - - return &Data{ - Ctx: ctx, - Sources: sources, - ExtraHeaders: cfg.ExtraHeaders, - } -} - -// Source - a data source -// -// Deprecated: will be replaced in future -type Source struct { - URL *url.URL - Header http.Header // used for http[s]: URLs, nil otherwise -} - -// String is the method to format the flag's value, part of the flag.Value interface. -// The String method's output will be used in diagnostics. -func (s *Source) String() string { - return s.URL.String() -} - -// DefineDatasource - -func (d *Data) DefineDatasource(alias, value string) (string, error) { - if alias == "" { - return "", fmt.Errorf("datasource alias must be provided") - } - if d.DatasourceExists(alias) { - return "", nil - } - srcURL, err := urlhelpers.ParseSourceURL(value) - if err != nil { - return "", err - } - s := config.DataSource{ - URL: srcURL, - Header: d.ExtraHeaders[alias], - } - if d.Sources == nil { - d.Sources = make(map[string]config.DataSource) - } - d.Sources[alias] = s - return "", nil -} - -// DatasourceExists - -func (d *Data) DatasourceExists(alias string) bool { - _, ok := d.Sources[alias] - return ok -} - -func (d *Data) lookupSource(alias string) (*config.DataSource, error) { - source, ok := d.Sources[alias] - if !ok { - srcURL, err := url.Parse(alias) - if err != nil || !srcURL.IsAbs() { - return nil, fmt.Errorf("undefined datasource '%s': %w", alias, err) - } - source = config.DataSource{ - URL: srcURL, - Header: d.ExtraHeaders[alias], - } - d.Sources[alias] = source - } - - return &source, nil -} - -func (d *Data) readDataSource(ctx context.Context, alias string, args ...string) (*fileContent, error) { - source, err := d.lookupSource(alias) - if err != nil { - return nil, err - } - fc, err := d.readSource(ctx, alias, source, args...) - if err != nil { - return nil, fmt.Errorf("couldn't read datasource '%s': %w", alias, err) - } - - return fc, nil -} - -// Include - -func (d *Data) Include(alias string, args ...string) (string, error) { - fc, err := d.readDataSource(d.Ctx, alias, args...) - if err != nil { - return "", err - } - - return string(fc.b), err -} - -// Datasource - -func (d *Data) Datasource(alias string, args ...string) (interface{}, error) { - fc, err := d.readDataSource(d.Ctx, alias, args...) - if err != nil { - return nil, err - } - - return parsers.ParseData(fc.contentType, string(fc.b)) -} - -// DatasourceReachable - Determines if the named datasource is reachable with -// the given arguments. Reads from the datasource, and discards the returned data. -func (d *Data) DatasourceReachable(alias string, args ...string) bool { - source, ok := d.Sources[alias] - if !ok { - return false - } - _, err := d.readSource(d.Ctx, alias, &source, args...) - return err == nil -} - -// readSource returns the (possibly cached) data from the given source, -// as referenced by the given args -func (d *Data) readSource(ctx context.Context, alias string, source *config.DataSource, args ...string) (*fileContent, error) { - if d.cache == nil { - d.cache = make(map[string]*fileContent) - } - cacheKey := alias - for _, v := range args { - cacheKey += v - } - cached, ok := d.cache[cacheKey] - if ok { - return cached, nil - } - - arg := "" - if len(args) > 0 { - arg = args[0] - } - u, err := resolveURL(source.URL, arg) - if err != nil { - return nil, err - } - - fc, err := d.readFileContent(ctx, u, source.Header) - if err != nil { - return nil, fmt.Errorf("reading %s: %w", u, err) - } - d.cache[cacheKey] = fc - return fc, nil -} - -// readFileContent returns content from the given URL -func (d Data) readFileContent(ctx context.Context, u *url.URL, hdr http.Header) (*fileContent, error) { - fsys, err := datafs.FSysForPath(ctx, u.String()) - if err != nil { - return nil, fmt.Errorf("fsys for path %v: %w", u, err) - } - - u, fname := datafs.SplitFSMuxURL(u) - - // need to support absolute paths on local filesystem too - // TODO: this is a hack, probably fix this? - if u.Scheme == "file" && runtime.GOOS != "windows" { - fname = u.Path + fname - } - - fsys = fsimpl.WithContextFS(ctx, fsys) - fsys = fsimpl.WithHeaderFS(hdr, fsys) - - // convert d.Sources to a map[string]config.DataSources - // TODO: remove this when d.Sources is removed - ds := make(map[string]config.DataSource) - for k, v := range d.Sources { - ds[k] = config.DataSource{ - URL: v.URL, - Header: v.Header, - } - } - - fsys = datafs.WithDataSourcesFS(ds, fsys) - - f, err := fsys.Open(fname) - if err != nil { - return nil, fmt.Errorf("open (url: %q, name: %q): %w", u, fname, err) - } - defer f.Close() - - fi, err := f.Stat() - if err != nil { - return nil, fmt.Errorf("stat (url: %q, name: %q): %w", u, fname, err) - } - - // possible type hint in the type query param. Contrary to spec, we allow - // unescaped '+' characters to make it simpler to provide types like - // "application/array+json" - mimeType := u.Query().Get("type") - mimeType = strings.ReplaceAll(mimeType, " ", "+") - - if mimeType == "" { - mimeType = fsimpl.ContentType(fi) - } - - var data []byte - - if fi.IsDir() { - var dirents []fs.DirEntry - dirents, err = fs.ReadDir(fsys, fname) - if err != nil { - return nil, fmt.Errorf("readDir (url: %q, name: %s): %w", u, fname, err) - } - - entries := make([]string, len(dirents)) - for i, e := range dirents { - entries[i] = e.Name() - } - data, err = json.Marshal(entries) - if err != nil { - return nil, fmt.Errorf("json.Marshal: %w", err) - } - - mimeType = jsonArrayMimetype - } else { - data, err = io.ReadAll(f) - if err != nil { - return nil, fmt.Errorf("read (url: %q, name: %s): %w", u, fname, err) - } - } - - if mimeType == "" { - // default to text/plain - mimeType = textMimetype - } - - return &fileContent{contentType: mimeType, b: data}, nil -} - -// Show all datasources - -func (d *Data) ListDatasources() []string { - datasources := make([]string, 0, len(d.Sources)) - for source := range d.Sources { - datasources = append(datasources, source) - } - sort.Strings(datasources) - return datasources -} - -// resolveURL parses the relative URL rel against base, and returns the -// resolved URL. Differs from url.ResolveReference in that query parameters are -// added. In case of duplicates, params from rel are used. -func resolveURL(base *url.URL, rel string) (*url.URL, error) { - // if there's an opaque part, there's no resolving to do - just return the - // base URL - if base.Opaque != "" { - return base, nil - } - - // git URLs are special - they have double-slashes that separate a repo - // from a path in the repo. A missing double-slash means the path is the - // root. - switch base.Scheme { - case "git", "git+file", "git+http", "git+https", "git+ssh": - if strings.Contains(base.Path, "//") && strings.Contains(rel, "//") { - return nil, fmt.Errorf("both base URL and subpath contain '//', which is not allowed in git URLs") - } - - // If there's a subpath, the base path must end with '/'. This behaviour - // is unique to git URLs - other schemes would instead drop the last - // path element and replace with the subpath. - if rel != "" && !strings.HasSuffix(base.Path, "/") { - base.Path += "/" - } - - // If subpath starts with '//', make it relative by prefixing a '.', - // otherwise it'll be treated as a schemeless URI and the first part - // will be interpreted as a hostname. - if strings.HasPrefix(rel, "//") { - rel = "." + rel - } - } - - relURL, err := url.Parse(rel) - if err != nil { - return nil, err - } - - // URL.ResolveReference requires (or assumes, at least) that the base is - // absolute. We want to support relative URLs too though, so we need to - // correct for that. - out := base.ResolveReference(relURL) - if out.Scheme == "" && out.Path[0] == '/' { - out.Path = out.Path[1:] - } - - if base.RawQuery != "" { - bq := base.Query() - rq := relURL.Query() - for k := range rq { - bq.Set(k, rq.Get(k)) - } - out.RawQuery = bq.Encode() - } - - return out, nil -} |
