mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
* unifying idk and featurebase: first pass * resolved conflict with master for gitignore & dockerignore * deleted binaries that were accidentally pushed to git * combined gitlab jobs for idk & featurebase * run go fmt for idk * updated ssh env variable, and made docker password variable in gitlab env variables * fixed typo assigning variable name * trying to fix docker login error * trying a different solution for docker password * pass registry * fixed docker login * updated paths for idk * exclude idk tests from featurebase test run * fix vendor error * update certificates * grpc needs to be in version 1.38 genproto, which is imported by big query updates the grpc version to 1.47.0 grpc 1.47.0 causes etcd to deadlock when calling etcd.Close() the fix is to have a replace in go.mod to specify a specific grpc version * run go mod tidy * go mod * run go mod tidy * exclude bigquery since it is causing issues and undo grpc replace in go.mod * fix grpc version * fix formatting error * update formatting * attempt to fix formatting * update path for code coverage * update to use current branch binaries, not master * fix for building idk - path updates * udpate path for binaries * update job dependecies * update docker idk tests to use the current branch registry * update stages for jobs * updated job dependencies * not allow idk s3 dump to fail since it is a dependency for integration tests * update dependecy for idk tests * update paths for idk build and code coverage * download featurebase binary from s3 * pass branch name to all setup scripts * change to current branch instead of master * updated sonarcloud * sonarcloud fix and branch name fix * trying to speed up pipeline run time * update stage * branch name fix + sonar cloud * sonarcloud
138 lines
3.5 KiB
Go
138 lines
3.5 KiB
Go
package csv
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"github.com/molecula/featurebase/v3/idk"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type Main struct {
|
|
idk.Main `flag:"!embed"`
|
|
Files []string `help:"List of files, URLs, or directories to ingest."`
|
|
Header []string `help:"Optional header. If not passed, first line of each file is used."`
|
|
IgnoreHeader bool `short:"g" help:"Ignore header in file and use configured header. You *must* configure a header."`
|
|
JustDoIt bool `short:"j" help:"Any header field not in the appropriate format, just downcase, use it as the name and process the value as a String/set field"`
|
|
|
|
files chan string
|
|
visited map[string]struct{}
|
|
}
|
|
|
|
func NewMain() *Main {
|
|
m := &Main{
|
|
Main: *idk.NewMain(),
|
|
files: make(chan string),
|
|
visited: make(map[string]struct{}),
|
|
}
|
|
m.Main.Namespace = "ingester_csv"
|
|
|
|
once := &sync.Once{}
|
|
m.NewSource = func() (idk.Source, error) {
|
|
if len(m.Files) == 0 {
|
|
return nil, errors.New("must provide at least one file or directory with --files")
|
|
}
|
|
once.Do(func() { go m.streamFileNames() })
|
|
|
|
source := NewSource()
|
|
source.Files = m.files
|
|
source.Header = m.Header
|
|
source.Log = m.Main.Log()
|
|
source.once = &sync.Once{}
|
|
source.JustDoIt = m.JustDoIt
|
|
source.IgnoreHeader = m.IgnoreHeader
|
|
return source, nil
|
|
}
|
|
return m
|
|
}
|
|
|
|
// ValidateHeaders is used during --dry-run to check that all headers provided at CLI or in
|
|
// CSV files can be parsed properly.
|
|
// Also returns the field structs as it would be parsed in a normal run. In the case of
|
|
// multiple CSV files, only returns the fields for the last file.
|
|
func (m *Main) ValidateHeaders() ([]idk.Field, error) {
|
|
var err error
|
|
fields := make([]idk.Field, 0)
|
|
if len(m.Header) > 0 {
|
|
fields, err = m.validateHeader(m.Header)
|
|
if err != nil {
|
|
return fields, errors.Wrap(err, "validating header")
|
|
}
|
|
} else {
|
|
for _, f := range m.Files {
|
|
f, err := openFileOrURL(f)
|
|
if err != nil {
|
|
return fields, errors.Wrapf(err, "opening file (%s)", f)
|
|
}
|
|
defer f.Close()
|
|
reader := csv.NewReader(f)
|
|
header, err := reader.Read()
|
|
if err != nil {
|
|
return fields, errors.Wrapf(err, "reading CSV file (%s)", f)
|
|
}
|
|
fields, err = m.validateHeader(header)
|
|
if err != nil {
|
|
return fields, errors.Wrap(err, "validating header")
|
|
}
|
|
}
|
|
}
|
|
return fields, nil
|
|
}
|
|
|
|
func (m *Main) validateHeader(h []string) ([]idk.Field, error) {
|
|
fields := make([]idk.Field, 0)
|
|
for _, val := range h {
|
|
field, err := idk.HeaderToField(val, m.Log())
|
|
if err != nil {
|
|
return fields, errors.Wrapf(err, "invalid header (%v)", val)
|
|
}
|
|
fields = append(fields, field)
|
|
}
|
|
return fields, nil
|
|
}
|
|
|
|
func (m *Main) streamFileNames() {
|
|
defer close(m.files)
|
|
|
|
for _, filename := range m.Files {
|
|
if err := filepath.Walk(filename, m.traverse); err != nil {
|
|
m.Log().Printf("filepath.Walk(%s) %v", filename, err)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *Main) traverse(path string, fi os.FileInfo, err error) error {
|
|
if err != nil {
|
|
m.Log().Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
|
|
return err
|
|
}
|
|
|
|
switch mode := fi.Mode(); {
|
|
case mode.IsRegular():
|
|
if _, ok := m.visited[path]; !ok {
|
|
m.visited[path] = struct{}{}
|
|
m.files <- path
|
|
}
|
|
|
|
case mode.IsDir():
|
|
if _, ok := m.visited[path]; !ok {
|
|
m.visited[path] = struct{}{}
|
|
}
|
|
|
|
case mode&os.ModeSymlink != 0:
|
|
lnk, err := filepath.EvalSymlinks(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, ok := m.visited[lnk]; !ok {
|
|
if err := filepath.Walk(lnk, m.traverse); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|