Skip to content

Commit

Permalink
CLI: fix tar input handling (#75)
Browse files Browse the repository at this point in the history
* CLI: fix tar input handling

* incorporate review comments
  • Loading branch information
mandelsoft authored Aug 31, 2022
1 parent 3a483ae commit 5a3c7b9
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi"
"github.com/open-component-model/ocm/pkg/common/accessio"
"github.com/open-component-model/ocm/pkg/mime"
"github.com/open-component-model/ocm/pkg/utils/tarutils"
)

type Spec struct {
Expand Down Expand Up @@ -80,7 +81,7 @@ func (s *Spec) GetBlob(ctx clictx.Context, inputFilePath string) (accessio.Tempo
return nil, "", fmt.Errorf("resource type is dir but a file was provided")
}

opts := TarFileSystemOptions{
opts := tarutils.TarFileSystemOptions{
IncludeFiles: s.IncludeFiles,
ExcludeFiles: s.ExcludeFiles,
PreserveDir: s.PreserveDir != nil && *s.PreserveDir,
Expand All @@ -96,15 +97,15 @@ func (s *Spec) GetBlob(ctx clictx.Context, inputFilePath string) (accessio.Tempo
if s.Compress() {
s.SetMediaTypeIfNotDefined(mime.MIME_GZIP)
gw := gzip.NewWriter(temp.Writer())
if err := TarFileSystem(fs, inputPath, gw, opts); err != nil {
if err := tarutils.TarFileSystem(fs, inputPath, gw, opts); err != nil {
return nil, "", fmt.Errorf("unable to tar input artifact: %w", err)
}
if err := gw.Close(); err != nil {
return nil, "", fmt.Errorf("unable to close gzip writer: %w", err)
}
} else {
s.SetMediaTypeIfNotDefined(mime.MIME_TAR)
if err := TarFileSystem(fs, inputPath, temp.Writer(), opts); err != nil {
if err := tarutils.TarFileSystem(fs, inputPath, temp.Writer(), opts); err != nil {
return nil, "", fmt.Errorf("unable to tar input artifact: %w", err)
}
}
Expand Down
9 changes: 3 additions & 6 deletions pkg/common/accessio/reserttablereader.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"io"
"io/ioutil"
"os"

"github.com/open-component-model/ocm/pkg/errors"
)

type ResettableReader struct {
Expand Down Expand Up @@ -159,10 +161,5 @@ func (b *fileBuffer) Len() int {
}

func (b *fileBuffer) Close() error {
err := b.file.Close()
err2 := os.Remove(b.path)
if err2 != nil {
return err2
}
return err
return errors.ErrListf("closing file buffer").Add(b.file.Close(), os.Remove(b.path)).Result()
}
9 changes: 9 additions & 0 deletions pkg/contexts/oci/repositories/artefactset/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,26 @@ func (r Repository) Close() error {
return nil
}

// NamespaceLister handles the namespaces provided by an artefact set.
// This is always single anonymous namespace, which by ddefinition
// is the empty string.
type NamespaceLister struct{}

var anonymous cpi.NamespaceLister = &NamespaceLister{}

// NumNamespaces returns the number of namespaces with a given prefix
// for an artefact set. This is either one (the anonymous namespace) if
// the prefix is empty (all namespaces) or zero if a prefix is given.
func (n *NamespaceLister) NumNamespaces(prefix string) (int, error) {
if prefix == "" {
return 1, nil
}
return 0, nil
}

// GetNamespaces returns namespaces with a given prefix.
// This is the anonymous namespace ("") for an empty prefix
// or no namespace at all if a prefix is given.
func (n *NamespaceLister) GetNamespaces(prefix string, closure bool) ([]string, error) {
if prefix == "" {
return []string{""}, nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/contexts/oci/repositories/docker/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func (n *NamespaceContainer) GetArtefact(vers string) (cpi.ArtefactAccess, error
if err != nil {
return nil, err
}
src, err := ref.NewImageSource(dummyContext, nil)
src, err := ref.NewImageSource(dummyContext, n.repo.sysctx)
if err != nil {
return nil, err
}
Expand Down
163 changes: 163 additions & 0 deletions pkg/utils/tarutils/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright 2022 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file
//
// 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 tarutils

import (
"archive/tar"
"fmt"
"io"
"os"
pathutil "path"
"path/filepath"
"strings"

"github.com/mandelsoft/vfs/pkg/vfs"
)

// TarFileSystemOptions describes additional options for tarring a filesystem.
type TarFileSystemOptions struct {
IncludeFiles []string
ExcludeFiles []string
// PreserveDir defines that the directory specified in the Path field should be included in the blob.
// Only supported for Type dir.
PreserveDir bool
FollowSymlinks bool

root string
}

// Included determines whether a file should be included.
func (opts *TarFileSystemOptions) Included(path string) (bool, error) {
// if a root path is given remove it rom the path to be checked
if len(opts.root) != 0 {
path = strings.TrimPrefix(path, opts.root)
}
// first check if a exclude regex matches
for _, ex := range opts.ExcludeFiles {
match, err := filepath.Match(ex, path)
if err != nil {
return false, fmt.Errorf("malformed filepath syntax %q", ex)
}
if match {
return false, nil
}
}

// if no includes are defined, include all files
if len(opts.IncludeFiles) == 0 {
return true, nil
}
// otherwise check if the file should be included
for _, in := range opts.IncludeFiles {
match, err := filepath.Match(in, path)
if err != nil {
return false, fmt.Errorf("malformed filepath syntax %q", in)
}
if match {
return true, nil
}
}
return false, nil
}

// TarFileSystem creates a tar archive from a filesystem.
func TarFileSystem(fs vfs.FileSystem, root string, writer io.Writer, opts TarFileSystemOptions) error {
tw := tar.NewWriter(writer)
if opts.PreserveDir {
opts.root = pathutil.Base(root)
}
if err := addFileToTar(fs, tw, opts.root, root, opts); err != nil {
return err
}
return tw.Close()
}

func addFileToTar(fs vfs.FileSystem, tw *tar.Writer, path string, realPath string, opts TarFileSystemOptions) error {
if len(path) != 0 { // do not check the root
include, err := opts.Included(path)
if err != nil {
return err
}
if !include {
return nil
}
}
info, err := fs.Lstat(realPath)
if err != nil {
return err
}

header, err := tar.FileInfoHeader(info, "")
if err != nil {
return err
}
header.Name = path

switch {
case info.IsDir():
// do not write root header
if len(path) != 0 {
if err := tw.WriteHeader(header); err != nil {
return fmt.Errorf("unable to write header for %q: %w", path, err)
}
}
err := vfs.Walk(fs, realPath, func(subFilePath string, info os.FileInfo, err error) error {
if subFilePath == realPath {
return nil
}
if err != nil {
return err
}
relPath, err := filepath.Rel(realPath, subFilePath)
if err != nil {
return fmt.Errorf("unable to calculate relative path for %s: %w", subFilePath, err)
}
err = addFileToTar(fs, tw, pathutil.Join(path, relPath), subFilePath, opts)
if err == nil && info.IsDir() {
err = vfs.SkipDir
}
return err
})
return err
case info.Mode().IsRegular():
if err := tw.WriteHeader(header); err != nil {
return fmt.Errorf("unable to write header for %q: %w", path, err)
}
file, err := fs.OpenFile(realPath, os.O_RDONLY, os.ModePerm)
if err != nil {
return fmt.Errorf("unable to open file %q: %w", path, err)
}
if _, err := io.Copy(tw, file); err != nil {
_ = file.Close()
return fmt.Errorf("unable to add file to tar %q: %w", path, err)
}
if err := file.Close(); err != nil {
return fmt.Errorf("unable to close file %q: %w", path, err)
}
return nil
case header.Typeflag == tar.TypeSymlink:
if !opts.FollowSymlinks {
//log.Info(fmt.Sprintf("symlink found in %q but symlinks are not followed", path))
return nil
}
realPath, err := vfs.EvalSymlinks(fs, realPath)
if err != nil {
return fmt.Errorf("unable to follow symlink %s: %w", realPath, err)
}
return addFileToTar(fs, tw, path, realPath, opts)
default:
return fmt.Errorf("unsupported file type %s in %s", info.Mode().String(), path)
}
}

0 comments on commit 5a3c7b9

Please sign in to comment.