diff --git a/fake/fakefileloader.go b/fake/fakefileloader.go deleted file mode 100644 index 1df7b2c0b..000000000 --- a/fake/fakefileloader.go +++ /dev/null @@ -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 -} diff --git a/fileloader.go b/fileloader.go index 91ce86b0c..eb73461e2 100644 --- a/fileloader.go +++ b/fileloader.go @@ -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) } diff --git a/fileloader_test.go b/fileloader_test.go index 2461e3e76..a9bb4b2a9 100644 --- a/fileloader_test.go +++ b/fileloader_test.go @@ -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") } diff --git a/loader.go b/loader.go index d6f2fd3cf..dd5e1b671 100644 --- a/loader.go +++ b/loader.go @@ -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) } diff --git a/factory.go b/loader_test.go similarity index 56% rename from factory.go rename to loader_test.go index 86fc97527..35dd75391 100644 --- a/factory.go +++ b/loader_test.go @@ -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 -}