Skip to content

Commit

Permalink
Refactor Loader interface
Browse files Browse the repository at this point in the history
  • Loading branch information
seans3 committed Feb 26, 2018
1 parent c7ec56e commit 2803503
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 128 deletions.
59 changes: 51 additions & 8 deletions fileloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,75 @@ limitations under the License.
package loader

import (
"fmt"
"os"
"path/filepath"

"k8s.io/kubectl/pkg/kinflate/util/fs"
)

// Implements internal interface schemeLoader.
const currentDir = "."

// Internal implementation of SchemeLoader interface.
type fileLoader struct {
fs fs.FileSystem
}

func newFileLoader(fs fs.FileSystem) (schemeLoader, error) {
return &fileLoader{fs: fs}, nil
// NewFileLoader returns a SchemeLoader to handle a file system.
func NewFileLoader(fs fs.FileSystem) SchemeLoader {
return &fileLoader{fs: fs}
}

// Is the location calculated with the root and location params a full file path.
func (l *fileLoader) IsScheme(root string, location string) bool {
fullFilePath, err := l.FullLocation(root, location)
if err != nil {
return false
}
return filepath.IsAbs(fullFilePath)
}

// Returns the directory of the calculated full file path.
// Example: "/home/seans/project", "subdir/file.txt" -> "/home/seans/project/subdir".
func (l *fileLoader) Root(root string, location string) (string, error) {
fullFilePath, err := l.FullLocation(root, location)
if err != nil {
return "", err
}
return filepath.Dir(fullFilePath), nil
}

// Join the root path with the location path.
func (l *fileLoader) fullLocation(root string, location string) string {
// If location is a full file path, then ignore root. If location is relative, then
// join the root path with the location path. Either root or location can be empty,
// but not both.
// Example: "/home/seans/project", "subdir/file.txt" -> "/home/seans/project/subdir/file.txt".
func (l *fileLoader) FullLocation(root string, location string) (string, error) {
// First, validate the parameters
if len(root) == 0 && len(location) == 0 {
return "", fmt.Errorf("Unable to calculate full location: root and location empty")
}
// Special case current directory, expanding to full file path.
if location == currentDir {
currentDir, err := os.Getwd()
if err != nil {
return "", err
}
location = currentDir
}
// Assume the location is an full file path. If not, then join root with location.
fullLocation := location
if !filepath.IsAbs(location) {
fullLocation = filepath.Join(root, location)
}
return fullLocation
return fullLocation, nil
}

// Load returns the bytes from reading a file at fullFilePath.
// Implements the Loader interface.
func (l *fileLoader) load(fullFilePath string) ([]byte, error) {
// TODO: Check that fullFilePath is an absolute file path.
func (l *fileLoader) Load(fullFilePath string) ([]byte, error) {
// Validate path to load from is a full file path.
if !filepath.IsAbs(fullFilePath) {
return nil, fmt.Errorf("Attempting to load file without full file path: %s\n", fullFilePath)
}
return l.fs.ReadFile(fullFilePath)
}
54 changes: 0 additions & 54 deletions fileloader_test.go

This file was deleted.

111 changes: 53 additions & 58 deletions loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,87 +16,82 @@ limitations under the License.

package loader

import (
"fmt"
import "fmt"

"k8s.io/kubectl/pkg/kinflate/util/fs"
)

// Loader abstracts how bytes are read for manifest, resource, patch, or other
// files. Each Loader is tightly coupled with the location of a manifest file.
// So each file without an absolute path is located relative to the
// manifest file it was read from. Each Load() call is relative to this manifest
// location (referenced as root). The Loader hides how to read bytes from different
// "schemes" (e.g. file, url, or git).
// Loader interface exposes methods to read bytes in a scheme-agnostic manner.
// The Loader encapsulating a root location to calculate where to read from.
type Loader interface {
// Clones new Loader for a new app/package with its own manifest from the current
// Loader. The "newRoot" can be relative or absolute. If it's relative, the new
// Loader root is calculated from the current Loader root. Can be a file or directory.
// If it's a file, then the base directory is used for root calculation.
// Root returns the scheme-specific string representing the root location for this Loader.
Root() string
// New returns Loader located at newRoot.
New(newRoot string) (Loader, error)
// Returns the bytes at location or an error. If it's a relative path, then
// the location is expanded using the Loader root.
// Example: returns YAML bytes at location "/home/seans/project/service.yaml".
// Load returns the bytes read from the location or an error.
Load(location string) ([]byte, error)
}

// Private implmentation of Loader interface.
type loaderImpl struct {
root string
fs fs.FileSystem
// http client for URL loading
// git client for Git loading
root string
schemes []SchemeLoader
}

// Interface for different types of loaders (e.g. fileLoader, httpLoader, etc.)
type SchemeLoader interface {
// Does this location correspond to this scheme.
IsScheme(root string, location string) bool
// Calulcate a new root.
Root(root string, location string) (string, error)
// Combines the root and path into a full location string.
FullLocation(root string, path string) (string, error)
// Load bytes at scheme-specific location or an error.
Load(location string) ([]byte, error)
}

// RootLoader initializes the first Loader, with the initial root location.
func RootLoader(root string, fs fs.FileSystem) Loader {
// TODO: Validate the root
return &loaderImpl{root: root, fs: fs}
const emptyRoot = ""

// Init initializes the first loader with the supported schemes.
// Example schemes: fileLoader, httpLoader, gitLoader.
func Init(schemes []SchemeLoader) Loader {
return &loaderImpl{root: emptyRoot, schemes: schemes}
}

// Root returns the scheme-specific root location for this Loader.
func (l *loaderImpl) Root() string {
return l.root
}

// New clones a new Loader with a new absolute root path.
// Returns a new Loader rooted at newRoot.
func (l *loaderImpl) New(newRoot string) (Loader, error) {
loader, err := l.getSchemeLoader(newRoot)
scheme, err := l.getSchemeLoader(newRoot)
if err != nil {
return nil, err
}
return &loaderImpl{root: loader.fullLocation(l.root, newRoot), fs: l.fs}, nil
root, err := scheme.Root(l.root, newRoot)
if err != nil {
return nil, err
}
return &loaderImpl{root: root, schemes: l.schemes}, nil
}

// Load returns the bytes at the specified location.
// Implemented by getting a scheme-specific structure to
// load the bytes.
// Load returns all the bytes read from scheme-specific location or an error.
func (l *loaderImpl) Load(location string) ([]byte, error) {
loader, err := l.getSchemeLoader(location)
scheme, err := l.getSchemeLoader(location)
if err != nil {
return nil, err
}
fullLocation := loader.fullLocation(l.root, location)
return loader.load(fullLocation)
}

// Helper function to parse scheme from location parameter and return
func (l *loaderImpl) getSchemeLoader(location string) (schemeLoader, error) {
// FIXME: First check the scheme of root location.
switch {
case isFilePath(location):
return newFileLoader(l.fs)
default:
return nil, fmt.Errorf("unknown scheme: %v", location)
fullLocation, err := scheme.FullLocation(l.root, location)
if err != nil {
return nil, err
}
return scheme.Load(fullLocation)
}

// Parses the location to determine if it is a file path.
func isFilePath(location string) bool {
return true
}

/////////////////////////////////////////////////
// Internal interface for specific type of loader
// Examples: fileLoader, HttpLoader, or GitLoader
type schemeLoader interface {
// Combines the root and path into a full location string.
fullLocation(root string, path string) string
// Must be a full, non-relative location string.
load(location string) ([]byte, error)
// Helper function to parse scheme from location parameter.
func (l *loaderImpl) getSchemeLoader(location string) (SchemeLoader, error) {
for _, scheme := range l.schemes {
if scheme.IsScheme(l.root, location) {
return scheme, nil
}
}
return nil, fmt.Errorf("Unknown Scheme: %s, %s\n", l.root, location)
}
Loading

0 comments on commit 2803503

Please sign in to comment.