Skip to content

Commit b72657c

Browse files
authored
allow componentsDir to be a glob (#193)
* allow componentsDir to be a glob This adds support for multiple component directories expressed as a glob, but does not introduce any namespace semantics. Components still have to be unqiue across all directories. * add tests
1 parent ad51b2a commit b72657c

File tree

7 files changed

+145
-48
lines changed

7 files changed

+145
-48
lines changed

internal/model/app.go

Lines changed: 72 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -466,71 +466,95 @@ func (a *App) DeclaredTopLevelVars() map[string]interface{} {
466466
return ret
467467
}
468468

469-
// loadComponents loads metadata for all components for the app.
470-
// The data is returned as a map keyed by component name. It does _not_ recurse
471-
// into subdirectories.
469+
// loadComponents loads metadata for all components for the app. It first expands the components directory
470+
// for glob patterns and loads components from all directories that match. It does _not_ recurse
471+
// into subdirectories. The data is returned as a map keyed by component name.
472+
// Note that component names must be unique across all directories. Support for multiple directories is just a
473+
// way to partition classes of components and does not introduce any namespace semantics.
472474
func (a *App) loadComponents() (map[string]Component, error) {
473475
var list []Component
474-
dir := strings.TrimSuffix(filepath.Clean(a.inner.Spec.ComponentsDir), "/")
475-
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
476-
if err != nil {
477-
return err
478-
}
479-
if path == dir {
480-
return nil
481-
}
482-
if info.IsDir() {
483-
files, err := filepath.Glob(filepath.Join(path, "*"))
476+
loadDirComponents := func(dir string) error {
477+
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
484478
if err != nil {
485479
return err
486480
}
487-
var staticFiles []string
488-
hasIndexJsonnet := false
489-
hasIndexYAML := false
490-
for _, f := range files {
491-
stat, err := os.Stat(f)
481+
if path == dir {
482+
return nil
483+
}
484+
if info.IsDir() {
485+
files, err := filepath.Glob(filepath.Join(path, "*"))
492486
if err != nil {
493487
return err
494488
}
495-
if stat.IsDir() {
496-
continue
489+
var staticFiles []string
490+
hasIndexJsonnet := false
491+
hasIndexYAML := false
492+
for _, f := range files {
493+
stat, err := os.Stat(f)
494+
if err != nil {
495+
return err
496+
}
497+
if stat.IsDir() {
498+
continue
499+
}
500+
switch filepath.Base(f) {
501+
case "index.jsonnet":
502+
hasIndexJsonnet = true
503+
case "index.yaml":
504+
hasIndexYAML = true
505+
}
506+
if strings.HasSuffix(f, ".json") || strings.HasSuffix(f, ".yaml") {
507+
staticFiles = append(staticFiles, f)
508+
}
497509
}
498-
switch filepath.Base(f) {
499-
case "index.jsonnet":
500-
hasIndexJsonnet = true
501-
case "index.yaml":
502-
hasIndexYAML = true
503-
}
504-
if strings.HasSuffix(f, ".json") || strings.HasSuffix(f, ".yaml") {
505-
staticFiles = append(staticFiles, f)
510+
switch {
511+
case hasIndexJsonnet:
512+
list = append(list, Component{
513+
Name: filepath.Base(path),
514+
Files: []string{filepath.Join(path, "index.jsonnet")},
515+
})
516+
case hasIndexYAML:
517+
list = append(list, Component{
518+
Name: filepath.Base(path),
519+
Files: staticFiles,
520+
})
506521
}
522+
return filepath.SkipDir
507523
}
508-
switch {
509-
case hasIndexJsonnet:
510-
list = append(list, Component{
511-
Name: filepath.Base(path),
512-
Files: []string{filepath.Join(path, "index.jsonnet")},
513-
})
514-
case hasIndexYAML:
524+
extension := filepath.Ext(path)
525+
if supportedExtensions[extension] {
515526
list = append(list, Component{
516-
Name: filepath.Base(path),
517-
Files: staticFiles,
527+
Name: strings.TrimSuffix(filepath.Base(path), extension),
528+
Files: []string{path},
518529
})
519530
}
520-
return filepath.SkipDir
521-
}
522-
extension := filepath.Ext(path)
523-
if supportedExtensions[extension] {
524-
list = append(list, Component{
525-
Name: strings.TrimSuffix(filepath.Base(path), extension),
526-
Files: []string{path},
527-
})
528-
}
529-
return nil
530-
})
531+
return nil
532+
})
533+
return err
534+
}
535+
ds, err := filepath.Glob(a.inner.Spec.ComponentsDir)
531536
if err != nil {
532537
return nil, err
533538
}
539+
var dirs []string
540+
for _, d := range ds {
541+
s, err := os.Stat(d)
542+
if err != nil {
543+
return nil, err
544+
}
545+
if s.IsDir() {
546+
dirs = append(dirs, d)
547+
}
548+
}
549+
if len(dirs) == 0 {
550+
return nil, fmt.Errorf("no component directories found after expanding %s", a.inner.Spec.ComponentsDir)
551+
}
552+
for _, d := range dirs {
553+
err := loadDirComponents(d)
554+
if err != nil {
555+
return nil, err
556+
}
557+
}
534558
m := make(map[string]Component, len(list))
535559
for _, c := range list {
536560
if old, ok := m[c.Name]; ok {

internal/model/app_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ func TestAppSimple(t *testing.T) {
238238

239239
envs := app.Environments()
240240
a.Equal(4, len(envs))
241+
a.False(app.ClusterScopedLists())
241242
}
242243

243244
func TestAppWarnings(t *testing.T) {
@@ -299,6 +300,33 @@ func TestAppComponentLoadSubdirs(t *testing.T) {
299300
a.Contains(comp.Files, filepath.Join("components", "comp2", "index.yaml"))
300301
}
301302

303+
func TestAppComponentLoadMultidirs(t *testing.T) {
304+
reset := setPwd(t, "testdata/multi-dir-app")
305+
defer reset()
306+
app, err := NewApp("qbec.yaml", nil, "")
307+
require.Nil(t, err)
308+
comps, err := app.ComponentsForEnvironment("dev", nil, nil)
309+
require.Nil(t, err)
310+
a := assert.New(t)
311+
a.Equal(2, len(comps))
312+
comp := comps[0]
313+
a.Equal("a", comp.Name)
314+
a.Equal(1, len(comp.Files))
315+
a.Contains(comp.Files, filepath.Join("components", "dir1", "a.jsonnet"))
316+
comp = comps[1]
317+
a.Equal("b", comp.Name)
318+
a.Equal(1, len(comp.Files))
319+
a.Contains(comp.Files, filepath.Join("components", "dir2", "b", "index.jsonnet"))
320+
}
321+
322+
func TestAppComponentNoDirs(t *testing.T) {
323+
reset := setPwd(t, "testdata/no-dirs-app")
324+
defer reset()
325+
_, err := NewApp("qbec.yaml", nil, "")
326+
require.Error(t, err)
327+
assert.Equal(t, "load components: no component directories found after expanding components/dir*", err.Error())
328+
}
329+
302330
func TestAppComponentLoadNegative(t *testing.T) {
303331
reset := setPwd(t, "../../examples/test-app")
304332
defer reset()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
### multi dirs
2+
3+
A multi dir app is allowed to have files that are ignored.
4+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
apiVersion: 'v1',
3+
kind: 'ConfigMap',
4+
metadata: {
5+
name: 'cm-a',
6+
},
7+
data: {
8+
foo: 'bar'
9+
}
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
apiVersion: 'v1',
3+
kind: 'ConfigMap',
4+
metadata: {
5+
name: 'cm-b',
6+
},
7+
data: {
8+
bar: 'baz'
9+
}
10+
}
11+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
apiVersion: qbec.io/v1alpha1
3+
kind: App
4+
metadata:
5+
name: multi-dir-app
6+
spec:
7+
componentsDir: components/*
8+
environments:
9+
dev:
10+
server: https://dev-server
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
apiVersion: qbec.io/v1alpha1
3+
kind: App
4+
metadata:
5+
name: no-dirs-app
6+
spec:
7+
componentsDir: components/dir*
8+
environments:
9+
dev:
10+
server: https://dev-server

0 commit comments

Comments
 (0)