Copying pkg/loader

This commit is contained in:
Jeffrey Regan
2018-05-11 14:01:15 -07:00
4 changed files with 372 additions and 0 deletions

81
pkg/loader/fileloader.go Normal file
View File

@@ -0,0 +1,81 @@
/*
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 loader
import (
"fmt"
"os"
"path/filepath"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
const currentDir = "."
// Internal implementation of SchemeLoader interface.
type fileLoader struct {
fs fs.FileSystem
}
// 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)
}
// 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. Special case for ".": Expands to current working directory.
// Example: "/home/seans/project", "subdir/bar" -> "/home/seans/project/subdir/bar".
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 a full file path. If not, then join root with location.
fullLocation := location
if !filepath.IsAbs(location) {
fullLocation = filepath.Join(root, location)
}
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) {
// 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)
}

101
pkg/loader/loader.go Normal file
View File

@@ -0,0 +1,101 @@
/*
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 loader
import "fmt"
// 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 {
// 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)
// 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
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
// 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)
}
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
}
// Returns a new Loader rooted at newRoot. "newRoot" MUST be
// a directory (not a file). The directory can have a trailing
// slash or not.
// Example: "/home/seans/project" or "/home/seans/project/"
// NOT "/home/seans/project/file.yaml".
func (l *loaderImpl) New(newRoot string) (Loader, error) {
scheme, err := l.getSchemeLoader(newRoot)
if err != nil {
return nil, err
}
root, err := scheme.FullLocation(l.root, newRoot)
if err != nil {
return nil, err
}
return &loaderImpl{root: root, schemes: l.schemes}, nil
}
// Load returns all the bytes read from scheme-specific location or an error.
// "location" can be an absolute path, or if relative, full location is
// calculated from the Root().
func (l *loaderImpl) Load(location string) ([]byte, error) {
scheme, err := l.getSchemeLoader(location)
if err != nil {
return nil, err
}
fullLocation, err := scheme.FullLocation(l.root, location)
if err != nil {
return nil, err
}
return scheme.Load(fullLocation)
}
// 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)
}

125
pkg/loader/loader_test.go Normal file
View File

@@ -0,0 +1,125 @@
/*
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 loader
import (
"path/filepath"
"reflect"
"testing"
"k8s.io/kubectl/pkg/kustomize/util/fs"
)
func initializeRootLoader(fakefs fs.FileSystem) Loader {
var schemes []SchemeLoader
schemes = append(schemes, NewFileLoader(fakefs))
rootLoader := Init(schemes)
return rootLoader
}
func TestLoader_Root(t *testing.T) {
// Initialize the fake file system and the root loader.
fakefs := fs.MakeFakeFS()
fakefs.WriteFile("/home/seans/project/file.yaml", []byte("Unused"))
fakefs.WriteFile("/home/seans/project/subdir/file.yaml", []byte("Unused"))
fakefs.WriteFile("/home/seans/project2/file.yaml", []byte("Unused"))
rootLoader := initializeRootLoader(fakefs)
_, err := rootLoader.New("")
if err == nil {
t.Fatalf("Expected error for empty root location not returned")
}
_, err = rootLoader.New("https://google.com/project")
if err == nil {
t.Fatalf("Expected error for unknown scheme not returned")
}
// Test with trailing slash in directory.
loader, err := rootLoader.New("/home/seans/project/")
if err != nil {
t.Fatalf("Unexpected in New(): %v\n", err)
}
if "/home/seans/project/" != loader.Root() {
t.Fatalf("Incorrect Loader Root: %s\n", loader.Root())
}
subLoader, err := loader.New("subdir")
if err != nil {
t.Fatalf("Unexpected in New(): %v\n", err)
}
if "/home/seans/project/subdir" != subLoader.Root() {
t.Fatalf("Incorrect Loader Root: %s\n", subLoader.Root())
}
// Test without trailing slash in directory.
anotherLoader, err := loader.New("/home/seans/project2")
if err != nil {
t.Fatalf("Unexpected in New(): %v\n", err)
}
if "/home/seans/project2" != anotherLoader.Root() {
t.Fatalf("Incorrect Loader Root: %s\n", anotherLoader.Root())
}
// Current directory should be expanded to a full absolute file path.
currentDirLoader, err := loader.New(".")
if err != nil {
t.Fatalf("Unexpected in New(): %v\n", err)
}
if !filepath.IsAbs(currentDirLoader.Root()) {
t.Fatalf("Incorrect Loader Root: %s\n", currentDirLoader.Root())
}
}
func TestLoader_Load(t *testing.T) {
// Initialize the fake file system and the root loader.
fakefs := fs.MakeFakeFS()
fakefs.WriteFile("/home/seans/project/file.yaml", []byte("This is a yaml file"))
fakefs.WriteFile("/home/seans/project/subdir/file.yaml", []byte("Subdirectory file content"))
fakefs.WriteFile("/home/seans/project2/file.yaml", []byte("This is another yaml file"))
rootLoader := initializeRootLoader(fakefs)
loader, err := rootLoader.New("/home/seans/project")
if err != nil {
t.Fatalf("Unexpected in New(): %v\n", err)
}
fileBytes, err := loader.Load("file.yaml") // Load relative to root location
if err != nil {
t.Fatalf("Unexpected error in Load(): %v", err)
}
if !reflect.DeepEqual([]byte("This is a yaml file"), fileBytes) {
t.Fatalf("Load failed. Expected %s, but got %s", "This is a yaml file", fileBytes)
}
fileBytes, err = loader.Load("subdir/file.yaml")
if err != nil {
t.Fatalf("Unexpected error in Load(): %v", err)
}
if !reflect.DeepEqual([]byte("Subdirectory file content"), fileBytes) {
t.Fatalf("Load failed. Expected %s, but got %s", "Subdirectory file content", fileBytes)
}
fileBytes, err = loader.Load("/home/seans/project2/file.yaml")
if err != nil {
t.Fatalf("Unexpected error in Load(): %v", err)
}
if !reflect.DeepEqual([]byte("This is another yaml file"), fileBytes) {
t.Fatalf("Load failed. Expected %s, but got %s", "This is another yaml file", fileBytes)
}
}

View File

@@ -0,0 +1,65 @@
/*
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 loadertest
import (
"os"
"k8s.io/kubectl/pkg/kustomize/util/fs"
"k8s.io/kubectl/pkg/loader"
)
// FakeLoader encapsulates the delegate Loader and the fake file system.
type FakeLoader struct {
fs fs.FileSystem
delegate loader.Loader
}
// NewFakeLoader returns a Loader that delegates calls, and encapsulates
// a fake file system that the Loader reads from. "initialDir" parameter
// must be an full, absolute directory (trailing slash doesn't matter).
func NewFakeLoader(initialDir string) FakeLoader {
// Create fake filesystem and inject it into initial Loader.
fakefs := fs.MakeFakeFS()
var schemes []loader.SchemeLoader
schemes = append(schemes, loader.NewFileLoader(fakefs))
rootLoader := loader.Init(schemes)
loader, _ := rootLoader.New(initialDir)
return FakeLoader{fs: fakefs, delegate: loader}
}
// Adds a fake file to the file system.
func (f FakeLoader) AddFile(fullFilePath string, content []byte) error {
return f.fs.WriteFile(fullFilePath, content)
}
// Adds a fake directory to the file system.
func (f FakeLoader) AddDirectory(fullDirPath string, mode os.FileMode) error {
return f.fs.Mkdir(fullDirPath, mode)
}
func (f FakeLoader) Root() string {
return f.delegate.Root()
}
func (f FakeLoader) New(newRoot string) (loader.Loader, error) {
return f.delegate.New(newRoot)
}
func (f FakeLoader) Load(location string) ([]byte, error) {
return f.delegate.Load(location)
}