feat(influx): add support for providing directories of pkgs to pkg command
closes: #16657pull/16767/head
parent
c46045e673
commit
2d2fb4056b
cmd/influx
|
@ -1,5 +1,9 @@
|
|||
## Undecided
|
||||
|
||||
### Features
|
||||
|
||||
1. [16765](https://github.com/influxdata/influxdb/pull/16765): Extend influx cli pkg command with ability to take multiple files and directories
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
1. [16733](https://github.com/influxdata/influxdb/pull/16733): Fix notification rule renaming panics from UI
|
||||
|
|
|
@ -45,6 +45,7 @@ type cmdPkgBuilder struct {
|
|||
disableTableBorders bool
|
||||
org organization
|
||||
quiet bool
|
||||
recurse bool
|
||||
|
||||
applyOpts struct {
|
||||
envRefs []string
|
||||
|
@ -96,6 +97,7 @@ func (b *cmdPkgBuilder) cmdPkgApply() *cobra.Command {
|
|||
cmd.Short = "Apply a pkg to create resources"
|
||||
|
||||
b.org.register(cmd, false)
|
||||
b.registerFileFlags(cmd)
|
||||
cmd.Flags().StringVarP(&b.encoding, "encoding", "e", "", "Encoding for the input stream. If a file is provided will gather encoding type from file extension. If extension provided will override.")
|
||||
cmd.Flags().BoolVarP(&b.quiet, "quiet", "q", false, "Disable output printing")
|
||||
cmd.Flags().StringVar(&b.applyOpts.force, "force", "", `TTY input, if package will have destructive changes, proceed if set "true"`)
|
||||
|
@ -104,8 +106,6 @@ func (b *cmdPkgBuilder) cmdPkgApply() *cobra.Command {
|
|||
cmd.Flags().BoolVar(&b.disableTableBorders, "disable-table-borders", false, "Disable table borders")
|
||||
|
||||
b.applyOpts.secrets = []string{}
|
||||
cmd.Flags().StringSliceVarP(&b.files, "file", "f", nil, "Path to package file")
|
||||
cmd.MarkFlagFilename("file", "yaml", "yml", "json", "jsonnet")
|
||||
cmd.Flags().StringSliceVar(&b.applyOpts.secrets, "secret", nil, "Secrets to provide alongside the package; format should --secret=SECRET_KEY=SECRET_VALUE --secret=SECRET_KEY_2=SECRET_VALUE_2")
|
||||
cmd.Flags().StringSliceVar(&b.applyOpts.envRefs, "env-ref", nil, "Environment references to provide alongside the package; format should --env-ref=REF_KEY=REF_VALUE --env-ref=REF_KEY_2=REF_VALUE_2")
|
||||
|
||||
|
@ -137,9 +137,9 @@ func (b *cmdPkgBuilder) pkgApplyRunEFn(cmd *cobra.Command, args []string) error
|
|||
isTTY bool
|
||||
)
|
||||
if b.applyOpts.url != "" {
|
||||
pkg, err = pkger.Parse(b.applyEncoding(), pkger.FromHTTPRequest(b.applyOpts.url))
|
||||
pkg, err = pkger.Parse(b.applyEncoding(""), pkger.FromHTTPRequest(b.applyOpts.url))
|
||||
} else {
|
||||
pkg, isTTY, err = b.readPkgStdInOrFile(b.files)
|
||||
pkg, isTTY, err = b.readPkgStdInOrFile(b.files, b.recurse)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -300,7 +300,7 @@ func (b *cmdPkgBuilder) pkgExportAllRunEFn(cmd *cobra.Command, args []string) er
|
|||
|
||||
func (b *cmdPkgBuilder) cmdPkgSummary() *cobra.Command {
|
||||
runE := func(cmd *cobra.Command, args []string) error {
|
||||
pkg, _, err := b.readPkgStdInOrFile(b.files)
|
||||
pkg, _, err := b.readPkgStdInOrFile(b.files, b.recurse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -312,7 +312,7 @@ func (b *cmdPkgBuilder) cmdPkgSummary() *cobra.Command {
|
|||
cmd := b.newCmd("summary", runE)
|
||||
cmd.Short = "Summarize the provided package"
|
||||
|
||||
cmd.Flags().StringSliceVarP(&b.files, "file", "f", nil, "input file for pkg; if none provided will use TTY input")
|
||||
b.registerFileFlags(cmd)
|
||||
cmd.Flags().BoolVarP(&b.disableColor, "disable-color", "c", false, "Disable color in output")
|
||||
cmd.Flags().BoolVar(&b.disableTableBorders, "disable-table-borders", false, "Disable table borders")
|
||||
|
||||
|
@ -321,7 +321,7 @@ func (b *cmdPkgBuilder) cmdPkgSummary() *cobra.Command {
|
|||
|
||||
func (b *cmdPkgBuilder) cmdPkgValidate() *cobra.Command {
|
||||
runE := func(cmd *cobra.Command, args []string) error {
|
||||
pkg, _, err := b.readPkgStdInOrFile(b.files)
|
||||
pkg, _, err := b.readPkgStdInOrFile(b.files, b.recurse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -331,12 +331,18 @@ func (b *cmdPkgBuilder) cmdPkgValidate() *cobra.Command {
|
|||
cmd := b.newCmd("validate", runE)
|
||||
cmd.Short = "Validate the provided package"
|
||||
|
||||
b.registerFileFlags(cmd)
|
||||
cmd.Flags().StringVarP(&b.encoding, "encoding", "e", "", "Encoding for the input stream. If a file is provided will gather encoding type from file extension. If extension provided will override.")
|
||||
cmd.Flags().StringSliceVarP(&b.files, "file", "f", nil, "input file for pkg; if none provided will use TTY input")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (b *cmdPkgBuilder) registerFileFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().StringSliceVarP(&b.files, "file", "f", nil, "Path to package file")
|
||||
cmd.MarkFlagFilename("file", "yaml", "yml", "json", "jsonnet")
|
||||
cmd.Flags().BoolVarP(&b.recurse, "recurse", "R", false, "Process the directory used in -f, --file recursively. Useful when you want to manage related manifests organized within the same directory.")
|
||||
}
|
||||
|
||||
func (b *cmdPkgBuilder) writePkg(w io.Writer, pkgSVC pkger.SVC, outPath string, opts ...pkger.CreatePkgSetFn) error {
|
||||
pkg, err := pkgSVC.CreatePkg(context.Background(), opts...)
|
||||
if err != nil {
|
||||
|
@ -356,17 +362,33 @@ func (b *cmdPkgBuilder) writePkg(w io.Writer, pkgSVC pkger.SVC, outPath string,
|
|||
return ioutil.WriteFile(outPath, buf.Bytes(), os.ModePerm)
|
||||
}
|
||||
|
||||
func (b *cmdPkgBuilder) readPkgStdInOrFile(files []string) (*pkger.Pkg, bool, error) {
|
||||
if len(files) > 0 {
|
||||
var rawPkgs []*pkger.Pkg
|
||||
for _, file := range files {
|
||||
pkg, err := pkger.Parse(b.applyEncoding(), pkger.FromFile(file), pkger.ValidSkipParseError())
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
rawPkgs = append(rawPkgs, pkg)
|
||||
func (b *cmdPkgBuilder) readPkgFromFiles(filePaths []string, recurse bool) (*pkger.Pkg, error) {
|
||||
mFiles := make(map[string]struct{})
|
||||
for _, f := range filePaths {
|
||||
files, err := readFilesFromPath(f, recurse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg, err := pkger.Combine(rawPkgs...)
|
||||
for _, ff := range files {
|
||||
mFiles[ff] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
var rawPkgs []*pkger.Pkg
|
||||
for f := range mFiles {
|
||||
pkg, err := pkger.Parse(b.applyEncoding(f), pkger.FromFile(f), pkger.ValidSkipParseError())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawPkgs = append(rawPkgs, pkg)
|
||||
}
|
||||
|
||||
return pkger.Combine(rawPkgs...)
|
||||
}
|
||||
|
||||
func (b *cmdPkgBuilder) readPkgStdInOrFile(files []string, recurse bool) (*pkger.Pkg, bool, error) {
|
||||
if len(files) > 0 {
|
||||
pkg, err := b.readPkgFromFiles(files, recurse)
|
||||
return pkg, false, err
|
||||
}
|
||||
|
||||
|
@ -376,7 +398,7 @@ func (b *cmdPkgBuilder) readPkgStdInOrFile(files []string) (*pkger.Pkg, bool, er
|
|||
isTTY = true
|
||||
}
|
||||
|
||||
pkg, err := pkger.Parse(b.applyEncoding(), pkger.FromReader(b.in))
|
||||
pkg, err := pkger.Parse(b.applyEncoding(""), pkger.FromReader(b.in))
|
||||
return pkg, isTTY, err
|
||||
}
|
||||
|
||||
|
@ -421,9 +443,9 @@ func (b *cmdPkgBuilder) getInput(msg, defaultVal string) string {
|
|||
return getInput(ui, msg, defaultVal)
|
||||
}
|
||||
|
||||
func (b *cmdPkgBuilder) applyEncoding() pkger.Encoding {
|
||||
func (b *cmdPkgBuilder) applyEncoding(file string) pkger.Encoding {
|
||||
urlBase := path.Ext(b.applyOpts.url)
|
||||
ext := filepath.Ext(b.file)
|
||||
ext := filepath.Ext(file)
|
||||
switch {
|
||||
case ext == ".json" || b.encoding == "json" || urlBase == ".json":
|
||||
return pkger.EncodingJSON
|
||||
|
@ -940,6 +962,48 @@ func formatDuration(d time.Duration) string {
|
|||
return d.String()
|
||||
}
|
||||
|
||||
func readFilesFromPath(filePath string, recurse bool) ([]string, error) {
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return []string{filePath}, nil
|
||||
}
|
||||
|
||||
dirFiles, err := ioutil.ReadDir(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mFiles := make(map[string]struct{})
|
||||
assign := func(ss ...string) {
|
||||
for _, s := range ss {
|
||||
mFiles[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, f := range dirFiles {
|
||||
fileP := filepath.Join(filePath, f.Name())
|
||||
if f.IsDir() {
|
||||
if recurse {
|
||||
rFiles, err := readFilesFromPath(fileP, recurse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
assign(rFiles...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
assign(fileP)
|
||||
}
|
||||
|
||||
var files []string
|
||||
for f := range mFiles {
|
||||
files = append(files, f)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func mapKeys(provided, kvPairs []string) map[string]string {
|
||||
out := make(map[string]string)
|
||||
for _, k := range provided {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -303,6 +304,77 @@ metadata:
|
|||
})
|
||||
}
|
||||
|
||||
func Test_readFilesFromPath(t *testing.T) {
|
||||
t.Run("single file", func(t *testing.T) {
|
||||
dir := newTempDir(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
f := newTempFile(t, dir)
|
||||
|
||||
files, err := readFilesFromPath(f.Name(), false)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{f.Name()}, files)
|
||||
|
||||
files, err = readFilesFromPath(f.Name(), true)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{f.Name()}, files)
|
||||
})
|
||||
|
||||
t.Run("dir with no files", func(t *testing.T) {
|
||||
dir := newTempDir(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
files, err := readFilesFromPath(dir, false)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, files)
|
||||
})
|
||||
|
||||
t.Run("dir with only files", func(t *testing.T) {
|
||||
dir := newTempDir(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
filePaths := []string{
|
||||
newTempFile(t, dir).Name(),
|
||||
newTempFile(t, dir).Name(),
|
||||
}
|
||||
sort.Strings(filePaths)
|
||||
|
||||
files, err := readFilesFromPath(dir, false)
|
||||
require.NoError(t, err)
|
||||
sort.Strings(files)
|
||||
assert.Equal(t, filePaths, files)
|
||||
|
||||
files, err = readFilesFromPath(dir, true)
|
||||
require.NoError(t, err)
|
||||
sort.Strings(files)
|
||||
assert.Equal(t, filePaths, files)
|
||||
})
|
||||
|
||||
t.Run("dir with nested dir that has files", func(t *testing.T) {
|
||||
dir := newTempDir(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
nestedDir := filepath.Join(dir, "/nested/twice")
|
||||
require.NoError(t, os.MkdirAll(nestedDir, os.ModePerm))
|
||||
|
||||
filePaths := []string{
|
||||
newTempFile(t, nestedDir).Name(),
|
||||
newTempFile(t, nestedDir).Name(),
|
||||
}
|
||||
sort.Strings(filePaths)
|
||||
|
||||
files, err := readFilesFromPath(dir, false)
|
||||
require.NoError(t, err)
|
||||
sort.Strings(files)
|
||||
assert.Empty(t, files)
|
||||
|
||||
files, err = readFilesFromPath(dir, true)
|
||||
require.NoError(t, err)
|
||||
sort.Strings(files)
|
||||
assert.Equal(t, filePaths, files)
|
||||
})
|
||||
}
|
||||
|
||||
type flagArg struct{ name, val string }
|
||||
|
||||
func (s flagArg) String() string {
|
||||
|
@ -414,6 +486,14 @@ func newTempDir(t *testing.T) string {
|
|||
return tempDir
|
||||
}
|
||||
|
||||
func newTempFile(t *testing.T, dir string) *os.File {
|
||||
t.Helper()
|
||||
|
||||
f, err := ioutil.TempFile(dir, "")
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
func idsStr(ids ...influxdb.ID) string {
|
||||
var idStrs []string
|
||||
for _, id := range ids {
|
||||
|
|
Loading…
Reference in New Issue