mirror of
https://github.com/mudler/luet.git
synced 2025-06-20 12:44:56 +00:00
Introduce an arch field that can be used to filter repositories based on the local architecture. If arch is provided and matching with the current GOARCH then the repository is enabled
1091 lines
31 KiB
Go
1091 lines
31 KiB
Go
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
|
|
//
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
package installer
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
artifact "github.com/mudler/luet/pkg/compiler/types/artifact"
|
|
compression "github.com/mudler/luet/pkg/compiler/types/compression"
|
|
fileHelper "github.com/mudler/luet/pkg/helpers/file"
|
|
"go.uber.org/multierr"
|
|
|
|
"github.com/mudler/luet/pkg/api/core/types"
|
|
"github.com/mudler/luet/pkg/compiler"
|
|
"github.com/mudler/luet/pkg/config"
|
|
"github.com/mudler/luet/pkg/installer/client"
|
|
. "github.com/mudler/luet/pkg/logger"
|
|
|
|
pkg "github.com/mudler/luet/pkg/package"
|
|
tree "github.com/mudler/luet/pkg/tree"
|
|
|
|
"github.com/ghodss/yaml"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
REPOSITORY_METAFILE = "repository.meta.yaml"
|
|
REPOSITORY_SPECFILE = "repository.yaml"
|
|
TREE_TARBALL = "tree.tar"
|
|
COMPILERTREE_TARBALL = "compilertree.tar"
|
|
|
|
REPOFILE_TREE_KEY = "tree"
|
|
REPOFILE_COMPILER_TREE_KEY = "compilertree"
|
|
REPOFILE_META_KEY = "meta"
|
|
|
|
DiskRepositoryType = "disk"
|
|
HttpRepositoryType = "http"
|
|
DockerRepositoryType = "docker"
|
|
)
|
|
|
|
type LuetRepositoryFile struct {
|
|
FileName string `json:"filename"`
|
|
CompressionType compression.Implementation `json:"compressiontype,omitempty"`
|
|
Checksums artifact.Checksums `json:"checksums,omitempty"`
|
|
}
|
|
|
|
type LuetSystemRepository struct {
|
|
*types.LuetRepository
|
|
|
|
Index compiler.ArtifactIndex `json:"index"`
|
|
BuildTree, Tree tree.Builder `json:"-"`
|
|
RepositoryFiles map[string]LuetRepositoryFile `json:"repo_files"`
|
|
Backend compiler.CompilerBackend `json:"-"`
|
|
PushImages bool `json:"-"`
|
|
ForcePush bool `json:"-"`
|
|
|
|
imagePrefix string
|
|
}
|
|
|
|
type LuetSystemRepositoryMetadata struct {
|
|
Index []*artifact.PackageArtifact `json:"index,omitempty"`
|
|
}
|
|
|
|
type LuetSearchModeType int
|
|
|
|
const (
|
|
SLabel = iota
|
|
SRegexPkg = iota
|
|
SRegexLabel = iota
|
|
FileSearch = iota
|
|
)
|
|
|
|
type LuetSearchOpts struct {
|
|
Mode LuetSearchModeType
|
|
}
|
|
|
|
func NewLuetSystemRepositoryMetadata(file string, removeFile bool) (*LuetSystemRepositoryMetadata, error) {
|
|
ans := &LuetSystemRepositoryMetadata{}
|
|
err := ans.ReadFile(file, removeFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ans, nil
|
|
}
|
|
|
|
// SystemRepositories returns the repositories from the local configuration file
|
|
// it filters the available repositories returning the ones that are enabled
|
|
func SystemRepositories(t types.LuetRepositories) Repositories {
|
|
repos := Repositories{}
|
|
for _, repo := range t.Enabled() {
|
|
r := NewSystemRepository(repo)
|
|
repos = append(repos, r)
|
|
}
|
|
return repos
|
|
}
|
|
|
|
// LoadBuildTree loads to the tree the compilation specs from the system repositories
|
|
func LoadBuildTree(t tree.Builder, db pkg.PackageDatabase, c *config.LuetConfig) error {
|
|
var reserr error
|
|
repos := SystemRepositories(c.SystemRepositories)
|
|
for _, r := range repos {
|
|
repodir, err := config.LuetCfg.GetSystem().TempDir(r.Name)
|
|
if err != nil {
|
|
reserr = multierr.Append(reserr, err)
|
|
}
|
|
if err := r.SyncBuildMetadata(repodir); err != nil {
|
|
reserr = multierr.Append(reserr, err)
|
|
}
|
|
|
|
generalRecipe := tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))
|
|
if err := generalRecipe.Load(filepath.Join(repodir, "tree")); err != nil {
|
|
reserr = multierr.Append(reserr, err)
|
|
}
|
|
if err := generalRecipe.GetDatabase().Clone(t.GetDatabase()); err != nil {
|
|
reserr = multierr.Append(reserr, err)
|
|
}
|
|
|
|
r.SetTree(generalRecipe)
|
|
}
|
|
|
|
repos.SyncDatabase(db)
|
|
|
|
return reserr
|
|
}
|
|
|
|
func (m *LuetSystemRepositoryMetadata) WriteFile(path string) error {
|
|
data, err := yaml.Marshal(m)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ioutil.WriteFile(path, data, os.ModePerm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *LuetSystemRepositoryMetadata) ReadFile(file string, removeFile bool) error {
|
|
if file == "" {
|
|
return errors.New("Invalid path for repository metadata")
|
|
}
|
|
|
|
dat, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if removeFile {
|
|
defer os.Remove(file)
|
|
}
|
|
|
|
err = yaml.Unmarshal(dat, m)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *LuetSystemRepositoryMetadata) ToArtifactIndex() (ans compiler.ArtifactIndex) {
|
|
for _, a := range m.Index {
|
|
ans = append(ans, a)
|
|
}
|
|
return
|
|
}
|
|
|
|
func NewDefaultTreeRepositoryFile() LuetRepositoryFile {
|
|
return LuetRepositoryFile{
|
|
FileName: TREE_TARBALL,
|
|
CompressionType: compression.GZip,
|
|
}
|
|
}
|
|
|
|
func NewDefaultCompilerTreeRepositoryFile() LuetRepositoryFile {
|
|
return LuetRepositoryFile{
|
|
FileName: COMPILERTREE_TARBALL,
|
|
CompressionType: compression.GZip,
|
|
}
|
|
}
|
|
|
|
func NewDefaultMetaRepositoryFile() LuetRepositoryFile {
|
|
return LuetRepositoryFile{
|
|
FileName: REPOSITORY_METAFILE + ".tar",
|
|
CompressionType: compression.None,
|
|
}
|
|
}
|
|
|
|
// SetFileName sets the name of the repository file.
|
|
// Each repository can ship arbitrary file that will be downloaded by the client
|
|
// in case of need, this set the filename that the client will pull
|
|
func (f *LuetRepositoryFile) SetFileName(n string) {
|
|
f.FileName = n
|
|
}
|
|
|
|
// GetFileName returns the name of the repository file.
|
|
// Each repository can ship arbitrary file that will be downloaded by the client
|
|
// in case of need, this gets the filename that the client will pull
|
|
func (f *LuetRepositoryFile) GetFileName() string {
|
|
return f.FileName
|
|
}
|
|
|
|
// SetCompressionType sets the compression type of the repository file.
|
|
// Each repository can ship arbitrary file that will be downloaded by the client
|
|
// in case of need, this sets the compression type that the client will use to uncompress the artifact
|
|
func (f *LuetRepositoryFile) SetCompressionType(c compression.Implementation) {
|
|
f.CompressionType = c
|
|
}
|
|
|
|
// GetCompressionType gets the compression type of the repository file.
|
|
// Each repository can ship arbitrary file that will be downloaded by the client
|
|
// in case of need, this gets the compression type that the client will use to uncompress the artifact
|
|
func (f *LuetRepositoryFile) GetCompressionType() compression.Implementation {
|
|
return f.CompressionType
|
|
}
|
|
|
|
// SetChecksums sets the checksum of the repository file.
|
|
// Each repository can ship arbitrary file that will be downloaded by the client
|
|
// in case of need, this sets the checksums that the client will use to verify the artifact
|
|
func (f *LuetRepositoryFile) SetChecksums(c artifact.Checksums) {
|
|
f.Checksums = c
|
|
}
|
|
|
|
// GetChecksums gets the checksum of the repository file.
|
|
// Each repository can ship arbitrary file that will be downloaded by the client
|
|
// in case of need, this gets the checksums that the client will use to verify the artifact
|
|
func (f *LuetRepositoryFile) GetChecksums() artifact.Checksums {
|
|
return f.Checksums
|
|
}
|
|
|
|
// GenerateRepository generates a new repository from the given argument.
|
|
// If the repository is of the docker type, it will also push the package images.
|
|
// In case the repository is local, it will build the package Index
|
|
func GenerateRepository(p ...RepositoryOption) (*LuetSystemRepository, error) {
|
|
c := RepositoryConfig{}
|
|
c.Apply(p...)
|
|
|
|
btr := tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))
|
|
runtimeTree := pkg.NewInMemoryDatabase(false)
|
|
|
|
tempTree := pkg.NewInMemoryDatabase(false)
|
|
temptr := tree.NewInstallerRecipe(tempTree)
|
|
|
|
for _, treeDir := range c.Tree {
|
|
if err := temptr.Load(treeDir); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := btr.Load(treeDir); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// 2: if fromRepo, build a new tree like the compiler is doing and use it to source the above specs,
|
|
// instead of local tree
|
|
|
|
repodb := pkg.NewInMemoryDatabase(false)
|
|
generalRecipe := tree.NewCompilerRecipe(repodb)
|
|
|
|
if c.FromRepository {
|
|
if err := LoadBuildTree(generalRecipe, repodb, c.config); err != nil {
|
|
Warning("errors while loading trees from repositories", err.Error())
|
|
}
|
|
|
|
if err := repodb.Clone(tempTree); err != nil {
|
|
Warning("errors while cloning trees from repositories", err.Error())
|
|
}
|
|
|
|
}
|
|
|
|
// Pick only atoms in db which have a real metadata for runtime db (tr)
|
|
for _, p := range tempTree.World() {
|
|
if _, err := os.Stat(filepath.Join(c.Src, p.GetMetadataFilePath())); err == nil {
|
|
runtimeTree.CreatePackage(p)
|
|
}
|
|
}
|
|
|
|
// Load packages from metadata files if not present already.
|
|
var ff = func(currentpath string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Only those which are metadata
|
|
if !strings.HasSuffix(info.Name(), pkg.PackageMetaSuffix) {
|
|
return nil
|
|
}
|
|
|
|
dat, err := ioutil.ReadFile(currentpath)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
art, err := artifact.NewPackageArtifactFromYaml(dat)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if _, err := runtimeTree.FindPackage(art.CompileSpec.Package); err != nil && art.CompileSpec.Package.Name != "" {
|
|
Debug("Adding", art.CompileSpec.Package.HumanReadableString(), "from metadata file", currentpath)
|
|
if art.Runtime != nil && art.Runtime.Name != "" {
|
|
runtimeTree.CreatePackage(art.Runtime)
|
|
} else {
|
|
// We don't have runtime at this point. So we import the package as is
|
|
r := []*pkg.DefaultPackage{}
|
|
p := art.CompileSpec.Package.Clone()
|
|
p.Requires(r)
|
|
p.SetProvides(r)
|
|
p.Conflicts(r)
|
|
runtimeTree.CreatePackage(p)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
if c.FromMetadata {
|
|
// Best effort
|
|
filepath.Walk(c.Src, ff)
|
|
}
|
|
|
|
repo := &LuetSystemRepository{
|
|
LuetRepository: types.NewLuetRepository(c.Name, c.Type, c.Description, c.Urls, c.Priority, true, false),
|
|
Tree: tree.NewInstallerRecipe(runtimeTree),
|
|
BuildTree: btr,
|
|
RepositoryFiles: map[string]LuetRepositoryFile{},
|
|
PushImages: c.PushImages,
|
|
ForcePush: c.Force,
|
|
Backend: c.CompilerBackend,
|
|
imagePrefix: c.ImagePrefix,
|
|
}
|
|
|
|
if err := repo.initialize(c.Src); err != nil {
|
|
return nil, errors.Wrap(err, "while building repository artifact index")
|
|
}
|
|
|
|
return repo, nil
|
|
}
|
|
|
|
func NewSystemRepository(repo types.LuetRepository) *LuetSystemRepository {
|
|
return &LuetSystemRepository{
|
|
LuetRepository: &repo,
|
|
RepositoryFiles: map[string]LuetRepositoryFile{},
|
|
}
|
|
}
|
|
|
|
func NewLuetSystemRepositoryFromYaml(data []byte, db pkg.PackageDatabase) (*LuetSystemRepository, error) {
|
|
var p *LuetSystemRepository
|
|
err := yaml.Unmarshal(data, &p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.Tree = tree.NewInstallerRecipe(db)
|
|
|
|
return p, err
|
|
}
|
|
|
|
func (r *LuetSystemRepository) SetPriority(n int) {
|
|
r.LuetRepository.Priority = n
|
|
}
|
|
|
|
func (r *LuetSystemRepository) initialize(src string) error {
|
|
generator, err := r.getGenerator()
|
|
if err != nil {
|
|
return errors.Wrap(err, "while constructing repository generator")
|
|
}
|
|
art, err := generator.Initialize(src, r.Tree.GetDatabase())
|
|
if err != nil {
|
|
return errors.Wrap(err, "while initializing repository generator")
|
|
}
|
|
// update the repository index
|
|
r.Index = art
|
|
return nil
|
|
}
|
|
|
|
// FileSearch search a pattern among the artifacts in a repository
|
|
func (r *LuetSystemRepository) FileSearch(pattern string) (pkg.Packages, error) {
|
|
var matches pkg.Packages
|
|
reg, err := regexp.Compile(pattern)
|
|
if err != nil {
|
|
return matches, err
|
|
}
|
|
ARTIFACT:
|
|
for _, a := range r.GetIndex() {
|
|
for _, f := range a.Files {
|
|
if reg.MatchString(f) {
|
|
matches = append(matches, a.CompileSpec.GetPackage())
|
|
continue ARTIFACT
|
|
}
|
|
}
|
|
}
|
|
return matches, nil
|
|
}
|
|
|
|
func (r *LuetSystemRepository) GetName() string {
|
|
return r.LuetRepository.Name
|
|
}
|
|
func (r *LuetSystemRepository) GetDescription() string {
|
|
return r.LuetRepository.Description
|
|
}
|
|
|
|
func (r *LuetSystemRepository) GetAuthentication() map[string]string {
|
|
return r.LuetRepository.Authentication
|
|
}
|
|
|
|
func (r *LuetSystemRepository) GetType() string {
|
|
return r.LuetRepository.Type
|
|
}
|
|
|
|
func (r *LuetSystemRepository) SetType(p string) {
|
|
r.LuetRepository.Type = p
|
|
}
|
|
|
|
func (r *LuetSystemRepository) GetVerify() bool {
|
|
return r.LuetRepository.Verify
|
|
}
|
|
|
|
func (r *LuetSystemRepository) SetVerify(p bool) {
|
|
r.LuetRepository.Verify = p
|
|
}
|
|
|
|
func (r *LuetSystemRepository) GetBackend() compiler.CompilerBackend {
|
|
return r.Backend
|
|
}
|
|
func (r *LuetSystemRepository) SetBackend(b compiler.CompilerBackend) {
|
|
r.Backend = b
|
|
}
|
|
|
|
func (r *LuetSystemRepository) SetName(p string) {
|
|
r.LuetRepository.Name = p
|
|
}
|
|
|
|
func (r *LuetSystemRepository) AddUrl(p string) {
|
|
r.LuetRepository.Urls = append(r.LuetRepository.Urls, p)
|
|
}
|
|
func (r *LuetSystemRepository) GetUrls() []string {
|
|
return r.LuetRepository.Urls
|
|
}
|
|
func (r *LuetSystemRepository) SetUrls(urls []string) {
|
|
r.LuetRepository.Urls = urls
|
|
}
|
|
func (r *LuetSystemRepository) GetPriority() int {
|
|
return r.LuetRepository.Priority
|
|
}
|
|
func (r *LuetSystemRepository) GetTreePath() string {
|
|
return r.TreePath
|
|
}
|
|
func (r *LuetSystemRepository) SetTreePath(p string) {
|
|
r.TreePath = p
|
|
}
|
|
func (r *LuetSystemRepository) GetMetaPath() string {
|
|
return r.MetaPath
|
|
}
|
|
func (r *LuetSystemRepository) SetMetaPath(p string) {
|
|
r.MetaPath = p
|
|
}
|
|
func (r *LuetSystemRepository) SetTree(b tree.Builder) {
|
|
r.Tree = b
|
|
}
|
|
func (r *LuetSystemRepository) GetIndex() compiler.ArtifactIndex {
|
|
return r.Index
|
|
}
|
|
func (r *LuetSystemRepository) SetIndex(i compiler.ArtifactIndex) {
|
|
r.Index = i
|
|
}
|
|
func (r *LuetSystemRepository) GetTree() tree.Builder {
|
|
return r.Tree
|
|
}
|
|
func (r *LuetSystemRepository) GetRevision() int {
|
|
return r.LuetRepository.Revision
|
|
}
|
|
func (r *LuetSystemRepository) GetLastUpdate() string {
|
|
return r.LuetRepository.LastUpdate
|
|
}
|
|
func (r *LuetSystemRepository) SetLastUpdate(u string) {
|
|
r.LuetRepository.LastUpdate = u
|
|
}
|
|
func (r *LuetSystemRepository) IncrementRevision() {
|
|
r.LuetRepository.Revision++
|
|
}
|
|
func (r *LuetSystemRepository) SetAuthentication(auth map[string]string) {
|
|
r.LuetRepository.Authentication = auth
|
|
}
|
|
|
|
// BumpRevision bumps the internal repository revision by reading the current one from repospec
|
|
func (r *LuetSystemRepository) BumpRevision(repospec string, resetRevision bool) error {
|
|
if resetRevision {
|
|
r.Revision = 0
|
|
} else {
|
|
if _, err := os.Stat(repospec); !os.IsNotExist(err) {
|
|
// Read existing file for retrieve revision
|
|
spec, err := r.ReadSpecFile(repospec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.Revision = spec.GetRevision()
|
|
}
|
|
}
|
|
r.Revision++
|
|
return nil
|
|
}
|
|
|
|
// AddMetadata adds the repository serialized content into the metadata key of the repository
|
|
// It writes the serialized content to repospec, and writes the repository.meta.yaml file into dst
|
|
func (r *LuetSystemRepository) AddMetadata(repospec, dst string) (*artifact.PackageArtifact, error) {
|
|
// Create Metadata struct and serialized repository
|
|
meta, serialized := r.Serialize()
|
|
|
|
// Create metadata file and repository file
|
|
metaTmpDir, err := config.LuetCfg.GetSystem().TempDir("metadata")
|
|
defer os.RemoveAll(metaTmpDir) // clean up
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error met while creating tempdir for metadata")
|
|
}
|
|
|
|
repoMetaSpec := filepath.Join(metaTmpDir, REPOSITORY_METAFILE)
|
|
|
|
// Create repository.meta.yaml file
|
|
err = meta.WriteFile(repoMetaSpec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a, err := r.AddRepositoryFile(metaTmpDir, REPOFILE_META_KEY, dst, NewDefaultMetaRepositoryFile())
|
|
if err != nil {
|
|
return a, errors.Wrap(err, "Error met while adding archive to repository")
|
|
}
|
|
|
|
data, err := yaml.Marshal(serialized)
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
err = ioutil.WriteFile(repospec, data, os.ModePerm)
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// AddTree adds a tree.Builder with the given key to the repository.
|
|
// It will generate an artifact which will be then embedded in the repository manifest
|
|
// It returns the generated artifacts and an error
|
|
func (r *LuetSystemRepository) AddTree(t tree.Builder, dst, key string, f LuetRepositoryFile) (*artifact.PackageArtifact, error) {
|
|
// Create tree and repository file
|
|
archive, err := config.LuetCfg.GetSystem().TempDir("archive")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error met while creating tempdir for archive")
|
|
}
|
|
defer os.RemoveAll(archive) // clean up
|
|
|
|
if err := t.Save(archive); err != nil {
|
|
return nil, errors.Wrap(err, "Error met while saving the tree")
|
|
}
|
|
|
|
a, err := r.AddRepositoryFile(archive, key, dst, f)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error met while adding archive to repository")
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// AddRepositoryFile adds a path to a key in the repository manifest.
|
|
// The path will be compressed, and a default File has to be passed in case there is no entry into
|
|
// the repository manifest
|
|
func (r *LuetSystemRepository) AddRepositoryFile(src, fileKey, repositoryRoot string, defaults LuetRepositoryFile) (*artifact.PackageArtifact, error) {
|
|
treeFile, err := r.GetRepositoryFile(fileKey)
|
|
if err != nil {
|
|
treeFile = defaults
|
|
// r.SetRepositoryFile(fileKey, treeFile)
|
|
}
|
|
|
|
a := artifact.NewPackageArtifact(filepath.Join(repositoryRoot, treeFile.GetFileName()))
|
|
a.CompressionType = treeFile.GetCompressionType()
|
|
err = a.Compress(src, 1)
|
|
if err != nil {
|
|
return a, errors.Wrap(err, "Error met while creating package archive")
|
|
}
|
|
|
|
err = a.Hash()
|
|
if err != nil {
|
|
return a, errors.Wrap(err, "Failed generating checksums for tree")
|
|
}
|
|
// Update the tree name with the name created by compression selected.
|
|
treeFile.SetChecksums(a.Checksums)
|
|
treeFile.SetFileName(path.Base(a.Path))
|
|
|
|
r.SetRepositoryFile(fileKey, treeFile)
|
|
|
|
return a, nil
|
|
}
|
|
|
|
func (r *LuetSystemRepository) GetRepositoryFile(name string) (LuetRepositoryFile, error) {
|
|
ans, ok := r.RepositoryFiles[name]
|
|
if ok {
|
|
return ans, nil
|
|
}
|
|
return ans, errors.New("Repository file " + name + " not found!")
|
|
}
|
|
func (r *LuetSystemRepository) SetRepositoryFile(name string, f LuetRepositoryFile) {
|
|
r.RepositoryFiles[name] = f
|
|
}
|
|
|
|
func (r *LuetSystemRepository) ReadSpecFile(file string) (*LuetSystemRepository, error) {
|
|
dat, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error reading file "+file)
|
|
}
|
|
var repo *LuetSystemRepository
|
|
repo, err = NewLuetSystemRepositoryFromYaml(dat, pkg.NewInMemoryDatabase(false))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error reading repository from file "+file)
|
|
}
|
|
|
|
// Check if mandatory key are present
|
|
_, err = repo.GetRepositoryFile(REPOFILE_TREE_KEY)
|
|
if err != nil {
|
|
return nil, errors.New("Invalid repository without the " + REPOFILE_TREE_KEY + " key file.")
|
|
}
|
|
_, err = repo.GetRepositoryFile(REPOFILE_META_KEY)
|
|
if err != nil {
|
|
return nil, errors.New("Invalid repository without the " + REPOFILE_META_KEY + " key file.")
|
|
}
|
|
|
|
return repo, err
|
|
}
|
|
|
|
type RepositoryGenerator interface {
|
|
Generate(*LuetSystemRepository, string, bool) error
|
|
Initialize(string, pkg.PackageDatabase) ([]*artifact.PackageArtifact, error)
|
|
}
|
|
|
|
func (r *LuetSystemRepository) getGenerator() (RepositoryGenerator, error) {
|
|
var rg RepositoryGenerator
|
|
switch r.GetType() {
|
|
case DiskRepositoryType, HttpRepositoryType:
|
|
rg = &localRepositoryGenerator{}
|
|
case DockerRepositoryType:
|
|
rg = &dockerRepositoryGenerator{
|
|
b: r.Backend,
|
|
imagePrefix: r.imagePrefix,
|
|
imagePush: r.PushImages,
|
|
force: r.ForcePush,
|
|
}
|
|
default:
|
|
return nil, errors.New("invalid repository type")
|
|
}
|
|
return rg, nil
|
|
}
|
|
|
|
// Write writes the repository metadata to the supplied destination
|
|
func (r *LuetSystemRepository) Write(dst string, resetRevision, force bool) error {
|
|
rg, err := r.getGenerator()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return rg.Generate(r, dst, resetRevision)
|
|
}
|
|
|
|
func (r *LuetSystemRepository) Client() Client {
|
|
switch r.GetType() {
|
|
case DiskRepositoryType:
|
|
return client.NewLocalClient(client.RepoData{Urls: r.GetUrls()})
|
|
case HttpRepositoryType:
|
|
return client.NewHttpClient(
|
|
client.RepoData{
|
|
Urls: r.GetUrls(),
|
|
Authentication: r.GetAuthentication(),
|
|
})
|
|
|
|
case DockerRepositoryType:
|
|
return client.NewDockerClient(
|
|
client.RepoData{
|
|
Urls: r.GetUrls(),
|
|
Authentication: r.GetAuthentication(),
|
|
Verify: r.Verify,
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *LuetSystemRepository) SearchArtefact(p pkg.Package) (*artifact.PackageArtifact, error) {
|
|
for _, a := range r.GetIndex() {
|
|
if a.CompileSpec.GetPackage().Matches(p) {
|
|
return a, nil
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("Not found")
|
|
}
|
|
|
|
func (r *LuetSystemRepository) getRepoFile(c Client, key string) (*artifact.PackageArtifact, error) {
|
|
|
|
treeFile, err := r.GetRepositoryFile(key)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "key %s not present in the repository", key)
|
|
}
|
|
|
|
// Get Tree
|
|
downloadedTreeFile, err := c.DownloadFile(treeFile.GetFileName())
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "While downloading "+treeFile.GetFileName())
|
|
}
|
|
//defer os.Remove(downloadedTreeFile)
|
|
|
|
treeFileArtifact := artifact.NewPackageArtifact(downloadedTreeFile)
|
|
treeFileArtifact.Checksums = treeFile.GetChecksums()
|
|
treeFileArtifact.CompressionType = treeFile.GetCompressionType()
|
|
|
|
err = treeFileArtifact.Verify()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "file integrity check failure")
|
|
}
|
|
|
|
return treeFileArtifact, nil
|
|
|
|
}
|
|
|
|
func (r *LuetSystemRepository) SyncBuildMetadata(path string) error {
|
|
|
|
repo, err := r.Sync(false)
|
|
if err != nil {
|
|
return errors.Wrap(err, "while syncronizing repository")
|
|
}
|
|
|
|
c := repo.Client()
|
|
if c == nil {
|
|
return errors.New("no client could be generated from repository")
|
|
}
|
|
|
|
a, err := repo.getRepoFile(c, REPOFILE_COMPILER_TREE_KEY)
|
|
if err != nil {
|
|
return fmt.Errorf("failed while getting: %s", REPOFILE_COMPILER_TREE_KEY)
|
|
}
|
|
|
|
defer os.RemoveAll(a.Path)
|
|
|
|
if err := a.Unpack(filepath.Join(path, "tree"), false); err != nil {
|
|
return errors.Wrapf(err, "while unpacking: %s", REPOFILE_COMPILER_TREE_KEY)
|
|
}
|
|
|
|
for _, ai := range repo.GetTree().GetDatabase().World() {
|
|
// Retrieve remote repository.yaml for retrieve revision and date
|
|
file, err := c.DownloadFile(ai.GetMetadataFilePath())
|
|
if err != nil {
|
|
return errors.Wrapf(err, "while downloading metadata for %s", ai.HumanReadableString())
|
|
}
|
|
if err := fileHelper.Move(file, filepath.Join(path, ai.GetMetadataFilePath())); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *LuetSystemRepository) Sync(force bool) (*LuetSystemRepository, error) {
|
|
var repoUpdated bool = false
|
|
var treefs, metafs string
|
|
aurora := GetAurora()
|
|
|
|
Debug("Sync of the repository", r.Name, "in progress...")
|
|
c := r.Client()
|
|
if c == nil {
|
|
return nil, errors.New("no client could be generated from repository")
|
|
}
|
|
|
|
// Retrieve remote repository.yaml for retrieve revision and date
|
|
file, err := c.DownloadFile(REPOSITORY_SPECFILE)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "While downloading "+REPOSITORY_SPECFILE)
|
|
}
|
|
|
|
repobasedir := config.LuetCfg.GetSystem().GetRepoDatabaseDirPath(r.GetName())
|
|
downloadedRepoMeta, err := r.ReadSpecFile(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Remove temporary file that contains repository.yaml
|
|
// Example: /tmp/HttpClient236052003
|
|
defer os.RemoveAll(file)
|
|
|
|
if r.Cached {
|
|
if !force {
|
|
localRepo, _ := r.ReadSpecFile(filepath.Join(repobasedir, REPOSITORY_SPECFILE))
|
|
if localRepo != nil {
|
|
if localRepo.GetRevision() == downloadedRepoMeta.GetRevision() &&
|
|
localRepo.GetLastUpdate() == downloadedRepoMeta.GetLastUpdate() {
|
|
repoUpdated = true
|
|
}
|
|
}
|
|
}
|
|
if r.GetTreePath() == "" {
|
|
treefs = filepath.Join(repobasedir, "treefs")
|
|
} else {
|
|
treefs = r.GetTreePath()
|
|
}
|
|
if r.GetMetaPath() == "" {
|
|
metafs = filepath.Join(repobasedir, "metafs")
|
|
} else {
|
|
metafs = r.GetMetaPath()
|
|
}
|
|
|
|
} else {
|
|
treefs, err = config.LuetCfg.GetSystem().TempDir("treefs")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error met while creating tempdir for rootfs")
|
|
}
|
|
metafs, err = config.LuetCfg.GetSystem().TempDir("metafs")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error met whilte creating tempdir for metafs")
|
|
}
|
|
}
|
|
|
|
// treeFile and metaFile must be present, they aren't optional
|
|
if !repoUpdated {
|
|
|
|
treeFileArtifact, err := downloadedRepoMeta.getRepoFile(c, REPOFILE_TREE_KEY)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "while fetching '%s'", REPOFILE_TREE_KEY)
|
|
}
|
|
defer os.Remove(treeFileArtifact.Path)
|
|
|
|
Debug("Tree tarball for the repository " + r.GetName() + " downloaded correctly.")
|
|
|
|
metaFileArtifact, err := downloadedRepoMeta.getRepoFile(c, REPOFILE_META_KEY)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "while fetching '%s'", REPOFILE_META_KEY)
|
|
}
|
|
defer os.Remove(metaFileArtifact.Path)
|
|
|
|
Debug("Metadata tarball for the repository " + r.GetName() + " downloaded correctly.")
|
|
|
|
if r.Cached {
|
|
// Copy updated repository.yaml file to repo dir now that the tree is synced.
|
|
err = fileHelper.CopyFile(file, filepath.Join(repobasedir, REPOSITORY_SPECFILE))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error on update "+REPOSITORY_SPECFILE)
|
|
}
|
|
// Remove previous tree
|
|
os.RemoveAll(treefs)
|
|
// Remove previous meta dir
|
|
os.RemoveAll(metafs)
|
|
}
|
|
Debug("Decompress tree of the repository " + r.Name + "...")
|
|
|
|
err = treeFileArtifact.Unpack(treefs, true)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error met while unpacking tree")
|
|
}
|
|
|
|
// FIXME: It seems that tar with only one file doesn't create destination
|
|
// directory. I create directory directly for now.
|
|
os.MkdirAll(metafs, os.ModePerm)
|
|
err = metaFileArtifact.Unpack(metafs, true)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error met while unpacking metadata")
|
|
}
|
|
|
|
tsec, _ := strconv.ParseInt(downloadedRepoMeta.GetLastUpdate(), 10, 64)
|
|
|
|
InfoC(
|
|
aurora.Bold(
|
|
aurora.Red(":house: Repository "+downloadedRepoMeta.GetName()+" revision: ")).String() +
|
|
aurora.Bold(aurora.Green(downloadedRepoMeta.GetRevision())).String() + " - " +
|
|
aurora.Bold(aurora.Green(time.Unix(tsec, 0).String())).String(),
|
|
)
|
|
|
|
} else {
|
|
Info("Repository", downloadedRepoMeta.GetName(), "is already up to date.")
|
|
}
|
|
|
|
meta, err := NewLuetSystemRepositoryMetadata(
|
|
filepath.Join(metafs, REPOSITORY_METAFILE), false,
|
|
)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "While processing "+REPOSITORY_METAFILE)
|
|
}
|
|
downloadedRepoMeta.SetIndex(meta.ToArtifactIndex())
|
|
|
|
reciper := tree.NewInstallerRecipe(pkg.NewInMemoryDatabase(false))
|
|
err = reciper.Load(treefs)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error met while unpacking rootfs")
|
|
}
|
|
|
|
downloadedRepoMeta.SetTree(reciper)
|
|
downloadedRepoMeta.SetTreePath(treefs)
|
|
|
|
// Copy the local available data to the one which was synced
|
|
// e.g. locally we can override the type (disk), or priority
|
|
// while remotely it could be advertized differently
|
|
r.fill(downloadedRepoMeta)
|
|
|
|
InfoC(
|
|
aurora.Yellow(":information_source:").String() +
|
|
aurora.Magenta("Repository: ").String() +
|
|
aurora.Green(aurora.Bold(downloadedRepoMeta.GetName()).String()).String() +
|
|
aurora.Magenta(" Priority: ").String() +
|
|
aurora.Bold(aurora.Green(downloadedRepoMeta.GetPriority())).String() +
|
|
aurora.Magenta(" Type: ").String() +
|
|
aurora.Bold(aurora.Green(downloadedRepoMeta.GetType())).String(),
|
|
)
|
|
return downloadedRepoMeta, nil
|
|
}
|
|
|
|
func (r *LuetSystemRepository) fill(r2 *LuetSystemRepository) {
|
|
r2.SetUrls(r.GetUrls())
|
|
r2.SetAuthentication(r.GetAuthentication())
|
|
r2.SetType(r.GetType())
|
|
r2.SetPriority(r.GetPriority())
|
|
r2.SetName(r.GetName())
|
|
r2.SetVerify(r.GetVerify())
|
|
}
|
|
|
|
func (r *LuetSystemRepository) Serialize() (*LuetSystemRepositoryMetadata, LuetSystemRepository) {
|
|
|
|
serialized := *r
|
|
serialized.Authentication = nil
|
|
|
|
serialized.Index = compiler.ArtifactIndex{}
|
|
|
|
meta := &LuetSystemRepositoryMetadata{
|
|
Index: []*artifact.PackageArtifact{},
|
|
}
|
|
for _, a := range r.Index {
|
|
cp := *a
|
|
copy := &cp
|
|
copy.Path = filepath.Base(copy.Path)
|
|
meta.Index = append(meta.Index, copy)
|
|
}
|
|
|
|
return meta, serialized
|
|
}
|
|
|
|
func (r Repositories) Len() int { return len(r) }
|
|
func (r Repositories) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
|
func (r Repositories) Less(i, j int) bool {
|
|
return r[i].GetPriority() < r[j].GetPriority()
|
|
}
|
|
|
|
func (r Repositories) World() pkg.Packages {
|
|
cache := map[string]pkg.Package{}
|
|
world := pkg.Packages{}
|
|
|
|
// Get Uniques. Walk in reverse so the definitions of most prio-repo overwrites lower ones
|
|
// In this way, when we will walk again later the deps sorting them by most higher prio we have better chance of success.
|
|
for i := len(r) - 1; i >= 0; i-- {
|
|
for _, p := range r[i].GetTree().GetDatabase().World() {
|
|
cache[p.GetFingerPrint()] = p
|
|
}
|
|
}
|
|
|
|
for _, v := range cache {
|
|
world = append(world, v)
|
|
}
|
|
|
|
return world
|
|
}
|
|
|
|
func (r Repositories) SyncDatabase(d pkg.PackageDatabase) {
|
|
cache := map[string]bool{}
|
|
|
|
// Get Uniques. Walk in reverse so the definitions of most prio-repo overwrites lower ones
|
|
// In this way, when we will walk again later the deps sorting them by most higher prio we have better chance of success.
|
|
for i := len(r) - 1; i >= 0; i-- {
|
|
for _, p := range r[i].GetTree().GetDatabase().World() {
|
|
if _, ok := cache[p.GetFingerPrint()]; !ok {
|
|
cache[p.GetFingerPrint()] = true
|
|
d.CreatePackage(p)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type PackageMatch struct {
|
|
Repo *LuetSystemRepository
|
|
Artifact *artifact.PackageArtifact
|
|
Package pkg.Package
|
|
}
|
|
|
|
func (re Repositories) PackageMatches(p pkg.Packages) []PackageMatch {
|
|
// TODO: Better heuristic. here we pick the first repo that contains the atom, sorted by priority but
|
|
// we should do a permutations and get the best match, and in case there are more solutions the user should be able to pick
|
|
sort.Sort(re)
|
|
|
|
var matches []PackageMatch
|
|
PACKAGE:
|
|
for _, pack := range p {
|
|
for _, r := range re {
|
|
c, err := r.GetTree().GetDatabase().FindPackage(pack)
|
|
if err == nil {
|
|
matches = append(matches, PackageMatch{Package: c, Repo: r})
|
|
continue PACKAGE
|
|
}
|
|
}
|
|
}
|
|
|
|
return matches
|
|
|
|
}
|
|
|
|
func (re Repositories) ResolveSelectors(p pkg.Packages) pkg.Packages {
|
|
// If a selector is given, get the best from each repo
|
|
sort.Sort(re) // respect prio
|
|
var matches pkg.Packages
|
|
PACKAGE:
|
|
for _, pack := range p {
|
|
REPOSITORY:
|
|
for _, r := range re {
|
|
if pack.IsSelector() {
|
|
c, err := r.GetTree().GetDatabase().FindPackageCandidate(pack)
|
|
// If FindPackageCandidate returns the same package, it means it couldn't find one.
|
|
// Skip this repository and keep looking.
|
|
if err != nil { //c.String() == pack.String() {
|
|
continue REPOSITORY
|
|
}
|
|
matches = append(matches, c)
|
|
continue PACKAGE
|
|
} else {
|
|
// If it's not a selector, just append it
|
|
matches = append(matches, pack)
|
|
}
|
|
}
|
|
}
|
|
|
|
return matches
|
|
|
|
}
|
|
|
|
func (re Repositories) SearchPackages(p string, t LuetSearchModeType) []PackageMatch {
|
|
sort.Sort(re)
|
|
var matches []PackageMatch
|
|
var err error
|
|
|
|
for _, r := range re {
|
|
var repoMatches pkg.Packages
|
|
|
|
switch t {
|
|
case SRegexPkg:
|
|
repoMatches, err = r.GetTree().GetDatabase().FindPackageMatch(p)
|
|
case SLabel:
|
|
repoMatches, err = r.GetTree().GetDatabase().FindPackageLabel(p)
|
|
case SRegexLabel:
|
|
repoMatches, err = r.GetTree().GetDatabase().FindPackageLabelMatch(p)
|
|
case FileSearch:
|
|
repoMatches, err = r.FileSearch(p)
|
|
}
|
|
|
|
if err == nil && len(repoMatches) > 0 {
|
|
for _, pack := range repoMatches {
|
|
a, _ := r.SearchArtefact(pack)
|
|
matches = append(matches, PackageMatch{Package: pack, Repo: r, Artifact: a})
|
|
}
|
|
}
|
|
}
|
|
|
|
return matches
|
|
}
|
|
|
|
func (re Repositories) SearchLabelMatch(s string) []PackageMatch {
|
|
return re.SearchPackages(s, SRegexLabel)
|
|
}
|
|
|
|
func (re Repositories) SearchLabel(s string) []PackageMatch {
|
|
return re.SearchPackages(s, SLabel)
|
|
}
|
|
|
|
func (re Repositories) Search(s string) []PackageMatch {
|
|
return re.SearchPackages(s, SRegexPkg)
|
|
}
|