Unpack filesystem and whitelist from /proc/self/mountinfo

This commit is contained in:
Priya Wadhwa
2018-02-21 11:02:30 -08:00
parent 1589ccdf49
commit 093dfd04df
669 changed files with 96245 additions and 1 deletions
@@ -0,0 +1,72 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
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 util
import (
"io/ioutil"
"strings"
"github.com/containers/image/docker"
"github.com/containers/image/types"
"github.com/docker/docker/client"
)
// CloudPrepper prepares images sourced from a Cloud registry
type CloudPrepper struct {
Source string
Client *client.Client
ImageSource types.ImageSource
}
func (p CloudPrepper) Name() string {
return "Cloud Registry"
}
func (p CloudPrepper) GetSource() string {
return p.Source
}
func (p CloudPrepper) GetImage() (Image, error) {
image, err := getImage(p)
image.Type = ImageTypeCloud
return image, err
}
func (p CloudPrepper) GetFileSystem() (string, error) {
ref, err := docker.ParseReference("//" + p.Source)
if err != nil {
return "", err
}
sanitizedName := strings.Replace(p.Source, ":", "", -1)
sanitizedName = strings.Replace(sanitizedName, "/", "", -1)
path, err := ioutil.TempDir("", sanitizedName)
if err != nil {
return "", err
}
return path, GetFileSystemFromReference(ref, p.ImageSource, path, nil)
}
func (p CloudPrepper) GetConfig() (ConfigSchema, error) {
ref, err := docker.ParseReference("//" + p.Source)
if err != nil {
return ConfigSchema{}, err
}
return getConfigFromReference(ref, p.Source)
}
@@ -0,0 +1,88 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
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 util
import (
"context"
"io/ioutil"
"strings"
"github.com/containers/image/docker/daemon"
"github.com/docker/docker/client"
"github.com/sirupsen/logrus"
)
type DaemonPrepper struct {
Source string
Client *client.Client
}
func (p DaemonPrepper) Name() string {
return "Local Daemon"
}
func (p DaemonPrepper) GetSource() string {
return p.Source
}
func (p DaemonPrepper) GetImage() (Image, error) {
image, err := getImage(p)
image.Type = ImageTypeDaemon
return image, err
}
func (p DaemonPrepper) GetFileSystem() (string, error) {
ref, err := daemon.ParseReference(p.Source)
if err != nil {
return "", err
}
src, err := ref.NewImageSource(nil)
if err != nil {
return "", err
}
sanitizedName := strings.Replace(p.Source, ":", "", -1)
sanitizedName = strings.Replace(sanitizedName, "/", "", -1)
path, err := ioutil.TempDir("", sanitizedName)
if err != nil {
return "", err
}
return path, GetFileSystemFromReference(ref, src, path, nil)
}
func (p DaemonPrepper) GetConfig() (ConfigSchema, error) {
ref, err := daemon.ParseReference(p.Source)
if err != nil {
return ConfigSchema{}, err
}
return getConfigFromReference(ref, p.Source)
}
func (p DaemonPrepper) GetHistory() []ImageHistoryItem {
history, err := p.Client.ImageHistory(context.Background(), p.Source)
if err != nil {
logrus.Errorf("Could not obtain image history for %s: %s", p.Source, err)
}
historyItems := []ImageHistoryItem{}
for _, item := range history {
historyItems = append(historyItems, ImageHistoryItem{CreatedBy: item.CreatedBy})
}
return historyItems
}
@@ -0,0 +1,123 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
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 util
import (
"archive/tar"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"github.com/docker/docker/client"
)
type Event struct {
Status string `json:"status"`
Error string `json:"error"`
Progress string `json:"progress"`
ProgressDetail struct {
Current int `json:"current"`
Total int `json:"total"`
} `json:"progressDetail"`
}
func NewClient() (*client.Client, error) {
cli, err := client.NewEnvClient()
if err != nil {
return nil, fmt.Errorf("Error getting docker client: %s", err)
}
cli.NegotiateAPIVersion(context.Background())
return cli, nil
}
func getLayersFromManifest(r io.Reader) ([]string, error) {
type Manifest struct {
Layers []string
}
manifestJSON, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
var imageManifest []Manifest
if err := json.Unmarshal(manifestJSON, &imageManifest); err != nil {
return []string{}, fmt.Errorf("Could not unmarshal manifest to get layer order: %s", err)
}
return imageManifest[0].Layers, nil
}
func unpackDockerSave(tarPath string, target string) error {
if _, ok := os.Stat(target); ok != nil {
os.MkdirAll(target, 0775)
}
f, err := os.Open(tarPath)
if err != nil {
return err
}
tr := tar.NewReader(f)
// Unpack the layers into a map, since we need to sort out the order later.
var layers []string
layerMap := map[string][]byte{}
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
// Docker save contains files and directories. Ignore the directories.
// We care about the layers and the manifest. The layers look like:
// $SHA/layer.tar
// and they are referenced that way in the manifest.
switch t := hdr.Typeflag; t {
case tar.TypeReg:
if hdr.Name == "manifest.json" {
layers, err = getLayersFromManifest(tr)
if err != nil {
return err
}
} else if strings.HasSuffix(hdr.Name, ".tar") {
layerMap[hdr.Name], err = ioutil.ReadAll(tr)
if err != nil {
return err
}
}
case tar.TypeDir:
continue
default:
return fmt.Errorf("unsupported file type %v found in file %s tar %s", t, hdr.Name, tarPath)
}
}
for _, layer := range layers {
if err = UnTar(bytes.NewReader(layerMap[layer]), target, nil); err != nil {
return fmt.Errorf("Could not unpack layer %s: %s", layer, err)
}
}
return nil
}
@@ -0,0 +1,181 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
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 util
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/sirupsen/logrus"
)
// Directory stores a representation of a file directory.
type Directory struct {
Root string
Content []string
}
type DirectoryEntry struct {
Name string
Size int64
}
func GetSize(path string) int64 {
stat, err := os.Stat(path)
if err != nil {
logrus.Errorf("Could not obtain size for %s: %s", path, err)
return -1
}
if stat.IsDir() {
size, err := getDirectorySize(path)
if err != nil {
logrus.Errorf("Could not obtain directory size for %s: %s", path, err)
}
return size
}
return stat.Size()
}
//GetFileContents returns the contents of a file at the specified path
func GetFileContents(path string) (*string, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err
}
contents, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
strContents := string(contents)
//If file is empty, return nil
if strContents == "" {
return nil, nil
}
return &strContents, nil
}
func getDirectorySize(path string) (int64, error) {
var size int64
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if !info.IsDir() {
size += info.Size()
}
return err
})
return size, err
}
// GetDirectoryContents converts the directory starting at the provided path into a Directory struct.
func GetDirectory(path string, deep bool) (Directory, error) {
var directory Directory
directory.Root = path
var err error
if deep {
walkFn := func(currPath string, info os.FileInfo, err error) error {
newContent := strings.TrimPrefix(currPath, directory.Root)
if newContent != "" {
directory.Content = append(directory.Content, newContent)
}
return nil
}
err = filepath.Walk(path, walkFn)
} else {
contents, err := ioutil.ReadDir(path)
if err != nil {
return directory, err
}
for _, file := range contents {
fileName := "/" + file.Name()
directory.Content = append(directory.Content, fileName)
}
}
return directory, err
}
func GetDirectoryEntries(d Directory) []DirectoryEntry {
return CreateDirectoryEntries(d.Root, d.Content)
}
func CreateDirectoryEntries(root string, entryNames []string) (entries []DirectoryEntry) {
for _, name := range entryNames {
entryPath := filepath.Join(root, name)
size := GetSize(entryPath)
entry := DirectoryEntry{
Name: name,
Size: size,
}
entries = append(entries, entry)
}
return entries
}
func CheckSameFile(f1name, f2name string) (bool, error) {
// Check first if files differ in size and immediately return
f1stat, err := os.Stat(f1name)
if err != nil {
return false, err
}
f2stat, err := os.Stat(f2name)
if err != nil {
return false, err
}
if f1stat.Size() != f2stat.Size() {
return false, nil
}
// Next, check file contents
f1, err := ioutil.ReadFile(f1name)
if err != nil {
return false, err
}
f2, err := ioutil.ReadFile(f2name)
if err != nil {
return false, err
}
if !bytes.Equal(f1, f2) {
return false, nil
}
return true, nil
}
// HasFilepathPrefix checks if the given file path begins with prefix
func HasFilepathPrefix(path, prefix string) bool {
path = filepath.Clean(path)
prefix = filepath.Clean(prefix)
pathArray := strings.Split(path, "/")
prefixArray := strings.Split(prefix, "/")
if len(pathArray) < len(prefixArray) {
return false
}
for index := range prefixArray {
if prefixArray[index] == pathArray[index] {
continue
}
return false
}
return true
}
@@ -0,0 +1,214 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
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 util
import (
"archive/tar"
"encoding/json"
"errors"
"io/ioutil"
"os"
"strings"
"github.com/GoogleCloudPlatform/container-diff/cmd/util/output"
"github.com/containers/image/docker"
"github.com/containers/image/manifest"
"github.com/containers/image/pkg/compression"
"github.com/containers/image/types"
"github.com/sirupsen/logrus"
)
type Prepper interface {
Name() string
GetConfig() (ConfigSchema, error)
GetFileSystem() (string, error)
GetImage() (Image, error)
GetSource() string
}
type ImageType int
const (
ImageTypeTar ImageType = iota
ImageTypeDaemon
ImageTypeCloud
)
type Image struct {
Source string
FSPath string
Config ConfigSchema
Type ImageType
}
func (i *Image) IsTar() bool {
return i.Type == ImageTypeTar
}
func (i *Image) IsDaemon() bool {
return i.Type == ImageTypeDaemon
}
func (i *Image) IsCloud() bool {
return i.Type == ImageTypeCloud
}
func (i *Image) GetRemoteDigest() (string, error) {
ref, err := docker.ParseReference("//" + i.Source)
if err != nil {
return "", err
}
return getDigestFromReference(ref, i.Source)
}
func (i *Image) GetName() string {
return strings.Split(i.Source, ":")[0]
}
type ImageHistoryItem struct {
CreatedBy string `json:"created_by"`
}
type ConfigObject struct {
Env []string `json:"Env"`
Entrypoint []string `json:"Entrypoint"`
ExposedPorts map[string]struct{} `json:"ExposedPorts"`
Cmd []string `json:"Cmd"`
Volumes map[string]struct{} `json:"Volumes"`
Workdir string `json:"WorkingDir"`
Labels map[string]string `json:"Labels"`
}
type ConfigSchema struct {
Config ConfigObject `json:"config"`
History []ImageHistoryItem `json:"history"`
}
func getImage(p Prepper) (Image, error) {
output.PrintToStdErr("Retrieving image %s from source %s\n", p.GetSource(), p.Name())
imgPath, err := p.GetFileSystem()
if err != nil {
return Image{}, err
}
config, err := p.GetConfig()
if err != nil {
logrus.Error("Error retrieving History: ", err)
}
logrus.Infof("Finished prepping image %s", p.GetSource())
return Image{
Source: p.GetSource(),
FSPath: imgPath,
Config: config,
}, nil
}
func getImageFromTar(tarPath string) (string, error) {
logrus.Info("Extracting image tar to obtain image file system")
tempPath, err := ioutil.TempDir("", ".container-diff")
if err != nil {
return "", err
}
return tempPath, unpackDockerSave(tarPath, tempPath)
}
func GetFileSystemFromReference(ref types.ImageReference, imgSrc types.ImageSource, path string, whitelist []string) error {
img, err := ref.NewImage(nil)
if err != nil {
return err
}
defer img.Close()
for _, b := range img.LayerInfos() {
bi, _, err := imgSrc.GetBlob(b)
if err != nil {
return err
}
defer bi.Close()
f, reader, err := compression.DetectCompression(bi)
if err != nil {
return err
}
// Decompress if necessary.
if f != nil {
reader, err = f(reader)
if err != nil {
return err
}
}
tr := tar.NewReader(reader)
if err := unpackTar(tr, path, whitelist); err != nil {
return err
}
}
return nil
}
func getDigestFromReference(ref types.ImageReference, source string) (string, error) {
img, err := ref.NewImage(nil)
if err != nil {
logrus.Errorf("Error referencing image %s from registry: %s", source, err)
return "", errors.New("Could not obtain image digest")
}
defer img.Close()
rawManifest, _, err := img.Manifest()
if err != nil {
logrus.Errorf("Error referencing image %s from registry: %s", source, err)
return "", errors.New("Could not obtain image digest")
}
digest, err := manifest.Digest(rawManifest)
if err != nil {
logrus.Errorf("Error referencing image %s from registry: %s", source, err)
return "", errors.New("Could not obtain image digest")
}
return digest.String(), nil
}
func getConfigFromReference(ref types.ImageReference, source string) (ConfigSchema, error) {
img, err := ref.NewImage(nil)
if err != nil {
logrus.Errorf("Error referencing image %s from registry: %s", source, err)
return ConfigSchema{}, errors.New("Could not obtain image config")
}
defer img.Close()
configBlob, err := img.ConfigBlob()
if err != nil {
logrus.Errorf("Error obtaining config blob for image %s from registry: %s", source, err)
return ConfigSchema{}, errors.New("Could not obtain image config")
}
var config ConfigSchema
err = json.Unmarshal(configBlob, &config)
if err != nil {
logrus.Errorf("Error with config file struct for image %s: %s", source, err)
return ConfigSchema{}, errors.New("Could not obtain image config")
}
return config, nil
}
func CleanupImage(image Image) {
if image.FSPath != "" {
logrus.Infof("Removing image filesystem directory %s from system", image.FSPath)
if err := os.RemoveAll(image.FSPath); err != nil {
logrus.Error(err.Error())
}
}
}
@@ -0,0 +1,69 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
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 util
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/docker/docker/pkg/system"
"github.com/sirupsen/logrus"
)
func GetImageLayers(pathToImage string) []string {
layers := []string{}
contents, err := ioutil.ReadDir(pathToImage)
if err != nil {
logrus.Error(err.Error())
}
for _, file := range contents {
if file.IsDir() {
layers = append(layers, file.Name())
}
}
return layers
}
// copyToFile writes the content of the reader to the specified file
func copyToFile(outfile string, r io.Reader) error {
// We use sequential file access here to avoid depleting the standby list
// on Windows. On Linux, this is a call directly to ioutil.TempFile
tmpFile, err := system.TempFileSequential(filepath.Dir(outfile), ".docker_temp_")
if err != nil {
return err
}
tmpPath := tmpFile.Name()
_, err = io.Copy(tmpFile, r)
tmpFile.Close()
if err != nil {
os.Remove(tmpPath)
return err
}
if err = os.Rename(tmpPath, outfile); err != nil {
os.Remove(tmpPath)
return err
}
return nil
}
@@ -0,0 +1,98 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
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 util
import (
"encoding/json"
"errors"
"github.com/containers/image/docker/tarfile"
"github.com/docker/docker/client"
"github.com/sirupsen/logrus"
"io/ioutil"
"os"
"path/filepath"
)
type TarPrepper struct {
Source string
Client *client.Client
}
func (p TarPrepper) Name() string {
return "Tar Archive"
}
func (p TarPrepper) GetSource() string {
return p.Source
}
func (p TarPrepper) GetImage() (Image, error) {
image, err := getImage(p)
image.Type = ImageTypeTar
return image, err
}
func (p TarPrepper) GetFileSystem() (string, error) {
return getImageFromTar(p.Source)
}
func (p TarPrepper) GetConfig() (ConfigSchema, error) {
tempDir, err := ioutil.TempDir("", ".container-diff")
if err != nil {
return ConfigSchema{}, nil
}
defer os.RemoveAll(tempDir)
f, err := os.Open(p.Source)
if err != nil {
return ConfigSchema{}, err
}
defer f.Close()
if err := UnTar(f, tempDir, nil); err != nil {
return ConfigSchema{}, err
}
var config ConfigSchema
// First open the manifest, then find the referenced config.
manifestPath := filepath.Join(tempDir, "manifest.json")
contents, err := ioutil.ReadFile(manifestPath)
if err != nil {
return ConfigSchema{}, err
}
manifests := []tarfile.ManifestItem{}
if err := json.Unmarshal(contents, &manifests); err != nil {
return ConfigSchema{}, err
}
if len(manifests) != 1 {
return ConfigSchema{}, errors.New("specified tar file contains multiple images")
}
cfgFilename := filepath.Join(tempDir, manifests[0].Config)
file, err := ioutil.ReadFile(cfgFilename)
if err != nil {
logrus.Errorf("Could not read config file %s: %s", cfgFilename, err)
return ConfigSchema{}, errors.New("Could not obtain image config")
}
err = json.Unmarshal(file, &config)
if err != nil {
logrus.Errorf("Could not marshal config file %s: %s", cfgFilename, err)
return ConfigSchema{}, errors.New("Could not obtain image config")
}
return config, nil
}
@@ -0,0 +1,166 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
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 util
import (
"archive/tar"
"github.com/sirupsen/logrus"
"io"
"os"
"path/filepath"
"strings"
)
func unpackTar(tr *tar.Reader, path string, whitelist []string) error {
for {
header, err := tr.Next()
if err == io.EOF {
// end of tar archive
break
}
if err != nil {
logrus.Error("Error getting next tar header")
return err
}
if strings.Contains(header.Name, ".wh.") {
rmPath := filepath.Join(path, header.Name)
// Remove the .wh file if it was extracted.
if _, err := os.Stat(rmPath); !os.IsNotExist(err) {
if err := os.Remove(rmPath); err != nil {
logrus.Error(err)
}
}
// Remove the whited-out path.
newName := strings.Replace(rmPath, ".wh.", "", 1)
if err = os.RemoveAll(newName); err != nil {
logrus.Error(err)
}
continue
}
target := filepath.Join(path, header.Name)
// Make sure the target isn't part of the whitelist
if checkWhitelist(target, whitelist) {
continue
}
mode := header.FileInfo().Mode()
switch header.Typeflag {
// if its a dir and it doesn't exist create it
case tar.TypeDir:
if _, err := os.Stat(target); os.IsNotExist(err) {
if err := os.MkdirAll(target, mode); err != nil {
return err
}
// In some cases, MkdirAll doesn't change the permissions, so run Chmod
if err := os.Chmod(target, mode); err != nil {
return err
}
}
// if it's a file create it
case tar.TypeReg:
// It's possible for a file to be included before the directory it's in is created.
baseDir := filepath.Dir(target)
if _, err := os.Stat(baseDir); os.IsNotExist(err) {
logrus.Debugf("baseDir %s for file %s does not exist. Creating.", baseDir, target)
if err := os.MkdirAll(baseDir, 0755); err != nil {
return err
}
}
// It's possible we end up creating files that can't be overwritten based on their permissions.
// Explicitly delete an existing file before continuing.
if _, err := os.Stat(target); !os.IsNotExist(err) {
logrus.Debugf("Removing %s for overwrite.", target)
if err := os.Remove(target); err != nil {
return err
}
}
currFile, err := os.Create(target)
if err != nil {
logrus.Errorf("Error creating file %s %s", target, err)
return err
}
// manually set permissions on file, since the default umask (022) will interfere
if err = os.Chmod(target, mode); err != nil {
logrus.Errorf("Error updating file permissions on %s", target)
return err
}
_, err = io.Copy(currFile, tr)
if err != nil {
return err
}
currFile.Close()
case tar.TypeSymlink:
// It's possible we end up creating files that can't be overwritten based on their permissions.
// Explicitly delete an existing file before continuing.
if _, err := os.Stat(target); !os.IsNotExist(err) {
logrus.Debugf("Removing %s to create symlink.", target)
if err := os.RemoveAll(target); err != nil {
logrus.Debugf("Unable to remove %s: %s", target, err)
}
}
if err = os.Symlink(header.Linkname, target); err != nil {
logrus.Errorf("Failed to create symlink between %s and %s: %s", header.Linkname, target, err)
}
}
}
return nil
}
func checkWhitelist(target string, whitelist []string) bool {
for _, w := range whitelist {
if HasFilepathPrefix(target, w) {
logrus.Debugf("Not extracting %s, as it has prefix %s which is whitelisted", target, w)
return true
}
}
return false
}
// UnTar takes in a path to a tar file and writes the untarred version to the provided target.
// Only untars one level, does not untar nested tars.
func UnTar(r io.Reader, target string, whitelist []string) error {
if _, ok := os.Stat(target); ok != nil {
os.MkdirAll(target, 0775)
}
tr := tar.NewReader(r)
if err := unpackTar(tr, target, whitelist); err != nil {
return err
}
return nil
}
func IsTar(path string) bool {
return filepath.Ext(path) == ".tar" ||
filepath.Ext(path) == ".tar.gz" ||
filepath.Ext(path) == ".tgz"
}
func CheckTar(image string) bool {
if strings.TrimSuffix(image, ".tar") == image {
return false
}
if _, err := os.Stat(image); err != nil {
logrus.Errorf("%s does not exist", image)
return false
}
return true
}