Skip to content

Commit

Permalink
LocalFileSource: support a custom fs.FS
Browse files Browse the repository at this point in the history
Add fs.FS support to LocalFileSource, FileResource, and the commands
that use them (deploy, diff, inspect). This is useful so these commands
can be invoked programmatically.

Signed-off-by: Andy Goldstein <[email protected]>
  • Loading branch information
ncdc committed Aug 14, 2023
1 parent a2e6064 commit 2804e19
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 20 deletions.
14 changes: 9 additions & 5 deletions pkg/kapp/cmd/app/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ package app

import (
"fmt"
"io/fs"
"os"
"sort"
"strings"

"github.com/cppforlife/go-cli-ui/ui"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"

ctlapp "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/app"
ctlcap "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/clusterapply"
cmdcore "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/cmd/core"
Expand All @@ -23,10 +29,6 @@ import (
ctllogs "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/logs"
ctlres "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/resources"
ctlresm "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/resourcesmisc"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
)

const (
Expand All @@ -47,6 +49,8 @@ type DeployOptions struct {
DeployFlags DeployFlags
ResourceTypesFlags ResourceTypesFlags
LabelFlags LabelFlags

FileSystem fs.FS
}

func NewDeployOptions(ui ui.UI, depsFactory cmdcore.DepsFactory, logger logger.Logger) *DeployOptions {
Expand Down Expand Up @@ -329,7 +333,7 @@ func (o *DeployOptions) newResourcesFromFiles() ([]ctlres.Resource, error) {
return nil, fmt.Errorf("Expected at least one --file (-f) specified with a file or directory path")
}
for _, file := range o.FileFlags.Files {
fileRs, err := ctlres.NewFileResources(file)
fileRs, err := ctlres.NewFileResources(o.FileSystem, file)
if err != nil {
return nil, err
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/kapp/cmd/tools/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
package tools

import (
"io/fs"

"github.com/cppforlife/go-cli-ui/ui"
"github.com/spf13/cobra"

ctlcap "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/clusterapply"
cmdcore "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/cmd/core"
ctldiff "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/diff"
Expand All @@ -19,6 +22,8 @@ type DiffOptions struct {
FileFlags FileFlags
FileFlags2 FileFlags2
DiffFlags DiffFlags

FileSystem fs.FS
}

func NewDiffOptions(ui ui.UI, depsFactory cmdcore.DepsFactory) *DiffOptions {
Expand Down Expand Up @@ -71,7 +76,7 @@ func (o *DiffOptions) fileResources(files []string) ([]ctlres.Resource, error) {
var newResources []ctlres.Resource

for _, file := range files {
fileRs, err := ctlres.NewFileResources(file)
fileRs, err := ctlres.NewFileResources(o.FileSystem, file)
if err != nil {
return nil, err
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/kapp/cmd/tools/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
package tools

import (
"io/fs"

"github.com/cppforlife/go-cli-ui/ui"
"github.com/spf13/cobra"

cmdcore "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/cmd/core"
ctlres "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/resources"
)
Expand All @@ -17,6 +20,8 @@ type InspectOptions struct {
FileFlags FileFlags
ResourceFilterFlags ResourceFilterFlags
Raw bool

FileSystem fs.FS
}

func NewInspectOptions(ui ui.UI, depsFactory cmdcore.DepsFactory) *InspectOptions {
Expand Down Expand Up @@ -49,7 +54,7 @@ func (o *InspectOptions) inspectFiles() error {
}

for _, file := range o.FileFlags.Files {
fileRs, err := ctlres.NewFileResources(file)
fileRs, err := ctlres.NewFileResources(o.FileSystem, file)
if err != nil {
return err
}
Expand Down
67 changes: 57 additions & 10 deletions pkg/kapp/resources/file_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package resources

import (
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
Expand All @@ -19,7 +20,12 @@ type FileResource struct {
fileSrc FileSource
}

func NewFileResources(file string) ([]FileResource, error) {
// NewFileResources inspects file and returns a slice of FileResource objects. If file is "-", a FileResource for STDIN
// is returned. If it is prefixed with either http:// or https://, a FileResource that supports an HTTP transport is
// returned. If file is a directory, one FileResource object is returned for each file in the directory with an allowed
// extension (.json, .yml, .yaml). If file is not a directory, a FileResource object is returned for that one file. If
// fsys is nil, NewFileResources uses the OS's file system. Otherwise, it uses the passed in file system.
func NewFileResources(fsys fs.FS, file string) ([]FileResource, error) {
var fileRs []FileResource

switch {
Expand All @@ -30,16 +36,22 @@ func NewFileResources(file string) ([]FileResource, error) {
fileRs = append(fileRs, NewFileResource(NewHTTPFileSource(file)))

default:
fileInfo, err := os.Stat(file)
dir, err := isDir(fsys, file)
if err != nil {
return nil, fmt.Errorf("Checking file: %v", err)
return nil, err
}

if fileInfo.IsDir() {
var paths []string
if dir {
// The typical command line invocation won't set fsys. If it comes in nil, create a new DirFS rooted at
// file, then set file to '.' (current working directory) so the fs.WalkDir call below works correctly.
if fsys == nil {
fsys = os.DirFS(file)
file = "."
}

err := filepath.Walk(file, func(path string, fi os.FileInfo, err error) error {
if err != nil || fi.IsDir() {
var paths []string
err := fs.WalkDir(fsys, file, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
ext := filepath.Ext(path)
Expand All @@ -51,16 +63,16 @@ func NewFileResources(file string) ([]FileResource, error) {
return nil
})
if err != nil {
return nil, fmt.Errorf("Listing files '%s'", file)
return nil, fmt.Errorf("error listing file %q", file)
}

sort.Strings(paths)

for _, path := range paths {
fileRs = append(fileRs, NewFileResource(NewLocalFileSource(path)))
fileRs = append(fileRs, NewFileResource(NewLocalFileSource(fsys, path)))
}
} else {
fileRs = append(fileRs, NewFileResource(NewLocalFileSource(file)))
fileRs = append(fileRs, NewFileResource(NewLocalFileSource(fsys, file)))
}
}

Expand Down Expand Up @@ -94,3 +106,38 @@ func (r FileResource) Resources() ([]Resource, error) {

return resources, nil
}

// isDir returns if path is a directory. If fsys is nil, isDir calls os.Stat(path); otherwise, it checks path inside
// fsys.
func isDir(fsys fs.FS, path string) (bool, error) {
if fsys == nil {
fileInfo, err := os.Stat(path)
if err != nil {
return false, fmt.Errorf("error stat'ing file %q: %v", path, err)
}
return fileInfo.IsDir(), nil
}

switch t := fsys.(type) {
case fs.StatFS:
fileInfo, err := t.Stat(path)
if err != nil {
return false, fmt.Errorf("error stat'ing file %q: %v", path, err)
}
return fileInfo.IsDir(), nil
case fs.FS:
f, err := t.Open(path)
if err != nil {
return false, fmt.Errorf("error opening file %q: %v", path, err)
}
defer f.Close()

fileInfo, err := f.Stat()
if err != nil {
return false, fmt.Errorf("error stat'ing file %q: %v", path, err)
}
return fileInfo.IsDir(), nil
default:
return false, fmt.Errorf("error determining if %q is a directory: unexpected FS type %T", path, fsys)
}
}
24 changes: 21 additions & 3 deletions pkg/kapp/resources/file_sources.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package resources
import (
"fmt"
"io"
"io/fs"
"net/http"
"os"
)
Expand Down Expand Up @@ -34,14 +35,31 @@ func (s StdinSource) Description() string { return "stdin" }
func (s StdinSource) Bytes() ([]byte, error) { return io.ReadAll(os.Stdin) }

type LocalFileSource struct {
fsys fs.FS
path string
}

var _ FileSource = LocalFileSource{}

func NewLocalFileSource(path string) LocalFileSource { return LocalFileSource{path} }
func (s LocalFileSource) Description() string { return fmt.Sprintf("file '%s'", s.path) }
func (s LocalFileSource) Bytes() ([]byte, error) { return os.ReadFile(s.path) }
func NewLocalFileSource(fsys fs.FS, path string) LocalFileSource {
return LocalFileSource{fsys: fsys, path: path}
}
func (s LocalFileSource) Description() string { return fmt.Sprintf("file '%s'", s.path) }
func (s LocalFileSource) Bytes() ([]byte, error) {
switch t := s.fsys.(type) {
case fs.ReadFileFS:
return t.ReadFile(s.path)
case fs.FS:
f, err := t.Open(s.path)
if err != nil {

Check warning on line 54 in pkg/kapp/resources/file_sources.go

View workflow job for this annotation

GitHub Actions / lint

empty-block: this block is empty, you can remove it (revive)

}
defer f.Close()
return fs.ReadFile(s.fsys, s.path)
default:
return os.ReadFile(s.path)
}
}

type HTTPFileSource struct {
url string
Expand Down
Loading

0 comments on commit 2804e19

Please sign in to comment.