Skip to content

Commit

Permalink
Refactor of bundle (#414)
Browse files Browse the repository at this point in the history
* refactoring of bundle

* clean up formatting

* Update pkg/kudoctl/cmd/install/install.go

Co-Authored-By: Fabian Baier <fabian@mesosphere.io>

* Update pkg/kudoctl/cmd/install/install.go

Co-Authored-By: Fabian Baier <fabian@mesosphere.io>

* details of repository is now behind a repository interface and does not leak details like index files
  • Loading branch information
kensipe committed Jun 27, 2019
1 parent 479e0ed commit 5ff2289
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 165 deletions.
38 changes: 11 additions & 27 deletions pkg/kudoctl/cmd/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func installFrameworks(args []string, options *Options) error {
}

for _, name := range args {
err := installFramework(name, "", *r, kc, options)
err := installFramework(name, "", r, kc, options)
if err != nil {
return err
}
Expand All @@ -97,43 +97,27 @@ func installFrameworks(args []string, options *Options) error {
// - a framework name in the remote repository
// in that order. Should there exist a local folder e.g. `cassandra` it will take precedence
// over the remote repository package with the same name.
func getPackageCRDs(name string, options *Options, repository repo.FrameworkRepository) (*repo.PackageCRDs, error) {
func getPackageCRDs(name string, options *Options, repository repo.Repository) (*repo.PackageCRDs, error) {
// Local files/folder have priority
if _, err := os.Stat(name); err == nil {
return repo.ReadFileSystemPackage(name)
}

// Construct the package name and download the package from the remote repo
indexFile, err := repository.DownloadIndexFile()
if err != nil {
return nil, errors.WithMessage(err, "could not download repository index file: %v")
}

var bundleVersion *repo.BundleVersion

if options.PackageVersion == "" {
bv, err := indexFile.GetByName(name)
b, err := repo.NewBundle(name)
if err != nil {
return nil, errors.Wrapf(err, "getting %s in index file", name)
return nil, err
}
bundleVersion = bv
} else {
bv, err := indexFile.GetByNameAndVersion(name, options.PackageVersion)
if err != nil {
return nil, errors.Wrapf(err, "getting %s in index file", name)
}
bundleVersion = bv
return b.GetCRDs()
}

packageName := bundleVersion.Name + "-" + bundleVersion.Version

return repository.GetPackage(packageName)
bundle, err := repository.GetPackageBundle(name, options.PackageVersion)
if err != nil {
return nil, err
}
return bundle.GetCRDs()
}

// installFramework is the umbrella for a single framework installation that gathers the business logic
// for a cluster and returns an error in case there is a problem
// TODO: needs testing
func installFramework(name, previous string, repository repo.FrameworkRepository, kc *kudo.Client, options *Options) error {
func installFramework(name, previous string, repository repo.Repository, kc *kudo.Client, options *Options) error {
crds, err := getPackageCRDs(name, options, repository)
if err != nil {
return errors.Wrapf(err, "failed to install package: %s", name)
Expand Down
165 changes: 165 additions & 0 deletions pkg/kudoctl/util/repo/bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package repo

import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/pkg/errors"
)

// This is an abstraction which abstracts the underlying bundle, which is likely file system or compressed file.
// There should be a complete separation between retrieving a bundle if not local and working with a bundle.

// Bundle is an abstraction of the collection of files that makes up a package. It is anything we can retrieve the PackageCRDs from.
type Bundle interface {
GetCRDs() (*PackageCRDs, error)
}

type tarBundle struct {
reader io.Reader
}

type fileBundle struct {
path string
}

// NewBundle creates the implementation of the bundle based on the path. The expectation is the bundle
// is always local . The path can be relative or absolute location of the bundle.
func NewBundle(path string) (Bundle, error) {
// make sure file exists
fi, err := os.Stat(path)
if err != nil {
return nil, fmt.Errorf("unsupported file system format %v. Expect either a tar.gz file or a folder", path)
}
// order of discovery
// 1. tarball
// 2. file based
if fi.Mode().IsRegular() && strings.HasSuffix(path, ".tar.gz") {
r, err := getFileReader(path)
if err != nil {
return nil, err
}
return tarBundle{r}, nil
} else if fi.IsDir() {
return fileBundle{path}, nil
} else {
return nil, fmt.Errorf("unsupported file system format %v. Expect either a tar.gz file or a folder", path)
}
}

func getFileReader(path string) (io.Reader, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
return f, nil
}

// NewBundleFromReader is a bundle from a reader. This should only be used when a file cache isn't used.
func NewBundleFromReader(r io.Reader) Bundle {
return tarBundle{r}
}

func (b tarBundle) GetCRDs() (*PackageCRDs, error) {

p, err := parseTarPackage(b.reader)
if err != nil {
return nil, errors.Wrap(err, "while extracting package files")
}
return p.getCRDs()
}

func (b fileBundle) GetCRDs() (*PackageCRDs, error) {
p, err := fromFolder(b.path)
if err != nil {
return nil, errors.Wrap(err, "while reading package from the file system")
}
return p.getCRDs()
}

func fromFolder(packagePath string) (*PackageFiles, error) {
result := newPackageFiles()
err := filepath.Walk(packagePath, func(path string, file os.FileInfo, err error) error {
if err != nil {
return err
}
if file.IsDir() {
// skip directories
return nil
}
if path == packagePath {
// skip the root folder, as Walk always starts there
return nil
}
bytes, err := ioutil.ReadFile(path)
if err != nil {
return err
}

return parsePackageFile(path, bytes, &result)
})
if err != nil {
return nil, err
}
return &result, nil
}

func parseTarPackage(r io.Reader) (*PackageFiles, error) {
gzr, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
defer func() {
err := gzr.Close()
if err != nil {
fmt.Printf("Error when closing gzip reader: %s", err)
}
}()

tr := tar.NewReader(gzr)

result := newPackageFiles()
for {
header, err := tr.Next()

switch {

// if no more files are found return
case err == io.EOF:
return &result, nil

// return any other error
case err != nil:
return nil, err

// if the header is nil, just skip it (not sure how this happens)
case header == nil:
continue
}

// check the file type
switch header.Typeflag {

case tar.TypeDir:
// we don't need to handle folders, files have folder name in their names and that should be enough

// if it's a file create it
case tar.TypeReg:
bytes, err := ioutil.ReadAll(tr)
if err != nil {
return nil, errors.Wrapf(err, "while reading file from bundle tarball %s", header.Name)
}

err = parsePackageFile(header.Name, bytes, &result)
if err != nil {
return nil, err
}
}
}
}
130 changes: 0 additions & 130 deletions pkg/kudoctl/util/repo/package.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
package repo

import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"

Expand Down Expand Up @@ -42,76 +36,6 @@ type PackageFiles struct {
Params []v1alpha1.Parameter
}

// ReadTarGzPackage reads package from tarball and converts it to the CRD format
func ReadTarGzPackage(r io.Reader) (*PackageCRDs, error) {
p, err := parsePackage(r)
if err != nil {
return nil, errors.Wrap(err, "while extracting package files")
}
return p.getCRDs()
}

// ReadFileSystemPackage reads package from filesystem and converts it to the CRD format
func ReadFileSystemPackage(path string) (*PackageCRDs, error) {
isTarGz := func() bool {
if fi, err := os.Stat(path); err == nil {
return fi.Mode().IsRegular() && strings.HasSuffix(path, ".tar.gz")
}
return false
}

isFolder := func() bool {
if fi, err := os.Stat(path); err == nil {
return fi.IsDir()
}
return false
}

switch {
case isTarGz():
f, err := os.Open(path)
if err != nil {
return nil, err
}
return ReadTarGzPackage(f)
case isFolder():
p, err := fromFolder(path)
if err != nil {
return nil, errors.Wrap(err, "while reading package from the file system")
}
return p.getCRDs()
default:
return nil, fmt.Errorf("unsupported file system format %v. Expect either a tar.gz file or a folder", path)
}
}

func fromFolder(packagePath string) (*PackageFiles, error) {
result := newPackageFiles()
err := filepath.Walk(packagePath, func(path string, file os.FileInfo, err error) error {
if err != nil {
return err
}
if file.IsDir() {
// skip directories
return nil
}
if path == packagePath {
// skip the root folder, as Walk always starts there
return nil
}
bytes, err := ioutil.ReadFile(path)
if err != nil {
return err
}

return parsePackageFile(path, bytes, &result)
})
if err != nil {
return nil, err
}
return &result, nil
}

func parsePackageFile(filePath string, fileBytes []byte, currentPackage *PackageFiles) error {
isFrameworkFile := func(name string) bool {
return strings.HasSuffix(name, frameworkFileName)
Expand Down Expand Up @@ -156,60 +80,6 @@ func parsePackageFile(filePath string, fileBytes []byte, currentPackage *Package
return nil
}

func parsePackage(r io.Reader) (*PackageFiles, error) {
gzr, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
defer func() {
err := gzr.Close()
if err != nil {
fmt.Printf("Error when closing gzip reader: %s", err)
}
}()

tr := tar.NewReader(gzr)

result := newPackageFiles()
for {
header, err := tr.Next()

switch {

// if no more files are found return
case err == io.EOF:
return &result, nil

// return any other error
case err != nil:
return nil, err

// if the header is nil, just skip it (not sure how this happens)
case header == nil:
continue
}

// check the file type
switch header.Typeflag {

case tar.TypeDir:
// we don't need to handle folders, files have folder name in their names and that should be enough

// if it's a file create it
case tar.TypeReg:
bytes, err := ioutil.ReadAll(tr)
if err != nil {
return nil, errors.Wrapf(err, "while reading file from bundle tarball %s", header.Name)
}

err = parsePackageFile(header.Name, bytes, &result)
if err != nil {
return nil, err
}
}
}
}

func newPackageFiles() PackageFiles {
return PackageFiles{
Templates: make(map[string]string),
Expand Down
6 changes: 5 additions & 1 deletion pkg/kudoctl/util/repo/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ func TestReadFileSystemPackage(t *testing.T) {

for _, tt := range tests {
t.Run(fmt.Sprintf("%s-from-%s", tt.name, tt.path), func(t *testing.T) {
actual, err := ReadFileSystemPackage(tt.path)
bundle, err := NewBundle(tt.path)
if err != nil {
t.Fatalf("Found unexpected error: %v", err)
}
actual, err := bundle.GetCRDs()
if err != nil {
t.Fatalf("Found unexpected error: %v", err)
}
Expand Down
Loading

0 comments on commit 5ff2289

Please sign in to comment.