diff --git a/cmd/config.go b/cmd/config.go index e1fef1e5..94f12dc1 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -19,6 +19,7 @@ import ( "fmt" config "github.com/mudler/luet/pkg/config" + installer "github.com/mudler/luet/pkg/installer" "github.com/spf13/cobra" ) @@ -52,6 +53,23 @@ var configCmd = &cobra.Command{ } } + if len(config.LuetCfg.ConfigProtectConfDir) > 0 { + + // Load config protect configs + installer.LoadConfigProtectConfs(config.LuetCfg) + + fmt.Println("config_protect_confdir:") + for _, dir := range config.LuetCfg.ConfigProtectConfDir { + fmt.Println(" - ", dir) + } + + if len(config.LuetCfg.GetConfigProtectConfFiles()) > 0 { + fmt.Println("protect_conf_files:") + for _, file := range config.LuetCfg.GetConfigProtectConfFiles() { + fmt.Println(" - ", file.String()) + } + } + } }, } diff --git a/cmd/install.go b/cmd/install.go index a9ecf7a0..3834778c 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -80,6 +80,9 @@ var installCmd = &cobra.Command{ Debug("Solver", LuetCfg.GetSolverOptions().CompactString()) + // Load config protect configs + installer.LoadConfigProtectConfs(LuetCfg) + inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{ Concurrency: LuetCfg.GetGeneral().Concurrency, SolverOptions: *LuetCfg.GetSolverOptions(), diff --git a/contrib/config/luet.yaml b/contrib/config/luet.yaml index 9835442d..45f99e8b 100644 --- a/contrib/config/luet.yaml +++ b/contrib/config/luet.yaml @@ -73,7 +73,15 @@ # - /etc/luet/repos.conf.d # # -# --------------------------------------------- +# ------------------------------------------------ +# Config protect configuration files directories. +# ----------------------------------------------- +# Define the list of directories where load +# configuration files with the list of config +# protect paths. +# config_protect_confdir: +# - /etc/luet/config.protect.d +# # System repositories # --------------------------------------------- # In alternative to define repositories files diff --git a/go.mod b/go.mod index a79b8966..b6495019 100644 --- a/go.mod +++ b/go.mod @@ -46,4 +46,4 @@ require ( mvdan.cc/sh/v3 v3.0.0-beta1 ) -replace github.com/docker/docker => github.com/Luet-lab/moby v17.12.0-ce-rc1.0.20200418093736-b2b0766ef22c+incompatible +replace github.com/docker/docker => github.com/Luet-lab/moby v17.12.0-ce-rc1.0.20200605210607-749178b8f80d+incompatible diff --git a/go.sum b/go.sum index 3c6ed3ba..7bd4f729 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/Luet-lab/moby v17.12.0-ce-rc1.0.20200418091245-6e0951be974d+incompati github.com/Luet-lab/moby v17.12.0-ce-rc1.0.20200418091245-6e0951be974d+incompatible/go.mod h1:/XyFFC7lL96pE2kKmar2jd4LKxWzy1MmbiDHV0nK3bU= github.com/Luet-lab/moby v17.12.0-ce-rc1.0.20200418093736-b2b0766ef22c+incompatible h1:0cMtxRtHURUYiWHyJ7FOwd+wH5kPNGjXlDf48ovuLlw= github.com/Luet-lab/moby v17.12.0-ce-rc1.0.20200418093736-b2b0766ef22c+incompatible/go.mod h1:/XyFFC7lL96pE2kKmar2jd4LKxWzy1MmbiDHV0nK3bU= +github.com/Luet-lab/moby v17.12.0-ce-rc1.0.20200605210607-749178b8f80d+incompatible h1:YddBuPhhRLoz7uhSJ3Zm//e62jQeTW/qXEZrk5I4qsk= +github.com/Luet-lab/moby v17.12.0-ce-rc1.0.20200605210607-749178b8f80d+incompatible/go.mod h1:/XyFFC7lL96pE2kKmar2jd4LKxWzy1MmbiDHV0nK3bU= github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= diff --git a/pkg/compiler/artifact.go b/pkg/compiler/artifact.go index ce8c2527..b56fed40 100644 --- a/pkg/compiler/artifact.go +++ b/pkg/compiler/artifact.go @@ -18,6 +18,8 @@ package compiler import ( "archive/tar" "bufio" + "bytes" + "fmt" "io" "io/ioutil" "os" @@ -34,6 +36,7 @@ import ( . "github.com/mudler/luet/pkg/config" "github.com/mudler/luet/pkg/helpers" . "github.com/mudler/luet/pkg/logger" + pkg "github.com/mudler/luet/pkg/package" "github.com/mudler/luet/pkg/solver" "github.com/pkg/errors" yaml "gopkg.in/yaml.v2" @@ -277,8 +280,95 @@ func (a *PackageArtifact) Compress(src string, concurrency int) error { return errors.New("Compression type must be supplied") } +func tarModifierWrapperFunc(dst, path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) { + // If the destination path already exists I rename target file name with postfix. + var destPath string + + // Read data. TODO: We need change archive callback to permit to return a Reader + buffer := bytes.Buffer{} + if content != nil { + if _, err := buffer.ReadFrom(content); err != nil { + return nil, nil, err + } + } + + // If file is not present on archive but is defined on mods + // I receive the callback. Prevent nil exception. + if header != nil { + switch header.Typeflag { + case tar.TypeReg: + destPath = filepath.Join(dst, path) + default: + // Nothing to do. I return original reader + return header, buffer.Bytes(), nil + } + + // Check if exists + if helpers.Exists(destPath) { + for i := 1; i < 1000; i++ { + name := filepath.Join(filepath.Join(filepath.Dir(path), + fmt.Sprintf("._cfg%04d_%s", i, filepath.Base(path)))) + + if helpers.Exists(name) { + continue + } + Info(fmt.Sprintf("Found protected file %s. Creating %s.", destPath, + filepath.Join(dst, name))) + return &tar.Header{ + Mode: header.Mode, + Typeflag: header.Typeflag, + PAXRecords: header.PAXRecords, + Name: name, + }, buffer.Bytes(), nil + } + } + } + + return header, buffer.Bytes(), nil +} + +func (a *PackageArtifact) GetProtectFiles() []string { + ans := []string{} + + if LuetCfg.GetConfigProtectConfFiles() != nil && len(LuetCfg.GetConfigProtectConfFiles()) > 0 { + + for _, file := range a.Files { + for _, conf := range LuetCfg.GetConfigProtectConfFiles() { + for _, dir := range conf.Directories { + // Note file is without / at begin. + if strings.HasPrefix("/"+file, filepath.Clean(dir)) { + // docker archive modifier works with path without / at begin. + ans = append(ans, file) + goto nextFile + } + } + } + + if a.CompileSpec.GetPackage().HasAnnotation(string(pkg.ConfigProtectAnnnotation)) { + dir, ok := a.CompileSpec.GetPackage().GetAnnotations()[string(pkg.ConfigProtectAnnnotation)] + if ok { + if strings.HasPrefix("/"+file, filepath.Clean(dir)) { + ans = append(ans, file) + goto nextFile + } + } + } + + nextFile: + } + } + + return ans +} + // Unpack Untar and decompress (TODO) to the given path func (a *PackageArtifact) Unpack(dst string, keepPerms bool) error { + + // Create + protectedFiles := a.GetProtectFiles() + + tarModifier := helpers.NewTarModifierWrapper(dst, tarModifierWrapperFunc) + switch a.CompressionType { case GZip: // Create the uncompressed archive @@ -307,15 +397,16 @@ func (a *PackageArtifact) Unpack(dst string, keepPerms bool) error { return errors.Wrap(err, "Cannot copy to "+a.GetPath()+".uncompressed") } - err = helpers.Untar(a.GetPath()+".uncompressed", dst, - LuetCfg.GetGeneral().SameOwner) + err = helpers.UntarProtect(a.GetPath()+".uncompressed", dst, + LuetCfg.GetGeneral().SameOwner, protectedFiles, tarModifier) if err != nil { return err } return nil // Defaults to tar only (covers when "none" is supplied) default: - return helpers.Untar(a.GetPath(), dst, LuetCfg.GetGeneral().SameOwner) + return helpers.UntarProtect(a.GetPath(), dst, LuetCfg.GetGeneral().SameOwner, + protectedFiles, tarModifier) } return errors.New("Compression type must be supplied") } diff --git a/pkg/config/config.go b/pkg/config/config.go index 695020ad..44aa71a3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -200,9 +200,12 @@ type LuetConfig struct { System LuetSystemConfig `mapstructure:"system"` Solver LuetSolverOptions `mapstructure:"solver"` - RepositoriesConfDir []string `mapstructure:"repos_confdir"` - CacheRepositories []LuetRepository `mapstructure:"repetitors"` - SystemRepositories []LuetRepository `mapstructure:"repositories"` + RepositoriesConfDir []string `mapstructure:"repos_confdir"` + ConfigProtectConfDir []string `mapstructure:"config_protect_confdir"` + CacheRepositories []LuetRepository `mapstructure:"repetitors"` + SystemRepositories []LuetRepository `mapstructure:"repositories"` + + ConfigProtectConfFiles []ConfigProtectConfFile } func NewLuetConfig(viper *v.Viper) *LuetConfig { @@ -211,7 +214,7 @@ func NewLuetConfig(viper *v.Viper) *LuetConfig { } GenDefault(viper) - return &LuetConfig{Viper: viper} + return &LuetConfig{Viper: viper, ConfigProtectConfFiles: nil} } func GenDefault(viper *v.Viper) { @@ -244,6 +247,7 @@ func GenDefault(viper *v.Viper) { viper.SetDefault("system.pkgs_cache_path", "packages") viper.SetDefault("repos_confdir", []string{"/etc/luet/repos.conf.d"}) + viper.SetDefault("config_protect_confdir", []string{"/etc/luet/config.protect.d"}) viper.SetDefault("cache_repositories", []string{}) viper.SetDefault("system_repositories", []string{}) @@ -273,6 +277,18 @@ func (c *LuetConfig) GetSolverOptions() *LuetSolverOptions { return &c.Solver } +func (c *LuetConfig) GetConfigProtectConfFiles() []ConfigProtectConfFile { + return c.ConfigProtectConfFiles +} + +func (c *LuetConfig) AddConfigProtectConfFile(file *ConfigProtectConfFile) { + if c.ConfigProtectConfFiles == nil { + c.ConfigProtectConfFiles = []ConfigProtectConfFile{*file} + } else { + c.ConfigProtectConfFiles = append(c.ConfigProtectConfFiles, *file) + } +} + func (c *LuetConfig) GetSystemRepository(name string) (*LuetRepository, error) { var ans *LuetRepository = nil diff --git a/pkg/config/config_protect.go b/pkg/config/config_protect.go new file mode 100644 index 00000000..41c37e2b --- /dev/null +++ b/pkg/config/config_protect.go @@ -0,0 +1,41 @@ +// Copyright © 2019-2020 Ettore Di Giacinto +// Daniele Rondina +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package config + +import ( + "fmt" +) + +type ConfigProtectConfFile struct { + Filename string + + Name string `mapstructure:"name" yaml:"name" json:"name"` + Directories []string `mapstructure:"dirs" yaml:"dirs" json:"dirs"` +} + +func NewConfigProtectConfFile(filename string) *ConfigProtectConfFile { + return &ConfigProtectConfFile{ + Filename: filename, + Name: "", + Directories: []string{}, + } +} + +func (c *ConfigProtectConfFile) String() string { + return fmt.Sprintf("[%s] filename: %s, dirs: %s", c.Name, c.Filename, + c.Directories) +} diff --git a/pkg/helpers/archive.go b/pkg/helpers/archive.go index 941b7a89..8f2ba247 100644 --- a/pkg/helpers/archive.go +++ b/pkg/helpers/archive.go @@ -17,6 +17,7 @@ package helpers import ( "archive/tar" + "bytes" "io" "os" "path/filepath" @@ -49,6 +50,146 @@ func Tar(src, dest string) error { return err } +type TarModifierWrapperFunc func(path, dst string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) +type TarModifierWrapper struct { + DestinationPath string + Modifier TarModifierWrapperFunc +} + +func NewTarModifierWrapper(dst string, modifier TarModifierWrapperFunc) *TarModifierWrapper { + return &TarModifierWrapper{ + DestinationPath: dst, + Modifier: modifier, + } +} + +func (m *TarModifierWrapper) GetModifier() archive.TarModifierFunc { + return func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) { + return m.Modifier(m.DestinationPath, path, header, content) + } +} + +func UntarProtect(src, dst string, sameOwner bool, protectedFiles []string, modifier *TarModifierWrapper) error { + var ans error + + if len(protectedFiles) <= 0 { + return Untar(src, dst, sameOwner) + } + + // POST: we have files to protect. I create a ReplaceFileTarWrapper + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + // Create modifier map + mods := make(map[string]archive.TarModifierFunc) + for _, file := range protectedFiles { + mods[file] = modifier.GetModifier() + } + + if sameOwner { + // PRE: i have root privileged. + + replacerArchive := archive.ReplaceFileTarWrapper(in, mods) + + opts := &archive.TarOptions{ + // NOTE: NoLchown boolean is used for chmod of the symlink + // Probably it's needed set this always to true. + NoLchown: true, + ExcludePatterns: []string{"dev/"}, // prevent 'operation not permitted' + ContinueOnError: true, + } + + ans = archive.Untar(replacerArchive, dst, opts) + } else { + ans = unTarIgnoreOwner(dst, in, mods) + } + + return ans +} + +func unTarIgnoreOwner(dest string, in io.ReadCloser, mods map[string]archive.TarModifierFunc) error { + tr := tar.NewReader(in) + for { + header, err := tr.Next() + + var data []byte + var headerReplaced = false + + switch { + case err == io.EOF: + goto tarEof + case err != nil: + return err + case header == nil: + continue + } + + // the target location where the dir/file should be created + target := filepath.Join(dest, header.Name) + if mods != nil { + modifier, ok := mods[header.Name] + if ok { + header, data, err = modifier(header.Name, header, tr) + if err != nil { + return err + } + + // Override target path + target = filepath.Join(dest, header.Name) + headerReplaced = true + } + + } + + // Check the file type + switch header.Typeflag { + + // if its a dir and it doesn't exist create it + case tar.TypeDir: + if _, err := os.Stat(target); err != nil { + if err := os.MkdirAll(target, 0755); err != nil { + return err + } + } + + // handle creation of file + case tar.TypeReg: + + f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + if err != nil { + return err + } + + // copy over contents + if headerReplaced { + _, err = io.Copy(f, bytes.NewReader(data)) + } else { + _, err = io.Copy(f, tr) + } + if err != nil { + return err + } + + // manually close here after each file operation; defering would cause each + // file close to wait until all operations have completed. + f.Close() + + case tar.TypeSymlink: + source := header.Linkname + err := os.Symlink(source, target) + if err != nil { + return err + } + } + } +tarEof: + + return nil +} + // Untar just a wrapper around the docker functions func Untar(src, dest string, sameOwner bool) error { var ans error @@ -72,62 +213,7 @@ func Untar(src, dest string, sameOwner bool) error { ans = archive.Untar(in, dest, opts) } else { - - // TODO: replace with https://github.com/mholt/archiver ? - var fileReader io.ReadCloser = in - - tr := tar.NewReader(fileReader) - for { - header, err := tr.Next() - - switch { - case err == io.EOF: - goto tarEof - case err != nil: - return err - case header == nil: - continue - } - - // the target location where the dir/file should be created - target := filepath.Join(dest, header.Name) - - // Check the file type - switch header.Typeflag { - - // if its a dir and it doesn't exist create it - case tar.TypeDir: - if _, err := os.Stat(target); err != nil { - if err := os.MkdirAll(target, 0755); err != nil { - return err - } - } - - // handle creation of file - case tar.TypeReg: - f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) - if err != nil { - return err - } - - // copy over contents - if _, err := io.Copy(f, tr); err != nil { - return err - } - - // manually close here after each file operation; defering would cause each - // file close to wait until all operations have completed. - f.Close() - - case tar.TypeSymlink: - source := header.Linkname - err := os.Symlink(source, target) - if err != nil { - return err - } - } - } - tarEof: + ans = unTarIgnoreOwner(dest, in, nil) } return ans diff --git a/pkg/helpers/archive_test.go b/pkg/helpers/archive_test.go new file mode 100644 index 00000000..e2b9fd33 --- /dev/null +++ b/pkg/helpers/archive_test.go @@ -0,0 +1,134 @@ +// Copyright © 2019-2020 Ettore Di Giacinto +// Daniele Rondina +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package helpers_test + +import ( + "archive/tar" + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/docker/docker/pkg/archive" + . "github.com/mudler/luet/pkg/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +// Code from moby/moby pkg/archive/archive_test +func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) { + fileData := []byte("fooo") + for n := 0; n < numberOfFiles; n++ { + fileName := fmt.Sprintf("file-%d", n) + if err := ioutil.WriteFile(filepath.Join(targetPath, fileName), fileData, 0700); err != nil { + return 0, err + } + if makeLinks { + if err := os.Link(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil { + return 0, err + } + } + } + totalSize := numberOfFiles * len(fileData) + return totalSize, nil +} + +func tarModifierWrapperFunc(dst, path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) { + // If the destination path already exists I rename target file name with postfix. + var basePath string + + // Read data. TODO: We need change archive callback to permit to return a Reader + buffer := bytes.Buffer{} + if content != nil { + if _, err := buffer.ReadFrom(content); err != nil { + return nil, nil, err + } + } + + if header != nil { + + switch header.Typeflag { + case tar.TypeReg: + basePath = filepath.Base(path) + default: + // Nothing to do. I return original reader + return header, buffer.Bytes(), nil + } + + if basePath == "file-0" { + name := filepath.Join(filepath.Join(filepath.Dir(path), fmt.Sprintf("._cfg%04d_%s", 1, basePath))) + return &tar.Header{ + Mode: header.Mode, + Typeflag: header.Typeflag, + PAXRecords: header.PAXRecords, + Name: name, + }, buffer.Bytes(), nil + } else if basePath == "file-1" { + return header, []byte("newcontent"), nil + } + + // else file not present + } + + return header, buffer.Bytes(), nil +} + +var _ = Describe("Helpers Archive", func() { + Context("Untar Protect", func() { + + It("Detect existing and not-existing files", func() { + + archiveSourceDir, err := ioutil.TempDir("", "archive-source") + Expect(err).ToNot(HaveOccurred()) + defer os.RemoveAll(archiveSourceDir) + + _, err = prepareUntarSourceDirectory(10, archiveSourceDir, false) + Expect(err).ToNot(HaveOccurred()) + + targetDir, err := ioutil.TempDir("", "archive-target") + Expect(err).ToNot(HaveOccurred()) + // defer os.RemoveAll(targetDir) + + sourceArchive, err := archive.TarWithOptions(archiveSourceDir, &archive.TarOptions{}) + Expect(err).ToNot(HaveOccurred()) + defer sourceArchive.Close() + + tarModifier := NewTarModifierWrapper(targetDir, tarModifierWrapperFunc) + mods := make(map[string]archive.TarModifierFunc) + mods["file-0"] = tarModifier.GetModifier() + mods["file-1"] = tarModifier.GetModifier() + mods["file-9999"] = tarModifier.GetModifier() + + replacerArchive := archive.ReplaceFileTarWrapper(sourceArchive, mods) + //replacerArchive := archive.ReplaceFileTarWrapper(sourceArchive, mods) + opts := &archive.TarOptions{ + // NOTE: NoLchown boolean is used for chmod of the symlink + // Probably it's needed set this always to true. + NoLchown: true, + ExcludePatterns: []string{"dev/"}, // prevent 'operation not permitted' + ContinueOnError: true, + } + + err = archive.Untar(replacerArchive, targetDir, opts) + Expect(err).ToNot(HaveOccurred()) + + Expect(Exists(filepath.Join(targetDir, "._cfg0001_file-0"))).Should(Equal(true)) + }) + }) +}) diff --git a/pkg/installer/config_protect.go b/pkg/installer/config_protect.go new file mode 100644 index 00000000..88de742b --- /dev/null +++ b/pkg/installer/config_protect.go @@ -0,0 +1,86 @@ +// Copyright © 2019-2020 Ettore Di Giacinto +// Daniele Rondina +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package installer + +import ( + "io/ioutil" + "path" + "regexp" + + "github.com/ghodss/yaml" + + . "github.com/mudler/luet/pkg/config" + . "github.com/mudler/luet/pkg/logger" +) + +func LoadConfigProtectConfs(c *LuetConfig) error { + var regexConfs = regexp.MustCompile(`.yml$`) + + for _, cdir := range c.ConfigProtectConfDir { + Debug("Parsing Config Protect Directory", cdir, "...") + + files, err := ioutil.ReadDir(cdir) + if err != nil { + Debug("Skip dir", cdir, ":", err.Error()) + continue + } + + for _, file := range files { + if file.IsDir() { + continue + } + + if !regexConfs.MatchString(file.Name()) { + Debug("File", file.Name(), "skipped.") + continue + } + + content, err := ioutil.ReadFile(path.Join(cdir, file.Name())) + if err != nil { + Warning("On read file", file.Name(), ":", err.Error()) + Warning("File", file.Name(), "skipped.") + continue + } + + r, err := LoadConfigProtectConFile(file.Name(), content) + if err != nil { + Warning("On parse file", file.Name(), ":", err.Error()) + Warning("File", file.Name(), "skipped.") + continue + } + + if r.Name == "" || len(r.Directories) == 0 { + Warning("Invalid config protect file", file.Name()) + Warning("File", file.Name(), "skipped.") + continue + } + + c.AddConfigProtectConfFile(r) + } + } + return nil + +} + +func LoadConfigProtectConFile(filename string, data []byte) (*ConfigProtectConfFile, error) { + ans := NewConfigProtectConfFile(filename) + err := yaml.Unmarshal(data, &ans) + if err != nil { + return nil, err + } + return ans, nil +} diff --git a/pkg/package/annotations.go b/pkg/package/annotations.go new file mode 100644 index 00000000..583d4b93 --- /dev/null +++ b/pkg/package/annotations.go @@ -0,0 +1,23 @@ +// Copyright © 2019-2020 Ettore Di Giacinto +// Daniele Rondina +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package pkg + +type AnnotationKey string + +const ( + ConfigProtectAnnnotation AnnotationKey = "config_protect" +) diff --git a/tests/fixtures/config_protect/a/build.yaml b/tests/fixtures/config_protect/a/build.yaml new file mode 100644 index 00000000..d7567644 --- /dev/null +++ b/tests/fixtures/config_protect/a/build.yaml @@ -0,0 +1,9 @@ +image: "alpine" +prelude: + - echo foo > /test + - echo bar > /test2 +steps: + - echo c > /c + - echo c > /cd + - mkdir /etc/a + - echo config > /etc/a/conf diff --git a/tests/fixtures/config_protect/a/definition.yaml b/tests/fixtures/config_protect/a/definition.yaml new file mode 100644 index 00000000..6180c317 --- /dev/null +++ b/tests/fixtures/config_protect/a/definition.yaml @@ -0,0 +1,3 @@ +category: "test" +name: "a" +version: "1.0" diff --git a/tests/fixtures/config_protect_annotation/a/build.yaml b/tests/fixtures/config_protect_annotation/a/build.yaml new file mode 100644 index 00000000..5f3a7469 --- /dev/null +++ b/tests/fixtures/config_protect_annotation/a/build.yaml @@ -0,0 +1,9 @@ +image: "alpine" +prelude: + - echo foo > /test + - echo bar > /test2 +steps: + - echo c > /c + - echo c > /cd + - mkdir /opt/etc + - echo config > /opt/etc/conf diff --git a/tests/fixtures/config_protect_annotation/a/definition.yaml b/tests/fixtures/config_protect_annotation/a/definition.yaml new file mode 100644 index 00000000..573bbc5d --- /dev/null +++ b/tests/fixtures/config_protect_annotation/a/definition.yaml @@ -0,0 +1,5 @@ +category: "test" +name: "a" +version: "1.0" +annotations: + config_protect: "/opt/etc" diff --git a/tests/integration/12_config_protect.sh b/tests/integration/12_config_protect.sh new file mode 100755 index 00000000..b072bac0 --- /dev/null +++ b/tests/integration/12_config_protect.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +export LUET_NOLOCK=true + +oneTimeSetUp() { +export tmpdir="$(mktemp -d)" +} + +oneTimeTearDown() { + rm -rf "$tmpdir" +} + +testBuild() { + mkdir $tmpdir/testbuild + luet build --tree "$ROOT_DIR/tests/fixtures/config_protect" --destination $tmpdir/testbuild --compression gzip test/a + buildst=$? + assertEquals 'builds successfully' "$buildst" "0" + assertTrue 'create package' "[ -e '$tmpdir/testbuild/a-test-1.0.package.tar.gz' ]" +} + +testRepo() { + assertTrue 'no repository' "[ ! -e '$tmpdir/testbuild/repository.yaml' ]" + luet create-repo --tree "$ROOT_DIR/tests/fixtures/config_protect" \ + --output $tmpdir/testbuild \ + --packages $tmpdir/testbuild \ + --name "test" \ + --descr "Test Repo" \ + --urls $tmpdir/testrootfs \ + --type disk > /dev/null + + createst=$? + assertEquals 'create repo successfully' "$createst" "0" + assertTrue 'create repository' "[ -e '$tmpdir/testbuild/repository.yaml' ]" +} + +testConfig() { + mkdir $tmpdir/testrootfs + + mkdir $tmpdir/config.protect.d + + cat < $tmpdir/config.protect.d/conf1.yml +name: "protect1" +dirs: +- /etc/ +EOF + + cat < $tmpdir/luet.yaml +general: + debug: true +system: + rootfs: $tmpdir/testrootfs + database_path: "/" + database_engine: "boltdb" +config_protect_confdir: + - $tmpdir/config.protect.d +repositories: + - name: "main" + type: "disk" + enable: true + urls: + - "$tmpdir/testbuild" +EOF + luet config --config $tmpdir/luet.yaml + res=$? + assertEquals 'config test successfully' "$res" "0" +} + + + +testInstall() { + + # Simulate previous installation + mkdir $tmpdir/testrootfs/etc/a -p + echo "fakeconf" > $tmpdir/testrootfs/etc/a/conf + + luet install --config $tmpdir/luet.yaml test/a + installst=$? + assertEquals 'install test successfully' "$installst" "0" + + + # Simulate config protect + assertTrue 'package A installed' "[ -e '$tmpdir/testrootfs/c' ]" + assertTrue 'config protect created' "[ -e '$tmpdir/testrootfs/etc/a/._cfg0001_conf' ]" +} + + +testUnInstall() { + luet uninstall --full --config $tmpdir/luet.yaml test/a + installst=$? + assertEquals 'uninstall test successfully' "$installst" "0" + assertTrue 'package uninstalled' "[ ! -e '$tmpdir/testrootfs/c' ]" + # TODO: we need remove it or not?? + assertTrue 'config protect created' "[ -e '$tmpdir/testrootfs/etc/a/._cfg0001_conf' ]" +} + + +testCleanup() { + luet cleanup --config $tmpdir/luet.yaml + installst=$? + assertEquals 'install test successfully' "$installst" "0" + assertTrue 'package installed' "[ ! -e '$tmpdir/testrootfs/packages/a-test-1.0.package.tar.gz' ]" +} + +# Load shUnit2. +. "$ROOT_DIR/tests/integration/shunit2"/shunit2 + diff --git a/tests/integration/13_config_protect_annotation.sh b/tests/integration/13_config_protect_annotation.sh new file mode 100755 index 00000000..c45dfb26 --- /dev/null +++ b/tests/integration/13_config_protect_annotation.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +export LUET_NOLOCK=true + +oneTimeSetUp() { +export tmpdir="$(mktemp -d)" +} + + +oneTimeTearDown() { + rm -rf "$tmpdir" +} + +testBuild() { + mkdir $tmpdir/testbuild + luet build --tree "$ROOT_DIR/tests/fixtures/config_protect_annotation" --destination $tmpdir/testbuild --compression gzip test/a + buildst=$? + assertEquals 'builds successfully' "$buildst" "0" + assertTrue 'create package' "[ -e '$tmpdir/testbuild/a-test-1.0.package.tar.gz' ]" +} + +testRepo() { + assertTrue 'no repository' "[ ! -e '$tmpdir/testbuild/repository.yaml' ]" + luet create-repo --tree "$ROOT_DIR/tests/fixtures/config_protect_annotation" \ + --output $tmpdir/testbuild \ + --packages $tmpdir/testbuild \ + --name "test" \ + --descr "Test Repo" \ + --urls $tmpdir/testrootfs \ + --type disk > /dev/null + + createst=$? + assertEquals 'create repo successfully' "$createst" "0" + assertTrue 'create repository' "[ -e '$tmpdir/testbuild/repository.yaml' ]" +} + +testConfig() { + mkdir $tmpdir/testrootfs + + mkdir $tmpdir/config.protect.d + + cat < $tmpdir/config.protect.d/conf1.yml +name: "protect1" +dirs: +- /etc/ +EOF + + cat < $tmpdir/luet.yaml +general: + debug: true +system: + rootfs: $tmpdir/testrootfs + database_path: "/" + database_engine: "boltdb" +config_protect_confdir: + - $tmpdir/config.protect.d +repositories: + - name: "main" + type: "disk" + enable: true + urls: + - "$tmpdir/testbuild" +EOF + luet config --config $tmpdir/luet.yaml + res=$? + assertEquals 'config test successfully' "$res" "0" +} + + + +testInstall() { + # Simulate previous installation + mkdir $tmpdir/testrootfs/opt/etc -p + echo "fakeconf" > $tmpdir/testrootfs/opt/etc/conf + + luet install --config $tmpdir/luet.yaml test/a + installst=$? + assertEquals 'install test successfully' "$installst" "0" + + + # Simulate config protect + assertTrue 'package A installed' "[ -e '$tmpdir/testrootfs/c' ]" + assertTrue 'config protect created' "[ -e '$tmpdir/testrootfs/opt/etc/._cfg0001_conf' ]" +} + + +testUnInstall() { + luet uninstall --full --config $tmpdir/luet.yaml test/a + installst=$? + assertEquals 'uninstall test successfully' "$installst" "0" + assertTrue 'package uninstalled' "[ ! -e '$tmpdir/testrootfs/c' ]" + # TODO: we need remove it or not?? + assertTrue 'config protect created' "[ -e '$tmpdir/testrootfs/opt/etc/._cfg0001_conf' ]" +} + + +testCleanup() { + luet cleanup --config $tmpdir/luet.yaml + installst=$? + assertEquals 'install test successfully' "$installst" "0" + assertTrue 'package installed' "[ ! -e '$tmpdir/testrootfs/packages/a-test-1.0.package.tar.gz' ]" +} + +# Load shUnit2. +. "$ROOT_DIR/tests/integration/shunit2"/shunit2 + diff --git a/vendor/github.com/docker/docker/pkg/archive/archive.go b/vendor/github.com/docker/docker/pkg/archive/archive.go index d179949a..0994fd1a 100644 --- a/vendor/github.com/docker/docker/pkg/archive/archive.go +++ b/vendor/github.com/docker/docker/pkg/archive/archive.go @@ -279,7 +279,9 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi return nil } - header.Name = name + if header.Name == "" { + header.Name = name + } header.Size = int64(len(data)) if err := tarWriter.WriteHeader(header); err != nil { return err diff --git a/vendor/modules.txt b/vendor/modules.txt index 2cf0f3e8..057110a5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -66,7 +66,7 @@ github.com/docker/distribution/manifest/schema1 github.com/docker/distribution/manifest/schema2 github.com/docker/distribution/reference github.com/docker/distribution/registry/api/errcode -# github.com/docker/docker v17.12.0-ce-rc1.0.20200417035958-130b0bc6032c+incompatible => github.com/Luet-lab/moby v17.12.0-ce-rc1.0.20200418093736-b2b0766ef22c+incompatible +# github.com/docker/docker v17.12.0-ce-rc1.0.20200417035958-130b0bc6032c+incompatible => github.com/Luet-lab/moby v17.12.0-ce-rc1.0.20200605210607-749178b8f80d+incompatible github.com/docker/docker/api/types/blkiodev github.com/docker/docker/api/types/container github.com/docker/docker/api/types/filters