Refactor Loader interface

This commit is contained in:
Sean Sullivan
2018-02-22 15:38:43 -08:00
parent 194d845fc9
commit c7ec56e3e5
5 changed files with 119 additions and 58 deletions

View File

@@ -1,36 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fake
import "k8s.io/kubectl/pkg/loader"
type fakefileLoader struct {
data []byte
err error
}
// NewFakeFileLoader returns Loader which always returns byte array and an error.
// Example: case no error: NewFakeFileLoader(yamlBytes, nil)
// Example: case return an error: NewFakeFileLoader(nil, errors.New("forced error"))
// Location parameter is unneeded because we always return data bytes or an error.
func NewFakeFileLoader(content []byte, e error) (loader.Loader, error) {
return &fakefileLoader{data: content, err: e}, nil
}
func (l *fakefileLoader) Load() ([]byte, error) {
return l.data, l.err
}

View File

@@ -17,19 +17,32 @@ limitations under the License.
package loader
import (
"path/filepath"
"k8s.io/kubectl/pkg/kinflate/util/fs"
)
// Implements internal interface schemeLoader.
type fileLoader struct {
fs fs.FileSystem
path string
}
func NewFileLoader(fs fs.FileSystem, path string) (Loader, error) {
return &fileLoader{fs: fs, path: path}, nil
func newFileLoader(fs fs.FileSystem) (schemeLoader, error) {
return &fileLoader{fs: fs}, nil
}
func (l *fileLoader) Load() ([]byte, error) {
return l.fs.ReadFile(l.path)
// Join the root path with the location path.
func (l *fileLoader) fullLocation(root string, location string) string {
fullLocation := location
if !filepath.IsAbs(location) {
fullLocation = filepath.Join(root, location)
}
return fullLocation
}
// 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.
return l.fs.ReadFile(fullFilePath)
}

View File

@@ -28,11 +28,11 @@ func TestFileLoaderHappyPath(t *testing.T) {
location := "foo"
content := []byte("bar")
fakefs.WriteFile(location, content)
l, err := NewFileLoader(fakefs, location)
l, err := newFileLoader(fakefs)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
b, err := l.Load()
b, err := l.load(location)
if err != nil {
t.Fatalf("unexpected error in Load: %v", err)
}
@@ -43,11 +43,11 @@ func TestFileLoaderHappyPath(t *testing.T) {
func TestFileLoaderFileNotFound(t *testing.T) {
fakefs := fs.MakeFakeFS()
l, err := NewFileLoader(fakefs, "path/does/not/exist")
l, err := newFileLoader(fakefs)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = l.Load()
_, err = l.load("/path/does/not/exist")
if err == nil {
t.Fatal("expected error in Load, but no error returned")
}

View File

@@ -16,6 +16,87 @@ limitations under the License.
package loader
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).
type Loader interface {
Load() ([]byte, error)
// 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.
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(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
}
// 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}
}
// New clones a new Loader with a new absolute root path.
func (l *loaderImpl) New(newRoot string) (Loader, error) {
loader, err := l.getSchemeLoader(newRoot)
if err != nil {
return nil, err
}
return &loaderImpl{root: loader.fullLocation(l.root, newRoot), fs: l.fs}, nil
}
// Load returns the bytes at the specified location.
// Implemented by getting a scheme-specific structure to
// load the bytes.
func (l *loaderImpl) Load(location string) ([]byte, error) {
loader, 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)
}
}
// 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)
}

View File

@@ -17,20 +17,23 @@ limitations under the License.
package loader
import (
"fmt"
"reflect"
"testing"
"k8s.io/kubectl/pkg/kinflate/util/fs"
)
func GetLoader(location string) (Loader, error) {
switch {
case isFilePath(location):
return NewFileLoader(fs.MakeRealFS(), location)
default:
return nil, fmt.Errorf("unknown scheme: %v", location)
func TestLoader_Load(t *testing.T) {
fakefs := fs.MakeFakeFS()
location := "/home/seans/project/Kube-manifest.yaml"
content := []byte("This is a kinflate manifest")
fakefs.WriteFile(location, content)
loader := RootLoader(location, fakefs)
manifestBytes, err := loader.Load(location)
if err != nil {
t.Fatalf("Unexpected error in Load(): %v", err)
}
if !reflect.DeepEqual(content, manifestBytes) {
t.Fatalf("expected %s, but got %s", content, manifestBytes)
}
}
func isFilePath(location string) bool {
return true
}