Compare commits

...

31 Commits

Author SHA1 Message Date
Ettore Di Giacinto
caa1cfad5c Tag 0.11.6 2021-03-09 11:37:41 +01:00
Ettore Di Giacinto
39839edda9 Add util to rootcmd 2021-03-09 11:37:22 +01:00
Ettore Di Giacinto
ecd4be4ad3 Tag 0.11.5 2021-03-09 10:55:34 +01:00
Ettore Di Giacinto
675170939d Expose DownloadAndExtractDockerImage as a util
Create a util sub cmd to add all utils that are handy for development
and already present in the luet codebase. We expose in this case `luet
util unpack` to unpack a docker image without a docker daemon running.
2021-03-09 09:22:37 +01:00
Ettore Di Giacinto
0f1acac89b Tag 0.11.4 2021-03-07 12:38:37 +01:00
Ettore Di Giacinto
42f5210764 Add DownloadOnly option
It allows to download only the packages, without
installing/upgrading/replacing them

Fixes #179
2021-03-07 11:39:59 +01:00
Ettore Di Giacinto
9eda81667b Use common ImageID() when computing final images
It also adds tests and strip invalid "+" character which is not
supported in docker image tags
2021-03-07 11:01:08 +01:00
Ettore Di Giacinto
94f692266c Tag 0.11.3 2021-03-06 20:36:37 +01:00
Ettore Di Giacinto
c13a2174c4 Try to chown after copy 2021-03-06 18:49:14 +01:00
Ettore Di Giacinto
f07cf6c245 Add tests for file search 2021-03-06 18:49:14 +01:00
Ettore Di Giacinto
515017cabd ci: login with sudo -E 2021-03-06 16:52:20 +01:00
Ettore Di Giacinto
0bfb33db92 Allow to set db/rootfs from CLI in cleanup 2021-03-06 11:28:39 +01:00
Ettore Di Giacinto
c9c24dd174 Add integration test 2021-03-06 10:41:15 +01:00
Ettore Di Giacinto
194cfda8a4 Pass by the cli args to the underlying system struct
We didn't set previously what we catched from CLI. Note, this is a
temporary solution until we refactor the cli code

Fixes #186
2021-03-06 10:21:49 +01:00
Ettore Di Giacinto
233429bbeb Allow to search by file
Also make possible to retrieve the artifact when searching for matches
between repositories list. This made possible to show the package list
when calling `luet search`.
2021-02-28 18:42:58 +01:00
Ettore Di Giacinto
d84f6b31fd Add database get command 2021-02-28 18:42:16 +01:00
Ettore Di Giacinto
98b01ce00b Tag 0.11.2 2021-02-22 14:46:36 +01:00
Ettore Di Giacinto
749a4cb615 Add --backend-args
Allow to add arguments to the backend build arguments

Fixes #146
2021-02-22 13:49:29 +01:00
Ettore Di Giacinto
57e19c61e7 Start spinner before pulling docker artifacts 2021-02-22 11:54:09 +01:00
Ettore Di Giacinto
5ee1e28b9c Display informative message when pulling docker artifacts 2021-02-22 11:50:52 +01:00
Ettore Di Giacinto
21bd76af9c Uncomplicate runCommand and return command output
Fixes #189
2021-02-22 11:44:46 +01:00
Ettore Di Giacinto
89bd7c2281 Tag 0.11.1 2021-02-17 13:58:21 +01:00
Ettore Di Giacinto
49d7efa9ea Update vendor 2021-02-17 13:01:36 +01:00
Ettore Di Giacinto
b3e3abec8f Fixup spinner data race
Add spinner lock
2021-02-17 13:01:32 +01:00
Ettore Di Giacinto
92e73051a0 Initialize cached in singleton 2021-02-17 09:34:54 +01:00
Ettore Di Giacinto
fd7405c2cc Add inmemory DB integration test 2021-02-15 18:53:56 +01:00
Ettore Di Giacinto
2448f3175e Initialize cache db if empty 2021-02-15 18:53:37 +01:00
Ettore Di Giacinto
101df40eec Merge pull request #183 from mudler/docker-debug-output
Add realtime output for building phase
2021-02-13 12:02:20 +01:00
Daniele Rondina
c22adb3a47 compiler: Move spinner at the low level 2021-02-13 09:28:54 +01:00
Daniele Rondina
c1fe3278fa backend: Add realtime output on building phase
The realtime output could be configured through
LUET_GENERAL__SHOW_BUILD_OUTPUT environment
variable or related config option or through
`--live-output` option.
2021-02-02 12:58:34 +01:00
Daniele Rondina
2854c68209 logger: Add ln option for writer log 2021-02-01 19:10:05 +01:00
54 changed files with 1610 additions and 396 deletions

View File

@@ -14,7 +14,7 @@ jobs:
- name: setup-docker
uses: docker-practice/actions-setup-docker@0.0.1
- name: Login to quay
run: echo ${{ secrets.DOCKER_TESTING_PASSWORD }} | sudo docker login -u ${{ secrets.DOCKER_TESTING_USERNAME }} --password-stdin quay.io
run: echo ${{ secrets.DOCKER_TESTING_PASSWORD }} | sudo -E docker login -u ${{ secrets.DOCKER_TESTING_USERNAME }} --password-stdin quay.io
- name: Install deps
run: |
sudo apt-get install -y upx && sudo -E env "PATH=$PATH" make deps

View File

@@ -72,6 +72,7 @@ Build packages specifying multiple definition trees:
viper.BindPFlag("nodeps", cmd.Flags().Lookup("nodeps"))
viper.BindPFlag("onlydeps", cmd.Flags().Lookup("onlydeps"))
viper.BindPFlag("values", cmd.Flags().Lookup("values"))
viper.BindPFlag("backend-args", cmd.Flags().Lookup("backend-args"))
viper.BindPFlag("image-repository", cmd.Flags().Lookup("image-repository"))
viper.BindPFlag("push", cmd.Flags().Lookup("push"))
@@ -83,6 +84,9 @@ Build packages specifying multiple definition trees:
LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount"))
LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate"))
LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts"))
LuetCfg.Viper.BindPFlag("general.show_build_output", cmd.Flags().Lookup("live-output"))
LuetCfg.Viper.BindPFlag("backend-args", cmd.Flags().Lookup("backend-args"))
},
Run: func(cmd *cobra.Command, args []string) {
@@ -107,6 +111,7 @@ Build packages specifying multiple definition trees:
full, _ := cmd.Flags().GetBool("full")
concurrent, _ := cmd.Flags().GetBool("solver-concurrent")
var results Results
backendArgs := viper.GetStringSlice("backend-args")
out, _ := cmd.Flags().GetString("output")
if out != "terminal" {
@@ -155,6 +160,8 @@ Build packages specifying multiple definition trees:
LuetCfg.GetSolverOptions().Discount = float32(discount)
LuetCfg.GetSolverOptions().MaxAttempts = attempts
LuetCfg.GetGeneral().ShowBuildOutput = LuetCfg.Viper.GetBool("general.show_build_output")
Debug("Solver", LuetCfg.GetSolverOptions().CompactString())
opts := compiler.NewDefaultCompilerOptions()
@@ -176,6 +183,7 @@ Build packages specifying multiple definition trees:
}
luetCompiler := compiler.NewLuetCompiler(compilerBackend, generalRecipe.GetDatabase(), opts, solverOpts)
luetCompiler.SetBackendArgs(backendArgs)
luetCompiler.SetConcurrency(concurrency)
luetCompiler.SetCompressionType(compiler.CompressionImplementation(compressionType))
if full {
@@ -293,6 +301,7 @@ func init() {
if err != nil {
Fatal(err)
}
buildCmd.Flags().StringSliceP("tree", "t", []string{path}, "Path of the tree to use.")
buildCmd.Flags().String("backend", "docker", "backend used (docker,img)")
buildCmd.Flags().Bool("privileged", false, "Privileged (Keep permissions)")
@@ -301,6 +310,7 @@ func init() {
buildCmd.Flags().Bool("all", false, "Build all specfiles in the tree")
buildCmd.Flags().Bool("full", false, "Build all packages (optimized)")
buildCmd.Flags().String("values", "", "Build values file to interpolate with each package")
buildCmd.Flags().StringSliceP("backend-args", "a", []string{}, "Backend args")
buildCmd.Flags().String("destination", filepath.Join(path, "build"), "Destination folder")
buildCmd.Flags().String("compression", "none", "Compression alg: none, gzip, zstd")
@@ -317,6 +327,7 @@ func init() {
buildCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
buildCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts")
buildCmd.Flags().Bool("solver-concurrent", false, "Use concurrent solver (experimental)")
buildCmd.Flags().Bool("live-output", LuetCfg.GetGeneral().ShowBuildOutput, "Enable live output of the build phase.")
buildCmd.Flags().Bool("pretend", false, "Just print what packages will be compiled")

View File

@@ -20,6 +20,7 @@ import (
"os"
"path/filepath"
. "github.com/mudler/luet/pkg/config"
config "github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
. "github.com/mudler/luet/pkg/logger"
@@ -31,13 +32,24 @@ var cleanupCmd = &cobra.Command{
Use: "cleanup",
Short: "Clean packages cache.",
Long: `remove downloaded packages tarballs and clean cache directory`,
PreRun: func(cmd *cobra.Command, args []string) {
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("installed", cmd.Flags().Lookup("installed"))
},
Run: func(cmd *cobra.Command, args []string) {
var cleaned int = 0
dbpath := LuetCfg.Viper.GetString("system.database_path")
rootfs := config.LuetCfg.Viper.GetString("system.rootfs")
engine := config.LuetCfg.Viper.GetString("system.database_engine")
LuetCfg.System.DatabaseEngine = engine
LuetCfg.System.DatabasePath = dbpath
LuetCfg.System.Rootfs = rootfs
// Check if cache dir exists
if helpers.Exists(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath()) {
if helpers.Exists(LuetCfg.GetSystem().GetSystemPkgsCacheDirPath()) {
files, err := ioutil.ReadDir(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath())
files, err := ioutil.ReadDir(LuetCfg.GetSystem().GetSystemPkgsCacheDirPath())
if err != nil {
Fatal("Error on read cachedir ", err.Error())
}
@@ -47,12 +59,12 @@ var cleanupCmd = &cobra.Command{
continue
}
if config.LuetCfg.GetGeneral().Debug {
if LuetCfg.GetGeneral().Debug {
Info("Removing ", file.Name())
}
err := os.RemoveAll(
filepath.Join(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), file.Name()))
filepath.Join(LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), file.Name()))
if err != nil {
Fatal("Error on removing", file.Name())
}
@@ -66,5 +78,8 @@ var cleanupCmd = &cobra.Command{
}
func init() {
cleanupCmd.Flags().String("system-dbpath", "", "System db path")
cleanupCmd.Flags().String("system-target", "", "System rootpath")
cleanupCmd.Flags().String("system-engine", "", "System DB engine")
RootCmd.AddCommand(cleanupCmd)
}

View File

@@ -36,6 +36,7 @@ func init() {
databaseGroupCmd.AddCommand(
NewDatabaseCreateCommand(),
NewDatabaseGetCommand(),
NewDatabaseRemoveCommand(),
)
}

View File

@@ -46,10 +46,19 @@ For reference, inspect a "metadata.yaml" file generated while running "luet buil
PreRun: func(cmd *cobra.Command, args []string) {
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("system.database_engine", cmd.Flags().Lookup("system-engine"))
},
Run: func(cmd *cobra.Command, args []string) {
dbpath := LuetCfg.Viper.GetString("system.database_path")
rootfs := LuetCfg.Viper.GetString("system.rootfs")
engine := LuetCfg.Viper.GetString("system.database_engine")
LuetCfg.System.DatabaseEngine = engine
LuetCfg.System.DatabasePath = dbpath
LuetCfg.System.Rootfs = rootfs
systemDB := LuetCfg.GetSystemDB()
for _, a := range args {
@@ -77,5 +86,9 @@ For reference, inspect a "metadata.yaml" file generated while running "luet buil
},
}
ans.Flags().String("system-dbpath", "", "System db path")
ans.Flags().String("system-target", "", "System rootpath")
ans.Flags().String("system-engine", "", "System DB engine")
return ans
}

95
cmd/database/get.go Normal file
View File

@@ -0,0 +1,95 @@
// Copyright © 2021 Ettore Di Giacinto <mudler@mocaccino.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 cmd_database
import (
"fmt"
helpers "github.com/mudler/luet/cmd/helpers"
"gopkg.in/yaml.v2"
. "github.com/mudler/luet/pkg/config"
"github.com/spf13/cobra"
)
func NewDatabaseGetCommand() *cobra.Command {
var c = &cobra.Command{
Use: "get <package>",
Short: "Get a package in the system DB as yaml",
Long: `Get a package in the system database in the YAML format:
$ luet database get system/foo
To return also files:
$ luet database get --files system/foo`,
Args: cobra.OnlyValidArgs,
PreRun: func(cmd *cobra.Command, args []string) {
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("system.database_engine", cmd.Flags().Lookup("system-engine"))
},
Run: func(cmd *cobra.Command, args []string) {
showFiles, _ := cmd.Flags().GetBool("files")
dbpath := LuetCfg.Viper.GetString("system.database_path")
rootfs := LuetCfg.Viper.GetString("system.rootfs")
engine := LuetCfg.Viper.GetString("system.database_engine")
LuetCfg.System.DatabaseEngine = engine
LuetCfg.System.DatabasePath = dbpath
LuetCfg.System.Rootfs = rootfs
systemDB := LuetCfg.GetSystemDB()
for _, a := range args {
pack, err := helpers.ParsePackageStr(a)
if err != nil {
continue
}
ps, err := systemDB.FindPackages(pack)
if err != nil {
continue
}
for _, p := range ps {
y, err := p.Yaml()
if err != nil {
continue
}
fmt.Println(string(y))
if showFiles {
files, err := systemDB.GetPackageFiles(p)
if err != nil {
continue
}
b, err := yaml.Marshal(files)
if err != nil {
continue
}
fmt.Println("files:\n" + string(b))
}
}
}
},
}
c.Flags().Bool("files", false, "Show package files.")
c.Flags().String("system-dbpath", "", "System db path")
c.Flags().String("system-target", "", "System rootpath")
c.Flags().String("system-engine", "", "System DB engine")
return c
}

View File

@@ -38,9 +38,18 @@ This commands takes multiple packages as arguments and prunes their entries from
PreRun: func(cmd *cobra.Command, args []string) {
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("system.database_engine", cmd.Flags().Lookup("system-engine"))
},
Run: func(cmd *cobra.Command, args []string) {
dbpath := LuetCfg.Viper.GetString("system.database_path")
rootfs := LuetCfg.Viper.GetString("system.rootfs")
engine := LuetCfg.Viper.GetString("system.database_engine")
LuetCfg.System.DatabaseEngine = engine
LuetCfg.System.DatabasePath = dbpath
LuetCfg.System.Rootfs = rootfs
systemDB := LuetCfg.GetSystemDB()
for _, a := range args {
@@ -60,6 +69,9 @@ This commands takes multiple packages as arguments and prunes their entries from
},
}
ans.Flags().String("system-dbpath", "", "System db path")
ans.Flags().String("system-target", "", "System rootpath")
ans.Flags().String("system-engine", "", "System DB engine")
return ans
}

View File

@@ -15,8 +15,6 @@
package cmd
import (
"os"
installer "github.com/mudler/luet/pkg/installer"
"github.com/mudler/luet/pkg/solver"
@@ -51,6 +49,7 @@ To force install a package:
PreRun: func(cmd *cobra.Command, args []string) {
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("system.database_engine", cmd.Flags().Lookup("system-engine"))
LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type"))
LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount"))
LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate"))
@@ -90,6 +89,15 @@ To force install a package:
onlydeps := LuetCfg.Viper.GetBool("onlydeps")
concurrent, _ := cmd.Flags().GetBool("solver-concurrent")
yes := LuetCfg.Viper.GetBool("yes")
downloadOnly, _ := cmd.Flags().GetBool("download-only")
dbpath := LuetCfg.Viper.GetString("system.database_path")
rootfs := LuetCfg.Viper.GetString("system.rootfs")
engine := LuetCfg.Viper.GetString("system.database_engine")
LuetCfg.System.DatabaseEngine = engine
LuetCfg.System.DatabasePath = dbpath
LuetCfg.System.Rootfs = rootfs
LuetCfg.GetSolverOptions().Type = stype
LuetCfg.GetSolverOptions().LearnRate = float32(rate)
@@ -114,6 +122,7 @@ To force install a package:
Force: force,
OnlyDeps: onlydeps,
PreserveSystemEssentialData: true,
DownloadOnly: downloadOnly,
Ask: !yes,
})
inst.Repositories(repos)
@@ -127,12 +136,10 @@ To force install a package:
}
func init() {
path, err := os.Getwd()
if err != nil {
Fatal(err)
}
installCmd.Flags().String("system-dbpath", path, "System db path")
installCmd.Flags().String("system-target", path, "System rootpath")
installCmd.Flags().String("system-dbpath", "", "System db path")
installCmd.Flags().String("system-target", "", "System rootpath")
installCmd.Flags().String("system-engine", "", "System DB engine")
installCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )")
installCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
installCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
@@ -142,6 +149,7 @@ func init() {
installCmd.Flags().Bool("force", false, "Skip errors and keep going (potentially harmful)")
installCmd.Flags().Bool("solver-concurrent", false, "Use concurrent solver (experimental)")
installCmd.Flags().BoolP("yes", "y", false, "Don't ask questions")
installCmd.Flags().Bool("download-only", false, "Download only")
RootCmd.AddCommand(installCmd)
}

View File

@@ -15,8 +15,6 @@
package cmd
import (
"os"
installer "github.com/mudler/luet/pkg/installer"
. "github.com/mudler/luet/pkg/config"
@@ -31,6 +29,7 @@ var reclaimCmd = &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("system.database_engine", cmd.Flags().Lookup("system-engine"))
LuetCfg.Viper.BindPFlag("force", cmd.Flags().Lookup("force"))
},
Long: `Reclaim tries to find association between packages in the online repositories and the system one.
@@ -40,6 +39,13 @@ var reclaimCmd = &cobra.Command{
It scans the target file system, and if finds a match with a package available in the repositories, it marks as installed in the system database.
`,
Run: func(cmd *cobra.Command, args []string) {
dbpath := LuetCfg.Viper.GetString("system.database_path")
rootfs := LuetCfg.Viper.GetString("system.rootfs")
engine := LuetCfg.Viper.GetString("system.database_engine")
LuetCfg.System.DatabaseEngine = engine
LuetCfg.System.DatabasePath = dbpath
LuetCfg.System.Rootfs = rootfs
// This shouldn't be necessary, but we need to unmarshal the repositories to a concrete struct, thus we need to port them back to the Repositories type
repos := installer.Repositories{}
@@ -71,12 +77,11 @@ It scans the target file system, and if finds a match with a package available i
}
func init() {
path, err := os.Getwd()
if err != nil {
Fatal(err)
}
reclaimCmd.Flags().String("system-dbpath", path, "System db path")
reclaimCmd.Flags().String("system-target", path, "System rootpath")
reclaimCmd.Flags().String("system-dbpath", "", "System db path")
reclaimCmd.Flags().String("system-target", "", "System rootpath")
reclaimCmd.Flags().String("system-engine", "", "System DB engine")
reclaimCmd.Flags().Bool("force", false, "Skip errors and keep going (potentially harmful)")
RootCmd.AddCommand(reclaimCmd)

View File

@@ -15,8 +15,6 @@
package cmd
import (
"os"
installer "github.com/mudler/luet/pkg/installer"
"github.com/mudler/luet/pkg/solver"
@@ -38,6 +36,7 @@ var replaceCmd = &cobra.Command{
`,
PreRun: func(cmd *cobra.Command, args []string) {
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.database_engine", cmd.Flags().Lookup("system-engine"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type"))
LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount"))
@@ -64,6 +63,14 @@ var replaceCmd = &cobra.Command{
onlydeps := LuetCfg.Viper.GetBool("onlydeps")
concurrent, _ := cmd.Flags().GetBool("solver-concurrent")
yes := LuetCfg.Viper.GetBool("yes")
dbpath := LuetCfg.Viper.GetString("system.database_path")
rootfs := LuetCfg.Viper.GetString("system.rootfs")
engine := LuetCfg.Viper.GetString("system.database_engine")
downloadOnly, _ := cmd.Flags().GetBool("download-only")
LuetCfg.System.DatabaseEngine = engine
LuetCfg.System.DatabasePath = dbpath
LuetCfg.System.Rootfs = rootfs
for _, a := range args {
pack, err := helpers.ParsePackageStr(a)
@@ -115,6 +122,7 @@ var replaceCmd = &cobra.Command{
OnlyDeps: onlydeps,
PreserveSystemEssentialData: true,
Ask: !yes,
DownloadOnly: downloadOnly,
})
inst.Repositories(repos)
@@ -127,12 +135,11 @@ var replaceCmd = &cobra.Command{
}
func init() {
path, err := os.Getwd()
if err != nil {
Fatal(err)
}
replaceCmd.Flags().String("system-dbpath", path, "System db path")
replaceCmd.Flags().String("system-target", path, "System rootpath")
replaceCmd.Flags().String("system-dbpath", "", "System db path")
replaceCmd.Flags().String("system-target", "", "System rootpath")
replaceCmd.Flags().String("system-engine", "", "System DB engine")
replaceCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )")
replaceCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
replaceCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
@@ -143,6 +150,7 @@ func init() {
replaceCmd.Flags().Bool("solver-concurrent", false, "Use concurrent solver (experimental)")
replaceCmd.Flags().BoolP("yes", "y", false, "Don't ask questions")
replaceCmd.Flags().StringSlice("for", []string{}, "Packages that has to be installed in place of others")
replaceCmd.Flags().Bool("download-only", false, "Download only")
RootCmd.AddCommand(replaceCmd)
}

View File

@@ -40,7 +40,7 @@ var Verbose bool
var LockedCommands = []string{"install", "uninstall", "upgrade"}
const (
LuetCLIVersion = "0.11.0"
LuetCLIVersion = "0.11.6"
LuetEnvPrefix = "LUET"
)

View File

@@ -16,7 +16,6 @@ package cmd
import (
"fmt"
"os"
"strings"
"github.com/ghodss/yaml"
@@ -30,12 +29,13 @@ import (
)
type PackageResult struct {
Name string `json:"name"`
Category string `json:"category"`
Version string `json:"version"`
Repository string `json:"repository"`
Target string `json:"target"`
Hidden bool `json:"hidden"`
Name string `json:"name"`
Category string `json:"category"`
Version string `json:"version"`
Repository string `json:"repository"`
Target string `json:"target"`
Hidden bool `json:"hidden"`
Files []string `json:"files"`
}
type Results struct {
@@ -64,6 +64,205 @@ func packageToList(l list.Writer, repo string, p pkg.Package) {
l.UnIndent()
}
func searchLocally(term string, l list.Writer, t table.Writer, label, labelMatch, revdeps, hidden bool) Results {
var results Results
system := &installer.System{Database: LuetCfg.GetSystemDB(), Target: LuetCfg.GetSystem().Rootfs}
var err error
iMatches := pkg.Packages{}
if label {
iMatches, err = system.Database.FindPackageLabel(term)
} else if labelMatch {
iMatches, err = system.Database.FindPackageLabelMatch(term)
} else {
iMatches, err = system.Database.FindPackageMatch(term)
}
if err != nil {
Fatal("Error: " + err.Error())
}
for _, pack := range iMatches {
if !revdeps {
if !pack.IsHidden() || pack.IsHidden() && hidden {
t.AppendRow(packageToRow("system", pack))
packageToList(l, "system", pack)
f, _ := system.Database.GetPackageFiles(pack)
results.Packages = append(results.Packages,
PackageResult{
Name: pack.GetName(),
Version: pack.GetVersion(),
Category: pack.GetCategory(),
Repository: "system",
Hidden: pack.IsHidden(),
Files: f,
})
}
} else {
packs, _ := system.Database.GetRevdeps(pack)
for _, revdep := range packs {
if !revdep.IsHidden() || revdep.IsHidden() && hidden {
t.AppendRow(packageToRow("system", pack))
packageToList(l, "system", pack)
f, _ := system.Database.GetPackageFiles(revdep)
results.Packages = append(results.Packages,
PackageResult{
Name: revdep.GetName(),
Version: revdep.GetVersion(),
Category: revdep.GetCategory(),
Repository: "system",
Hidden: revdep.IsHidden(),
Files: f,
})
}
}
}
}
return results
}
func searchOnline(term string, l list.Writer, t table.Writer, label, labelMatch, revdeps, hidden bool) Results {
var results Results
repos := installer.Repositories{}
for _, repo := range LuetCfg.SystemRepositories {
if !repo.Enable {
continue
}
r := installer.NewSystemRepository(repo)
repos = append(repos, r)
}
inst := installer.NewLuetInstaller(
installer.LuetInstallerOptions{
Concurrency: LuetCfg.GetGeneral().Concurrency,
SolverOptions: *LuetCfg.GetSolverOptions(),
},
)
inst.Repositories(repos)
synced, err := inst.SyncRepositories(false)
if err != nil {
Fatal("Error: " + err.Error())
}
Info("--- Search results (" + term + "): ---")
matches := []installer.PackageMatch{}
if label {
matches = synced.SearchLabel(term)
} else if labelMatch {
matches = synced.SearchLabelMatch(term)
} else {
matches = synced.Search(term)
}
for _, m := range matches {
if !revdeps {
if !m.Package.IsHidden() || m.Package.IsHidden() && hidden {
t.AppendRow(packageToRow(m.Repo.GetName(), m.Package))
packageToList(l, m.Repo.GetName(), m.Package)
results.Packages = append(results.Packages,
PackageResult{
Name: m.Package.GetName(),
Version: m.Package.GetVersion(),
Category: m.Package.GetCategory(),
Repository: m.Repo.GetName(),
Hidden: m.Package.IsHidden(),
Files: m.Artifact.GetFiles(),
})
}
} else {
packs, _ := m.Repo.GetTree().GetDatabase().GetRevdeps(m.Package)
for _, revdep := range packs {
if !revdep.IsHidden() || revdep.IsHidden() && hidden {
t.AppendRow(packageToRow(m.Repo.GetName(), revdep))
packageToList(l, m.Repo.GetName(), revdep)
results.Packages = append(results.Packages,
PackageResult{
Name: revdep.GetName(),
Version: revdep.GetVersion(),
Category: revdep.GetCategory(),
Repository: m.Repo.GetName(),
Hidden: revdep.IsHidden(),
Files: m.Artifact.GetFiles(),
})
}
}
}
}
return results
}
func searchLocalFiles(term string, l list.Writer, t table.Writer) Results {
var results Results
Info("--- Search results (" + term + "): ---")
matches, _ := LuetCfg.GetSystemDB().FindPackageByFile(term)
for _, pack := range matches {
t.AppendRow(packageToRow("system", pack))
packageToList(l, "system", pack)
f, _ := LuetCfg.GetSystemDB().GetPackageFiles(pack)
results.Packages = append(results.Packages,
PackageResult{
Name: pack.GetName(),
Version: pack.GetVersion(),
Category: pack.GetCategory(),
Repository: "system",
Hidden: pack.IsHidden(),
Files: f,
})
}
return results
}
func searchFiles(term string, l list.Writer, t table.Writer) Results {
var results Results
repos := installer.Repositories{}
for _, repo := range LuetCfg.SystemRepositories {
if !repo.Enable {
continue
}
r := installer.NewSystemRepository(repo)
repos = append(repos, r)
}
inst := installer.NewLuetInstaller(
installer.LuetInstallerOptions{
Concurrency: LuetCfg.GetGeneral().Concurrency,
SolverOptions: *LuetCfg.GetSolverOptions(),
},
)
inst.Repositories(repos)
synced, err := inst.SyncRepositories(false)
if err != nil {
Fatal("Error: " + err.Error())
}
Info("--- Search results (" + term + "): ---")
matches := []installer.PackageMatch{}
matches = synced.SearchPackages(term, installer.FileSearch)
for _, m := range matches {
t.AppendRow(packageToRow(m.Repo.GetName(), m.Package))
packageToList(l, m.Repo.GetName(), m.Package)
results.Packages = append(results.Packages,
PackageResult{
Name: m.Package.GetName(),
Version: m.Package.GetVersion(),
Category: m.Package.GetCategory(),
Repository: m.Repo.GetName(),
Hidden: m.Package.IsHidden(),
Files: m.Artifact.GetFiles(),
})
}
return results
}
var searchCmd = &cobra.Command{
Use: "search <term>",
Short: "Search packages",
@@ -107,6 +306,7 @@ Search can also return results in the terminal in different ways: as terminal ou
LuetCfg.Viper.BindPFlag("installed", cmd.Flags().Lookup("installed"))
LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type"))
LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount"))
LuetCfg.Viper.BindPFlag("system.database_engine", cmd.Flags().Lookup("system-engine"))
LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate"))
LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts"))
},
@@ -128,7 +328,14 @@ Search can also return results in the terminal in different ways: as terminal ou
searchWithLabelMatch, _ := cmd.Flags().GetBool("by-label-regex")
revdeps, _ := cmd.Flags().GetBool("revdeps")
tableMode, _ := cmd.Flags().GetBool("table")
files, _ := cmd.Flags().GetBool("files")
dbpath := LuetCfg.Viper.GetString("system.database_path")
rootfs := LuetCfg.Viper.GetString("system.rootfs")
engine := LuetCfg.Viper.GetString("system.database_engine")
LuetCfg.System.DatabaseEngine = engine
LuetCfg.System.DatabasePath = dbpath
LuetCfg.System.Rootfs = rootfs
out, _ := cmd.Flags().GetString("output")
if out != "terminal" {
LuetCfg.GetLogging().SetLogLevel("error")
@@ -144,121 +351,15 @@ Search can also return results in the terminal in different ways: as terminal ou
t.AppendHeader(rows)
Debug("Solver", LuetCfg.GetSolverOptions().CompactString())
if !installed {
repos := installer.Repositories{}
for _, repo := range LuetCfg.SystemRepositories {
if !repo.Enable {
continue
}
r := installer.NewSystemRepository(repo)
repos = append(repos, r)
}
inst := installer.NewLuetInstaller(
installer.LuetInstallerOptions{
Concurrency: LuetCfg.GetGeneral().Concurrency,
SolverOptions: *LuetCfg.GetSolverOptions(),
},
)
inst.Repositories(repos)
synced, err := inst.SyncRepositories(false)
if err != nil {
Fatal("Error: " + err.Error())
}
Info("--- Search results (" + args[0] + "): ---")
matches := []installer.PackageMatch{}
if searchWithLabel {
matches = synced.SearchLabel(args[0])
} else if searchWithLabelMatch {
matches = synced.SearchLabelMatch(args[0])
} else {
matches = synced.Search(args[0])
}
for _, m := range matches {
if !revdeps {
if !m.Package.IsHidden() || m.Package.IsHidden() && hidden {
t.AppendRow(packageToRow(m.Repo.GetName(), m.Package))
packageToList(l, m.Repo.GetName(), m.Package)
results.Packages = append(results.Packages,
PackageResult{
Name: m.Package.GetName(),
Version: m.Package.GetVersion(),
Category: m.Package.GetCategory(),
Repository: m.Repo.GetName(),
Hidden: m.Package.IsHidden(),
})
}
} else {
packs, _ := m.Repo.GetTree().GetDatabase().GetRevdeps(m.Package)
for _, revdep := range packs {
if !revdep.IsHidden() || revdep.IsHidden() && hidden {
t.AppendRow(packageToRow(m.Repo.GetName(), revdep))
packageToList(l, m.Repo.GetName(), revdep)
results.Packages = append(results.Packages,
PackageResult{
Name: revdep.GetName(),
Version: revdep.GetVersion(),
Category: revdep.GetCategory(),
Repository: m.Repo.GetName(),
Hidden: revdep.IsHidden(),
})
}
}
}
}
} else {
system := &installer.System{Database: LuetCfg.GetSystemDB(), Target: LuetCfg.GetSystem().Rootfs}
var err error
iMatches := pkg.Packages{}
if searchWithLabel {
iMatches, err = system.Database.FindPackageLabel(args[0])
} else if searchWithLabelMatch {
iMatches, err = system.Database.FindPackageLabelMatch(args[0])
} else {
iMatches, err = system.Database.FindPackageMatch(args[0])
}
if err != nil {
Fatal("Error: " + err.Error())
}
for _, pack := range iMatches {
if !revdeps {
if !pack.IsHidden() || pack.IsHidden() && hidden {
t.AppendRow(packageToRow("system", pack))
packageToList(l, "system", pack)
results.Packages = append(results.Packages,
PackageResult{
Name: pack.GetName(),
Version: pack.GetVersion(),
Category: pack.GetCategory(),
Repository: "system",
Hidden: pack.IsHidden(),
})
}
} else {
packs, _ := system.Database.GetRevdeps(pack)
for _, revdep := range packs {
if !revdep.IsHidden() || revdep.IsHidden() && hidden {
t.AppendRow(packageToRow("system", pack))
packageToList(l, "system", pack)
results.Packages = append(results.Packages,
PackageResult{
Name: revdep.GetName(),
Version: revdep.GetVersion(),
Category: revdep.GetCategory(),
Repository: "system",
Hidden: revdep.IsHidden(),
})
}
}
}
}
switch {
case files && installed:
results = searchLocalFiles(args[0], l, t)
case files && !installed:
results = searchFiles(args[0], l, t)
case !installed:
results = searchOnline(args[0], l, t, searchWithLabel, searchWithLabelMatch, revdeps, hidden)
default:
results = searchLocally(args[0], l, t, searchWithLabel, searchWithLabelMatch, revdeps, hidden)
}
t.AppendFooter(rows)
@@ -292,12 +393,10 @@ Search can also return results in the terminal in different ways: as terminal ou
}
func init() {
path, err := os.Getwd()
if err != nil {
Fatal(err)
}
searchCmd.Flags().String("system-dbpath", path, "System db path")
searchCmd.Flags().String("system-target", path, "System rootpath")
searchCmd.Flags().String("system-dbpath", "", "System db path")
searchCmd.Flags().String("system-target", "", "System rootpath")
searchCmd.Flags().String("system-engine", "", "System DB engine")
searchCmd.Flags().Bool("installed", false, "Search between system packages")
searchCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )")
searchCmd.Flags().StringP("output", "o", "terminal", "Output format ( Defaults: terminal, available: json,yaml )")
@@ -309,6 +408,7 @@ func init() {
searchCmd.Flags().Bool("revdeps", false, "Search package reverse dependencies")
searchCmd.Flags().Bool("hidden", false, "Include hidden packages")
searchCmd.Flags().Bool("table", false, "show output in a table (wider screens)")
searchCmd.Flags().Bool("files", false, "Search between packages files")
RootCmd.AddCommand(searchCmd)
}

View File

@@ -15,8 +15,6 @@
package cmd
import (
"os"
helpers "github.com/mudler/luet/cmd/helpers"
. "github.com/mudler/luet/pkg/config"
installer "github.com/mudler/luet/pkg/installer"
@@ -42,6 +40,7 @@ var uninstallCmd = &cobra.Command{
LuetCfg.Viper.BindPFlag("nodeps", cmd.Flags().Lookup("nodeps"))
LuetCfg.Viper.BindPFlag("force", cmd.Flags().Lookup("force"))
LuetCfg.Viper.BindPFlag("yes", cmd.Flags().Lookup("yes"))
LuetCfg.Viper.BindPFlag("system.database_engine", cmd.Flags().Lookup("system-engine"))
},
Run: func(cmd *cobra.Command, args []string) {
toRemove := []pkg.Package{}
@@ -65,6 +64,13 @@ var uninstallCmd = &cobra.Command{
fullClean, _ := cmd.Flags().GetBool("full-clean")
concurrent, _ := cmd.Flags().GetBool("solver-concurrent")
yes := LuetCfg.Viper.GetBool("yes")
dbpath := LuetCfg.Viper.GetString("system.database_path")
rootfs := LuetCfg.Viper.GetString("system.rootfs")
engine := LuetCfg.Viper.GetString("system.database_engine")
LuetCfg.System.DatabaseEngine = engine
LuetCfg.System.DatabasePath = dbpath
LuetCfg.System.Rootfs = rootfs
LuetCfg.GetSolverOptions().Type = stype
LuetCfg.GetSolverOptions().LearnRate = float32(rate)
@@ -101,12 +107,11 @@ var uninstallCmd = &cobra.Command{
}
func init() {
path, err := os.Getwd()
if err != nil {
Fatal(err)
}
uninstallCmd.Flags().String("system-dbpath", path, "System db path")
uninstallCmd.Flags().String("system-target", path, "System rootpath")
uninstallCmd.Flags().String("system-dbpath", "", "System db path")
uninstallCmd.Flags().String("system-target", "", "System rootpath")
uninstallCmd.Flags().String("system-engine", "", "System DB engine")
uninstallCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )")
uninstallCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
uninstallCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")

View File

@@ -15,8 +15,6 @@
package cmd
import (
"os"
. "github.com/mudler/luet/pkg/config"
installer "github.com/mudler/luet/pkg/installer"
. "github.com/mudler/luet/pkg/logger"
@@ -38,6 +36,7 @@ var upgradeCmd = &cobra.Command{
LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts"))
LuetCfg.Viper.BindPFlag("force", cmd.Flags().Lookup("force"))
LuetCfg.Viper.BindPFlag("yes", cmd.Flags().Lookup("yes"))
LuetCfg.Viper.BindPFlag("system.database_engine", cmd.Flags().Lookup("system-engine"))
},
Long: `Upgrades packages in parallel`,
Run: func(cmd *cobra.Command, args []string) {
@@ -64,7 +63,14 @@ var upgradeCmd = &cobra.Command{
sync, _ := cmd.Flags().GetBool("sync")
concurrent, _ := cmd.Flags().GetBool("solver-concurrent")
yes := LuetCfg.Viper.GetBool("yes")
dbpath := LuetCfg.Viper.GetString("system.database_path")
rootfs := LuetCfg.Viper.GetString("system.rootfs")
engine := LuetCfg.Viper.GetString("system.database_engine")
downloadOnly, _ := cmd.Flags().GetBool("download-only")
LuetCfg.System.DatabaseEngine = engine
LuetCfg.System.DatabasePath = dbpath
LuetCfg.System.Rootfs = rootfs
LuetCfg.GetSolverOptions().Type = stype
LuetCfg.GetSolverOptions().LearnRate = float32(rate)
LuetCfg.GetSolverOptions().Discount = float32(discount)
@@ -91,6 +97,7 @@ var upgradeCmd = &cobra.Command{
UpgradeNewRevisions: sync,
PreserveSystemEssentialData: true,
Ask: !yes,
DownloadOnly: downloadOnly,
})
inst.Repositories(repos)
@@ -102,12 +109,10 @@ var upgradeCmd = &cobra.Command{
}
func init() {
path, err := os.Getwd()
if err != nil {
Fatal(err)
}
upgradeCmd.Flags().String("system-dbpath", path, "System db path")
upgradeCmd.Flags().String("system-target", path, "System rootpath")
upgradeCmd.Flags().String("system-dbpath", "", "System db path")
upgradeCmd.Flags().String("system-target", "", "System rootpath")
upgradeCmd.Flags().String("system-engine", "", "System DB engine")
upgradeCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )")
upgradeCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
upgradeCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
@@ -120,6 +125,7 @@ func init() {
upgradeCmd.Flags().Bool("sync", false, "Upgrade packages with new revisions (experimental)")
upgradeCmd.Flags().Bool("solver-concurrent", false, "Use concurrent solver (experimental)")
upgradeCmd.Flags().BoolP("yes", "y", false, "Don't ask questions")
upgradeCmd.Flags().Bool("download-only", false, "Download only")
RootCmd.AddCommand(upgradeCmd)
}

36
cmd/util.go Normal file
View File

@@ -0,0 +1,36 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.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 cmd
import (
. "github.com/mudler/luet/cmd/util"
"github.com/spf13/cobra"
)
var utilGroup = &cobra.Command{
Use: "util [command] [OPTIONS]",
Short: "General luet internal utilities exposed",
}
func init() {
RootCmd.AddCommand(utilGroup)
utilGroup.AddCommand(
NewUnpackCommand(),
)
}

72
cmd/util/unpack.go Normal file
View File

@@ -0,0 +1,72 @@
// Copyright © 2021 Ettore Di Giacinto <mudler@mocaccino.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 util
import (
"fmt"
"os"
"path/filepath"
"github.com/docker/go-units"
"github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
. "github.com/mudler/luet/pkg/logger"
"github.com/spf13/cobra"
)
func NewUnpackCommand() *cobra.Command {
return &cobra.Command{
Use: "unpack image path",
Short: "Unpack a docker image natively",
Long: `unpack doesn't need the docker daemon to run, and unpacks a docker image in the specified directory:
luet util unpack golang:alpine /alpine
`,
PreRun: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
Fatal("Expects an image and a path")
}
},
Run: func(cmd *cobra.Command, args []string) {
image := args[0]
destination, err := filepath.Abs(args[1])
if err != nil {
Error("Invalid path %s", destination)
os.Exit(1)
}
temp, err := config.LuetCfg.GetSystem().TempDir("contentstore")
if err != nil {
Fatal("Cannot create a tempdir", err.Error())
}
Info("Downloading", image, "to", destination)
info, err := helpers.DownloadAndExtractDockerImage(temp, image, destination)
if err != nil {
Error(err.Error())
os.Exit(1)
}
Info(fmt.Sprintf("Pulled: %s", info.Target.Digest))
Info(fmt.Sprintf("Size: %s", units.BytesSize(float64(info.ContentSize))))
},
}
}

2
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/Sabayon/pkgs-checker v0.7.2
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/asdine/storm v0.0.0-20190418133842-e0f77eada154
github.com/briandowns/spinner v1.7.0
github.com/briandowns/spinner v1.12.1-0.20201220203425-e201aaea0a31
github.com/cavaliercoder/grab v1.0.1-0.20201108051000-98a5bfe305ec
github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc
github.com/crillab/gophersat v1.3.2-0.20201023142334-3fc2ac466765

4
go.sum
View File

@@ -144,8 +144,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/briandowns/spinner v1.7.0 h1:aan1hBBOoscry2TXAkgtxkJiq7Se0+9pt+TUWaPrB4g=
github.com/briandowns/spinner v1.7.0/go.mod h1://Zf9tMcxfRUA36V23M6YGEAv+kECGfvpnLTnb8n4XQ=
github.com/briandowns/spinner v1.12.1-0.20201220203425-e201aaea0a31 h1:yInAg9pE5qGec5eQ7XdfOTTaGwGxD3bKFVjmD6VKkwc=
github.com/briandowns/spinner v1.12.1-0.20201220203425-e201aaea0a31/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=

View File

@@ -16,8 +16,14 @@
package backend
import (
"github.com/google/go-containerregistry/pkg/crane"
"os/exec"
"github.com/mudler/luet/pkg/compiler"
"github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/logger"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/pkg/errors"
)
const (
@@ -41,3 +47,40 @@ func NewBackend(s string) compiler.CompilerBackend {
}
return compilerBackend
}
func runCommand(cmd *exec.Cmd) error {
output := ""
buffered := !config.LuetCfg.GetGeneral().ShowBuildOutput
writer := NewBackendWriter(buffered)
cmd.Stdout = writer
cmd.Stderr = writer
if buffered {
Spinner(22)
defer SpinnerStop()
}
err := cmd.Start()
if err != nil {
return errors.Wrap(err, "Failed starting command")
}
err = cmd.Wait()
if err != nil {
output = writer.GetCombinedOutput()
return errors.Wrapf(err, "Failed running command: %s", output)
}
return nil
}
func genBuildCommand(opts compiler.CompilerBackendOptions) []string {
context := opts.Context
if context == "" {
context = "."
}
buildarg := append(opts.BackendArgs, "-f", opts.DockerFileName, "-t", opts.ImageName, context)
return append([]string{"build"}, buildarg...)
}

View File

@@ -44,28 +44,24 @@ func NewSimpleDockerBackend() compiler.CompilerBackend {
// TODO: Missing still: labels, and build args expansion
func (*SimpleDocker) BuildImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
path := opts.SourcePath
dockerfileName := opts.DockerFileName
context := opts.Context
if context == "" {
context = "."
}
buildarg := []string{"build", "-f", dockerfileName, "-t", name, context}
buildarg := genBuildCommand(opts)
Info(":whale2: Building image " + name)
cmd := exec.Command("docker", buildarg...)
cmd.Dir = path
out, err := cmd.CombinedOutput()
cmd.Dir = opts.SourcePath
err := runCommand(cmd)
if err != nil {
return errors.Wrap(err, "Failed building image: "+string(out))
return err
}
Info(":whale: Building image " + name + " done")
if os.Getenv("DOCKER_SQUASH") == "true" {
Info(":whale: Squashing image " + name)
var client *docker.Client
Spinner(22)
defer SpinnerStop()
client, err = docker.NewClientFromEnv()
if err != nil {
return errors.Wrap(err, "could not connect to the Docker daemon")
@@ -74,13 +70,8 @@ func (*SimpleDocker) BuildImage(opts compiler.CompilerBackendOptions) error {
if err != nil {
return errors.Wrap(err, "Failed squashing image")
}
Info(":whale: Squashing image " + name + " done")
}
if config.LuetCfg.GetGeneral().ShowBuildOutput {
Info(string(out))
} else {
Debug(string(out))
Info(":whale: Squashing image " + name + " done")
}
return nil
@@ -101,11 +92,16 @@ func (*SimpleDocker) DownloadImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
buildarg := []string{"pull", name}
Debug(":whale: Downloading image " + name)
Spinner(22)
defer SpinnerStop()
cmd := exec.Command("docker", buildarg...)
out, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed pulling image: "+string(out))
}
Info(":whale: Downloaded image:", name)
return nil
}
@@ -142,6 +138,10 @@ func (*SimpleDocker) RemoveImage(opts compiler.CompilerBackendOptions) error {
func (*SimpleDocker) Push(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
pusharg := []string{"push", name}
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("docker", pusharg...).CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed pushing image: "+string(out))
@@ -170,6 +170,10 @@ func (*SimpleDocker) ExportImage(opts compiler.CompilerBackendOptions) error {
buildarg := []string{"save", name, "-o", path}
Debug(":whale: Saving image " + name)
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("docker", buildarg...).CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed exporting image: "+string(out))
@@ -194,10 +198,16 @@ func (b *SimpleDocker) ExtractRootfs(opts compiler.CompilerBackendOptions, keepP
defer os.RemoveAll(tempexport) // clean up
imageExport := filepath.Join(tempexport, "image.tar")
Spinner(22)
defer SpinnerStop()
if err := b.ExportImage(compiler.CompilerBackendOptions{ImageName: name, Destination: imageExport}); err != nil {
return errors.Wrap(err, "failed while extracting rootfs for "+name)
}
SpinnerStop()
src := imageExport
if src == "" && opts.ImageName != "" {

View File

@@ -35,26 +35,20 @@ func NewSimpleImgBackend() compiler.CompilerBackend {
// TODO: Missing still: labels, and build args expansion
func (*SimpleImg) BuildImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
path := opts.SourcePath
context := opts.Context
if context == "" {
context = "."
}
dockerfileName := opts.DockerFileName
buildarg := genBuildCommand(opts)
buildarg := []string{"build", "-f", dockerfileName, "-t", name, context}
Spinner(22)
defer SpinnerStop()
Info(":tea: Building image " + name)
cmd := exec.Command("img", buildarg...)
cmd.Dir = path
out, err := cmd.CombinedOutput()
cmd := exec.Command("img", buildarg...)
cmd.Dir = opts.SourcePath
err := runCommand(cmd)
if err != nil {
return errors.Wrap(err, "Failed building image: "+string(out))
return err
}
Info(":tea: Building image " + name + " done")
return nil
}
@@ -73,11 +67,13 @@ func (*SimpleImg) RemoveImage(opts compiler.CompilerBackendOptions) error {
}
func (*SimpleImg) DownloadImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
buildarg := []string{"pull", name}
Debug(":tea: Downloading image " + name)
Spinner(22)
defer SpinnerStop()
cmd := exec.Command("img", buildarg...)
out, err := cmd.CombinedOutput()
if err != nil {
@@ -138,6 +134,10 @@ func (*SimpleImg) ExportImage(opts compiler.CompilerBackendOptions) error {
path := opts.Destination
buildarg := []string{"save", "-o", path, name}
Debug(":tea: Saving image " + name)
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("img", buildarg...).CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed exporting image: "+string(out))
@@ -158,8 +158,13 @@ func (s *SimpleImg) ExtractRootfs(opts compiler.CompilerBackendOptions, keepPerm
}
os.RemoveAll(path)
buildarg := []string{"unpack", "-o", path, name}
Debug(":tea: Extracting image " + name)
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("img", buildarg...).CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed extracting image: "+string(out))

View File

@@ -0,0 +1,48 @@
// Copyright © 2021 Daniele Rondina <geaaru@sabayonlinux.org>
// Ettore Di Giacinto <mudler@sabayonlinux.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 backend
import (
"bytes"
. "github.com/mudler/luet/pkg/logger"
)
type BackendWriter struct {
BufferedOutput bool
Buffer *bytes.Buffer
}
func NewBackendWriter(buffered bool) *BackendWriter {
return &BackendWriter{
BufferedOutput: buffered,
Buffer: &bytes.Buffer{},
}
}
func (b *BackendWriter) Write(p []byte) (int, error) {
if b.BufferedOutput {
return b.Buffer.Write(p)
}
Msg("info", false, false, (string(p)))
return len(p), nil
}
func (b *BackendWriter) Close() error { return nil }
func (b *BackendWriter) GetCombinedOutput() string { return b.Buffer.String() }

View File

@@ -51,6 +51,7 @@ type LuetCompiler struct {
CompressionType CompressionImplementation
Options CompilerOptions
SolverOptions solver.Options
BackedArgs []string
}
func NewLuetCompiler(backend CompilerBackend, db pkg.PackageDatabase, opt *CompilerOptions, solvopts solver.Options) Compiler {
@@ -70,7 +71,9 @@ func NewLuetCompiler(backend CompilerBackend, db pkg.PackageDatabase, opt *Compi
SolverOptions: solvopts,
}
}
func (cs *LuetCompiler) SetBackendArgs(args []string) {
cs.BackedArgs = args
}
func (cs *LuetCompiler) SetConcurrency(i int) {
cs.Concurrency = i
}
@@ -148,8 +151,6 @@ func (cs *LuetCompiler) CompileWithReverseDeps(keepPermissions bool, ps Compilat
}
func (cs *LuetCompiler) CompileParallel(keepPermissions bool, ps CompilationSpecs) ([]Artifact, []error) {
Spinner(22)
defer SpinnerStop()
all := make(chan CompilationSpec)
artifacts := []Artifact{}
mutex := &sync.Mutex{}
@@ -383,12 +384,14 @@ func (cs *LuetCompiler) buildPackageImage(image, buildertaggedImage, packageImag
SourcePath: buildDir,
DockerFileName: p.GetPackage().GetFingerPrint() + "-builder.dockerfile",
Destination: p.Rel(p.GetPackage().GetFingerPrint() + "-builder.image.tar"),
BackendArgs: cs.BackedArgs,
}
runnerOpts = CompilerBackendOptions{
ImageName: packageImage,
SourcePath: buildDir,
DockerFileName: p.GetPackage().GetFingerPrint() + ".dockerfile",
Destination: p.Rel(p.GetPackage().GetFingerPrint() + ".image.tar"),
BackendArgs: cs.BackedArgs,
}
buildAndPush := func(opts CompilerBackendOptions) error {

View File

@@ -34,6 +34,8 @@ type Compiler interface {
FromDatabase(db pkg.PackageDatabase, minimum bool, dst string) ([]CompilationSpec, error)
SetBackend(CompilerBackend)
GetBackend() CompilerBackend
SetBackendArgs([]string)
SetCompressionType(t CompressionImplementation)
}
@@ -43,6 +45,7 @@ type CompilerBackendOptions struct {
DockerFileName string
Destination string
Context string
BackendArgs []string
}
type CompilerOptions struct {

44
pkg/helpers/docker.go Normal file
View File

@@ -0,0 +1,44 @@
// Copyright © 2021 Ettore Di Giacinto <mudler@mocaccino.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 helpers
import (
"os"
"github.com/mudler/luet/pkg/helpers/imgworker"
"github.com/pkg/errors"
)
// DownloadAndExtractDockerImage is a re-adaption
// from genuinetools/img https://github.com/genuinetools/img/blob/54d0ca981c1260546d43961a538550eef55c87cf/pull.go
func DownloadAndExtractDockerImage(temp, image, dest string) (*imgworker.ListedImage, error) {
defer os.RemoveAll(temp)
c, err := imgworker.New(temp)
if err != nil {
return nil, errors.Wrapf(err, "failed creating client")
}
defer c.Close()
listedImage, err := c.Pull(image)
if err != nil {
return nil, errors.Wrapf(err, "failed listing images")
}
os.RemoveAll(dest)
err = c.Unpack(image, dest)
return listedImage, err
}

View File

@@ -16,6 +16,7 @@
package helpers
import (
"fmt"
"io"
"io/ioutil"
"os"
@@ -172,9 +173,18 @@ func CopyFile(src, dst string) (err error) {
return nil
}
return copy.Copy(src, dst, copy.Options{
err = copy.Copy(src, dst, copy.Options{
Sync: true,
OnSymlink: func(string) copy.SymlinkAction { return copy.Shallow }})
if err != nil {
return err
}
if stat, ok := fi.Sys().(*syscall.Stat_t); ok {
if err := os.Chown(dst, int(stat.Uid), int(stat.Gid)); err != nil {
fmt.Println("failed chowning", dst, err.Error())
}
}
return err
}
func IsDirectory(path string) (bool, error) {

View File

@@ -24,8 +24,6 @@ import (
"github.com/docker/go-units"
"github.com/pkg/errors"
imgworker "github.com/mudler/luet/pkg/installer/client/imgworker"
"github.com/mudler/luet/pkg/compiler"
"github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
@@ -40,37 +38,14 @@ func NewDockerClient(r RepoData) *DockerClient {
return &DockerClient{RepoData: r}
}
func downloadAndExtractDockerImage(image, dest string) error {
temp, err := config.LuetCfg.GetSystem().TempDir("contentstore")
if err != nil {
return err
}
defer os.RemoveAll(temp)
Debug("Temporary directory", temp)
c, err := imgworker.New(temp)
if err != nil {
return errors.Wrapf(err, "failed creating client")
}
defer c.Close()
// FROM Slightly adapted from genuinetools/img https://github.com/genuinetools/img/blob/54d0ca981c1260546d43961a538550eef55c87cf/pull.go
Debug("Pulling image", image)
listedImage, err := c.Pull(image)
if err != nil {
return errors.Wrapf(err, "failed listing images")
}
Debug("Pulled:", listedImage.Target.Digest)
Debug("Size:", units.BytesSize(float64(listedImage.ContentSize)))
Debug("Unpacking", image, "to", dest)
os.RemoveAll(dest)
return c.Unpack(image, dest)
}
func (c *DockerClient) DownloadArtifact(artifact compiler.Artifact) (compiler.Artifact, error) {
//var u *url.URL = nil
var err error
var temp string
Spinner(22)
defer SpinnerStop()
var resultingArtifact compiler.Artifact
artifactName := path.Base(artifact.GetPath())
cacheFile := filepath.Join(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), artifactName)
@@ -102,16 +77,25 @@ func (c *DockerClient) DownloadArtifact(artifact compiler.Artifact) (compiler.Ar
defer os.RemoveAll(temp)
for _, uri := range c.RepoData.Urls {
Debug("Downloading artifact", artifactName, "from", uri)
imageName := fmt.Sprintf("%s:%s", uri, artifact.GetCompileSpec().GetPackage().GetFingerPrint())
imageName := fmt.Sprintf("%s:%s", uri, artifact.GetCompileSpec().GetPackage().ImageID())
Info("Downloading image", imageName)
contentstore, err := config.LuetCfg.GetSystem().TempDir("contentstore")
if err != nil {
Warning("Cannot create contentstore", err.Error())
continue
}
// imageName := fmt.Sprintf("%s/%s", uri, artifact.GetCompileSpec().GetPackage().GetPackageImageName())
err = downloadAndExtractDockerImage(imageName, temp)
info, err := helpers.DownloadAndExtractDockerImage(contentstore, imageName, temp)
if err != nil {
Debug("Failed download of image", imageName)
continue
}
Info(fmt.Sprintf("Pulled: %s", info.Target.Digest))
Info(fmt.Sprintf("Size: %s", units.BytesSize(float64(info.ContentSize))))
Debug("\nCompressing result ", filepath.Join(temp), "to", cacheFile)
newart := artifact
@@ -158,16 +142,24 @@ func (c *DockerClient) DownloadFile(name string) (string, error) {
continue
}
Debug("Downloading file", name, "from", uri)
contentstore, err := config.LuetCfg.GetSystem().TempDir("contentstore")
if err != nil {
Warning("Cannot create contentstore", err.Error())
continue
}
imageName := fmt.Sprintf("%s:%s", uri, name)
//imageName := fmt.Sprintf("%s/%s:%s", uri, "repository", name)
err = downloadAndExtractDockerImage(imageName, temp)
Info("Downloading", imageName)
info, err := helpers.DownloadAndExtractDockerImage(contentstore, imageName, temp)
if err != nil {
Debug("Failed download of image", imageName)
continue
}
Info(fmt.Sprintf("Pulled: %s", info.Target.Digest))
Info(fmt.Sprintf("Size: %s", units.BytesSize(float64(info.ContentSize))))
Debug("\nCopying file ", filepath.Join(temp, name), "to", file.Name())
err = helpers.CopyFile(filepath.Join(temp, name), file.Name())

View File

@@ -47,6 +47,7 @@ type LuetInstallerOptions struct {
CheckConflicts bool
SolverUpgrade, RemoveUnavailableOnUpgrade, UpgradeNewRevisions bool
Ask bool
DownloadOnly bool
}
type LuetInstaller struct {
@@ -304,6 +305,9 @@ func (l *LuetInstaller) swap(syncedRepos Repositories, toRemove pkg.Packages, to
if err := l.download(syncedRepos, match); err != nil {
return errors.Wrap(err, "Pre-downloading packages")
}
if l.Options.DownloadOnly {
return nil
}
err = l.Uninstall(s, toRemove...)
if err != nil && !l.Options.Force {
@@ -530,6 +534,10 @@ func (l *LuetInstaller) install(syncedRepos Repositories, toInstall map[string]A
return errors.Wrap(err, "Downloading packages")
}
if l.Options.DownloadOnly {
return nil
}
all := make(chan ArtifactMatch)
wg := new(sync.WaitGroup)

View File

@@ -74,4 +74,6 @@ type Repository interface {
Serialize() (*LuetSystemRepositoryMetadata, LuetSystemRepositorySerialized)
GetBackend() compiler.CompilerBackend
SetBackend(b compiler.CompilerBackend)
FileSearch(pattern string) (pkg.Packages, error)
SearchArtefact(p pkg.Package) (compiler.Artifact, error)
}

View File

@@ -21,6 +21,7 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
@@ -86,17 +87,17 @@ type LuetSystemRepositoryMetadata struct {
Index []*compiler.PackageArtifact `json:"index,omitempty"`
}
type LuetSearchModeType string
type LuetSearchModeType int
const (
SLabel LuetSearchModeType = "label"
SRegexPkg LuetSearchModeType = "regexPkg"
SRegexLabel LuetSearchModeType = "regexLabel"
SLabel = iota
SRegexPkg = iota
SRegexLabel = iota
FileSearch = iota
)
type LuetSearchOpts struct {
Pattern string
Mode LuetSearchModeType
Mode LuetSearchModeType
}
func NewLuetSystemRepositoryMetadata(file string, removeFile bool) (*LuetSystemRepositoryMetadata, error) {
@@ -332,7 +333,7 @@ func generatePackageImages(b compiler.CompilerBackend, imagePrefix, path string,
return nil
}
packageImage := fmt.Sprintf("%s:%s", imagePrefix, artifact.GetCompileSpec().GetPackage().GetFingerPrint())
packageImage := fmt.Sprintf("%s:%s", imagePrefix, artifact.GetCompileSpec().GetPackage().ImageID())
if imagePush && b.ImageAvailable(packageImage) && !force {
Info("Image", packageImage, "already present, skipping. use --force-push to override")
@@ -406,6 +407,25 @@ func (r *LuetSystemRepository) SetPriority(n int) {
r.LuetRepository.Priority = n
}
// 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.GetFiles() {
if reg.MatchString(f) {
matches = append(matches, a.GetCompileSpec().GetPackage())
continue ARTIFACT
}
}
}
return matches, nil
}
func (r *LuetSystemRepository) GetName() string {
return r.LuetRepository.Name
}
@@ -881,6 +901,16 @@ func (r *LuetSystemRepository) Client() Client {
return nil
}
func (r *LuetSystemRepository) SearchArtefact(p pkg.Package) (compiler.Artifact, error) {
for _, a := range r.GetIndex() {
if a.GetCompileSpec().GetPackage().Matches(p) {
return a, nil
}
}
return nil, errors.New("Not found")
}
func (r *LuetSystemRepository) Sync(force bool) (Repository, error) {
var repoUpdated bool = false
var treefs, metafs string
@@ -1129,8 +1159,9 @@ func (r Repositories) SyncDatabase(d pkg.PackageDatabase) {
}
type PackageMatch struct {
Repo Repository
Package pkg.Package
Repo Repository
Artifact compiler.Artifact
Package pkg.Package
}
func (re Repositories) PackageMatches(p pkg.Packages) []PackageMatch {
@@ -1182,7 +1213,7 @@ PACKAGE:
}
func (re Repositories) SearchPackages(p string, o LuetSearchOpts) []PackageMatch {
func (re Repositories) SearchPackages(p string, t LuetSearchModeType) []PackageMatch {
sort.Sort(re)
var matches []PackageMatch
var err error
@@ -1190,18 +1221,21 @@ func (re Repositories) SearchPackages(p string, o LuetSearchOpts) []PackageMatch
for _, r := range re {
var repoMatches pkg.Packages
switch o.Mode {
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 {
matches = append(matches, PackageMatch{Package: pack, Repo: r})
a, _ := r.SearchArtefact(pack)
matches = append(matches, PackageMatch{Package: pack, Repo: r, Artifact: a})
}
}
}
@@ -1210,13 +1244,13 @@ func (re Repositories) SearchPackages(p string, o LuetSearchOpts) []PackageMatch
}
func (re Repositories) SearchLabelMatch(s string) []PackageMatch {
return re.SearchPackages(s, LuetSearchOpts{Pattern: s, Mode: SRegexLabel})
return re.SearchPackages(s, SRegexLabel)
}
func (re Repositories) SearchLabel(s string) []PackageMatch {
return re.SearchPackages(s, LuetSearchOpts{Pattern: s, Mode: SLabel})
return re.SearchPackages(s, SLabel)
}
func (re Repositories) Search(s string) []PackageMatch {
return re.SearchPackages(s, LuetSearchOpts{Pattern: s, Mode: SRegexPkg})
return re.SearchPackages(s, SRegexPkg)
}

View File

@@ -392,5 +392,80 @@ urls:
Expect(string(content)).To(Equal(""))
})
It("Searches files", func() {
repos := Repositories{
&LuetSystemRepository{
Index: compiler.ArtifactIndex{
&compiler.PackageArtifact{
CompileSpec: &compiler.LuetCompilationSpec{
Package: &pkg.DefaultPackage{},
},
Path: "bar",
Files: []string{"boo"},
},
&compiler.PackageArtifact{
Path: "d",
Files: []string{"baz"},
},
},
},
}
matches := repos.SearchPackages("bo", FileSearch)
Expect(len(matches)).To(Equal(1))
Expect(matches[0].Artifact.GetPath()).To(Equal("bar"))
})
It("Searches packages", func() {
repo := &LuetSystemRepository{
Index: compiler.ArtifactIndex{
&compiler.PackageArtifact{
Path: "foo",
CompileSpec: &compiler.LuetCompilationSpec{
Package: &pkg.DefaultPackage{
Name: "foo",
Category: "bar",
Version: "1.0",
},
},
},
&compiler.PackageArtifact{
Path: "baz",
CompileSpec: &compiler.LuetCompilationSpec{
Package: &pkg.DefaultPackage{
Name: "foo",
Category: "baz",
Version: "1.0",
},
},
},
},
}
a, err := repo.SearchArtefact(&pkg.DefaultPackage{
Name: "foo",
Category: "baz",
Version: "1.0",
})
Expect(err).ToNot(HaveOccurred())
Expect(a.GetPath()).To(Equal("baz"))
a, err = repo.SearchArtefact(&pkg.DefaultPackage{
Name: "foo",
Category: "bar",
Version: "1.0",
})
Expect(err).ToNot(HaveOccurred())
Expect(a.GetPath()).To(Equal("foo"))
// Doesn't exist. so must fail
_, err = repo.SearchArtefact(&pkg.DefaultPackage{
Name: "foo",
Category: "bar",
Version: "1.1",
})
Expect(err).To(HaveOccurred())
})
})
})

View File

@@ -8,6 +8,7 @@ import (
"runtime"
"strings"
"sync"
. "github.com/mudler/luet/pkg/config"
"github.com/briandowns/spinner"
@@ -20,7 +21,7 @@ import (
var s *spinner.Spinner = nil
var z *zap.Logger = nil
var aurora Aurora = nil
var spinnerLock = sync.Mutex{}
func NewSpinner() {
if s == nil {
s = spinner.New(
@@ -84,6 +85,8 @@ func ZapLogger() error {
}
func Spinner(i int) {
spinnerLock.Lock()
defer spinnerLock.Unlock()
var confLevel int
if LuetCfg.GetGeneral().Debug {
confLevel = 3
@@ -120,6 +123,8 @@ func SpinnerText(suffix, prefix string) {
}
func SpinnerStop() {
spinnerLock.Lock()
defer spinnerLock.Unlock()
var confLevel int
if LuetCfg.GetGeneral().Debug {
confLevel = 3
@@ -173,7 +178,7 @@ func level2AtomicLevel(level string) zap.AtomicLevel {
}
}
func msg(level string, withoutColor bool, msg ...interface{}) {
func Msg(level string, withoutColor, ln bool, msg ...interface{}) {
var message string
var confLevel, msgLevel int
@@ -219,11 +224,16 @@ func msg(level string, withoutColor bool, msg ...interface{}) {
log2File(level, message)
}
fmt.Println(levelMsg)
if ln {
fmt.Println(levelMsg)
} else {
fmt.Print(levelMsg)
}
}
func Warning(mess ...interface{}) {
msg("warning", false, mess...)
Msg("warning", false, true, mess...)
if LuetCfg.GetGeneral().FatalWarns {
os.Exit(2)
}
@@ -235,23 +245,23 @@ func Debug(mess ...interface{}) {
mess = append([]interface{}{fmt.Sprintf("DEBUG (%s:#%d:%v)",
path.Base(file), line, runtime.FuncForPC(pc).Name())}, mess...)
}
msg("debug", false, mess...)
Msg("debug", false, true, mess...)
}
func DebugC(mess ...interface{}) {
msg("debug", true, mess...)
Msg("debug", true, true, mess...)
}
func Info(mess ...interface{}) {
msg("info", false, mess...)
Msg("info", false, true, mess...)
}
func InfoC(mess ...interface{}) {
msg("info", true, mess...)
Msg("info", true, true, mess...)
}
func Error(mess ...interface{}) {
msg("error", false, mess...)
Msg("error", false, true, mess...)
}
func Fatal(mess ...interface{}) {

View File

@@ -52,6 +52,7 @@ type PackageSet interface {
FindPackageLabel(labelKey string) (Packages, error)
FindPackageLabelMatch(pattern string) (Packages, error)
FindPackageMatch(pattern string) (Packages, error)
FindPackageByFile(pattern string) (Packages, error)
}
type PackageFile struct {

View File

@@ -436,9 +436,9 @@ func (db *BoltDatabase) FindPackageLabel(labelKey string) (Packages, error) {
func (db *BoltDatabase) FindPackageLabelMatch(pattern string) (Packages, error) {
var ans []Package
re := regexp.MustCompile(pattern)
if re == nil {
return nil, errors.New("Invalid regex " + pattern + "!")
re, err := regexp.Compile(pattern)
if err != nil {
return nil, errors.Wrap(err, "Invalid regex "+pattern+"!")
}
for _, pack := range db.World() {
@@ -450,12 +450,15 @@ func (db *BoltDatabase) FindPackageLabelMatch(pattern string) (Packages, error)
return Packages(ans), nil
}
func (db *BoltDatabase) FindPackageByFile(pattern string) (Packages, error) {
return findPackageByFile(db, pattern)
}
func (db *BoltDatabase) FindPackageMatch(pattern string) (Packages, error) {
var ans []Package
re := regexp.MustCompile(pattern)
if re == nil {
return nil, errors.New("Invalid regex " + pattern + "!")
re, err := regexp.Compile(pattern)
if err != nil {
return nil, errors.Wrap(err, "Invalid regex "+pattern+"!")
}
for _, pack := range db.World() {

View File

@@ -57,6 +57,31 @@ var _ = Describe("BoltDB Database", func() {
})
It("Find package files", func() {
a := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
a1 := NewPackage("A", "1.1", []*DefaultPackage{}, []*DefaultPackage{})
a3 := NewPackage("A", "1.3", []*DefaultPackage{}, []*DefaultPackage{})
_, err := db.CreatePackage(a)
Expect(err).ToNot(HaveOccurred())
_, err = db.CreatePackage(a1)
Expect(err).ToNot(HaveOccurred())
_, err = db.CreatePackage(a3)
Expect(err).ToNot(HaveOccurred())
err = db.SetPackageFiles(&PackageFile{PackageFingerprint: a.GetFingerPrint(), Files: []string{"foo"}})
Expect(err).ToNot(HaveOccurred())
err = db.SetPackageFiles(&PackageFile{PackageFingerprint: a1.GetFingerPrint(), Files: []string{"bar"}})
Expect(err).ToNot(HaveOccurred())
pack, err := db.FindPackageByFile("fo")
Expect(err).ToNot(HaveOccurred())
Expect(len(pack)).To(Equal(1))
Expect(pack[0]).To(Equal(a))
})
It("Expands correctly", func() {
a := NewPackage("A", ">=1.0", []*DefaultPackage{}, []*DefaultPackage{})

View File

@@ -15,7 +15,11 @@
package pkg
import "github.com/pkg/errors"
import (
"regexp"
"github.com/pkg/errors"
)
func clone(src, dst PackageDatabase) error {
for _, i := range src.World() {
@@ -36,3 +40,29 @@ func copy(src PackageDatabase) (PackageDatabase, error) {
return dst, nil
}
func findPackageByFile(db PackageDatabase, pattern string) (Packages, error) {
var ans []Package
re, err := regexp.Compile(pattern)
if err != nil {
return nil, errors.Wrap(err, "Invalid regex "+pattern+"!")
}
PACKAGE:
for _, pack := range db.World() {
files, err := db.GetPackageFiles(pack)
if err == nil {
for _, f := range files {
if re.MatchString(f) {
ans = append(ans, pack)
continue PACKAGE
}
}
}
}
return Packages(ans), nil
}

View File

@@ -32,6 +32,7 @@ var DBInMemoryInstance = &InMemoryDatabase{
CacheNoVersion: map[string]map[string]interface{}{},
ProvidesDatabase: map[string]map[string]Package{},
RevDepsDatabase: map[string]map[string]Package{},
cached: map[string]interface{}{},
}
type InMemoryDatabase struct {
@@ -199,6 +200,10 @@ func (db *InMemoryDatabase) populateCaches(p Package) {
// Create extra cache between package -> []versions
db.Lock()
if db.cached == nil {
db.cached = map[string]interface{}{}
}
if _, ok := db.cached[p.GetFingerPrint()]; ok {
db.Unlock()
return
@@ -555,3 +560,7 @@ func (db *InMemoryDatabase) FindPackageMatch(pattern string) (Packages, error) {
return Packages(ans), nil
}
func (db *InMemoryDatabase) FindPackageByFile(pattern string) (Packages, error) {
return findPackageByFile(db, pattern)
}

View File

@@ -77,6 +77,32 @@ var _ = Describe("Database", func() {
})
It("Find package files", func() {
db := NewInMemoryDatabase(false)
a := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
a1 := NewPackage("A", "1.1", []*DefaultPackage{}, []*DefaultPackage{})
a3 := NewPackage("A", "1.3", []*DefaultPackage{}, []*DefaultPackage{})
_, err := db.CreatePackage(a)
Expect(err).ToNot(HaveOccurred())
_, err = db.CreatePackage(a1)
Expect(err).ToNot(HaveOccurred())
_, err = db.CreatePackage(a3)
Expect(err).ToNot(HaveOccurred())
err = db.SetPackageFiles(&PackageFile{PackageFingerprint: a.GetFingerPrint(), Files: []string{"foo"}})
Expect(err).ToNot(HaveOccurred())
err = db.SetPackageFiles(&PackageFile{PackageFingerprint: a1.GetFingerPrint(), Files: []string{"bar"}})
Expect(err).ToNot(HaveOccurred())
pack, err := db.FindPackageByFile("fo")
Expect(err).ToNot(HaveOccurred())
Expect(len(pack)).To(Equal(1))
Expect(pack[0]).To(Equal(a))
})
It("Find specific package candidate", func() {
db := NewInMemoryDatabase(false)
a := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})

View File

@@ -46,7 +46,7 @@ type Package interface {
GetFingerPrint() string
GetPackageName() string
GetPackageImageName() string
ImageID() string
Requires([]*DefaultPackage) Package
Conflicts([]*DefaultPackage) Package
Revdeps(PackageDatabase) Packages
@@ -289,8 +289,8 @@ func (p *DefaultPackage) GetPackageName() string {
return fmt.Sprintf("%s-%s", p.Name, p.Category)
}
func (p *DefaultPackage) GetPackageImageName() string {
return fmt.Sprintf("%s-%s:%s", p.Name, p.Category, p.Version)
func (p *DefaultPackage) ImageID() string {
return strings.ReplaceAll(p.GetFingerPrint(), "+", "-")
}
// GetBuildTimestamp returns the package build timestamp

View File

@@ -66,6 +66,17 @@ var _ = Describe("Package", func() {
})
})
Context("ImageID", func() {
It("Returns a correct ImageID escaping unsupported chars", func() {
p := NewPackage("A", "1.0+p1", []*DefaultPackage{}, []*DefaultPackage{})
Expect(p.ImageID()).To(Equal("A--1.0-p1"))
})
It("Returns a correct ImageID", func() {
p := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
Expect(p.ImageID()).To(Equal("A--1.0"))
})
})
Context("Find label on packages", func() {
a := NewPackage("A", ">=1.0", []*DefaultPackage{}, []*DefaultPackage{})
a.AddLabel("project1", "test1")

128
tests/integration/01_rootfs.sh Executable file
View File

@@ -0,0 +1,128 @@
#!/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/buildableseed" --destination $tmpdir/testbuild --compression gzip test/c > /dev/null
buildst=$?
assertEquals 'builds successfully' "$buildst" "0"
assertTrue 'create package dep B' "[ -e '$tmpdir/testbuild/b-test-1.0.package.tar.gz' ]"
assertTrue 'create package' "[ -e '$tmpdir/testbuild/c-test-1.0.package.tar.gz' ]"
}
testRepo() {
assertTrue 'no repository' "[ ! -e '$tmpdir/testbuild/repository.yaml' ]"
luet create-repo --tree "$ROOT_DIR/tests/fixtures/buildableseed" \
--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
cat <<EOF > $tmpdir/luet.yaml
general:
debug: true
system:
rootfs: $tmpdir/testrootfs
database_engine: "memory"
config_from_host: true
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() {
luet install -y --config $tmpdir/luet.yaml test/c
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package installed' "[ -e '$tmpdir/testrootfs/c' ]"
}
testCleanup() {
luet cleanup --config $tmpdir/luet.yaml
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package cleaned' "[ ! -e '$tmpdir/testrootfs/packages/c-test-1.0.package.tar.gz' ]"
}
testInstall2() {
luet install -y --config $tmpdir/luet.yaml --system-target $tmpdir/foo test/c
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'db not created' "[ ! -e '$tmpdir/foo/var/cache/luet/luet.db' ]"
assertTrue 'package installed' "[ -e '$tmpdir/foo/c' ]"
}
testCleanup2() {
luet cleanup --config $tmpdir/luet.yaml --system-target $tmpdir/foo
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package cleaned' "[ ! -e '$tmpdir/foo/packages/c-test-1.0.package.tar.gz' ]"
}
testInstall3() {
cat <<EOF > $tmpdir/luet2.yaml
general:
debug: true
config_from_host: true
repositories:
- name: "main"
type: "disk"
enable: true
urls:
- "$tmpdir/testbuild"
EOF
luet install -y --config $tmpdir/luet2.yaml --system-target $tmpdir/baz test/c
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package installed' "[ -e '$tmpdir/baz/c' ]"
}
testCleanup3() {
luet cleanup --config $tmpdir/luet2.yaml --system-target $tmpdir/baz
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package cleaned' "[ ! -e '$tmpdir/baz/packages/c-test-1.0.package.tar.gz' ]"
}
testInstall4() {
luet install -y --config $tmpdir/luet2.yaml --system-target $tmpdir/bad --system-engine boltdb test/c
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package installed' "[ -e '$tmpdir/bad/c' ]"
assertTrue 'db created' "[ -d '$tmpdir/bad/var/cache/luet' ]"
}
testCleanup4() {
luet cleanup --config $tmpdir/luet2.yaml --system-target $tmpdir/bad --system-engine boltdb
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package cleaned' "[ ! -e '$tmpdir/bad/packages/c-test-1.0.package.tar.gz' ]"
}
# Load shUnit2.
. "$ROOT_DIR/tests/integration/shunit2"/shunit2

View File

@@ -0,0 +1,80 @@
#!/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/buildableseed" --destination $tmpdir/testbuild --compression gzip test/c > /dev/null
buildst=$?
assertEquals 'builds successfully' "$buildst" "0"
assertTrue 'create package dep B' "[ -e '$tmpdir/testbuild/b-test-1.0.package.tar.gz' ]"
assertTrue 'create package' "[ -e '$tmpdir/testbuild/c-test-1.0.package.tar.gz' ]"
}
testRepo() {
assertTrue 'no repository' "[ ! -e '$tmpdir/testbuild/repository.yaml' ]"
luet create-repo --tree "$ROOT_DIR/tests/fixtures/buildableseed" \
--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
cat <<EOF > $tmpdir/luet.yaml
general:
debug: true
system:
rootfs: $tmpdir/testrootfs
database_engine: "memory"
config_from_host: true
repositories:
- name: "main"
type: "disk"
enable: true
urls:
- "$tmpdir/testbuild"
EOF
luet config --config $tmpdir/luet.yaml
res=$?
assertEquals 'config test successfully' "$res" "0"
}
testDownloadOnly() {
luet install -y --download-only --config $tmpdir/luet.yaml test/c > /dev/null
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package not installed' "[ ! -e '$tmpdir/testrootfs/c' ]"
assertTrue 'cache populated' "[ -e '$tmpdir/testrootfs/var/cache/luet/packages/c-test-1.0.package.tar.gz' ]"
luet install -y --config $tmpdir/luet.yaml test/c > /dev/null
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package installed' "[ -e '$tmpdir/testrootfs/c' ]"
}
testCleanup() {
luet cleanup --config $tmpdir/luet.yaml > /dev/null
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package installed' "[ ! -e '$tmpdir/testrootfs/packages/c-test-1.0.package.tar.gz' ]"
}
# Load shUnit2.
. "$ROOT_DIR/tests/integration/shunit2"/shunit2

View File

@@ -0,0 +1,74 @@
#!/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/buildableseed" --destination $tmpdir/testbuild --compression gzip test/c > /dev/null
buildst=$?
assertEquals 'builds successfully' "$buildst" "0"
assertTrue 'create package dep B' "[ -e '$tmpdir/testbuild/b-test-1.0.package.tar.gz' ]"
assertTrue 'create package' "[ -e '$tmpdir/testbuild/c-test-1.0.package.tar.gz' ]"
}
testRepo() {
assertTrue 'no repository' "[ ! -e '$tmpdir/testbuild/repository.yaml' ]"
luet create-repo --tree "$ROOT_DIR/tests/fixtures/buildableseed" \
--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
cat <<EOF > $tmpdir/luet.yaml
general:
debug: true
system:
rootfs: $tmpdir/testrootfs
database_engine: "memory"
config_from_host: true
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() {
luet install -y --config $tmpdir/luet.yaml test/c
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package installed' "[ -e '$tmpdir/testrootfs/c' ]"
}
testCleanup() {
luet cleanup --config $tmpdir/luet.yaml
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package installed' "[ ! -e '$tmpdir/testrootfs/packages/c-test-1.0.package.tar.gz' ]"
}
# Load shUnit2.
. "$ROOT_DIR/tests/integration/shunit2"/shunit2

View File

@@ -0,0 +1,128 @@
#!/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/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/b@1.0
buildst=$?
assertTrue 'create package B 1.0' "[ -e '$tmpdir/testbuild/b-test-1.0.package.tar.gz' ]"
assertEquals 'builds successfully' "$buildst" "0"
luet build --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/b@1.1
buildst=$?
assertEquals 'builds successfully' "$buildst" "0"
assertTrue 'create package B 1.1' "[ -e '$tmpdir/testbuild/b-test-1.1.package.tar.gz' ]"
luet build --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/a@1.0
buildst=$?
assertEquals 'builds successfully' "$buildst" "0"
assertTrue 'create package A 1.0' "[ -e '$tmpdir/testbuild/a-test-1.0.package.tar.gz' ]"
luet build --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/a@1.1
buildst=$?
assertEquals 'builds successfully' "$buildst" "0"
assertTrue 'create package A 1.1' "[ -e '$tmpdir/testbuild/a-test-1.1.package.tar.gz' ]"
luet build --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/a@1.2
buildst=$?
assertEquals 'builds successfully' "$buildst" "0"
assertTrue 'create package A 1.2' "[ -e '$tmpdir/testbuild/a-test-1.2.package.tar.gz' ]"
luet build --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" --destination $tmpdir/testbuild --compression gzip test/c@1.0
buildst=$?
assertEquals 'builds successfully' "$buildst" "0"
assertTrue 'create package C 1.0' "[ -e '$tmpdir/testbuild/c-test-1.0.package.tar.gz' ]"
}
testRepo() {
assertTrue 'no repository' "[ ! -e '$tmpdir/testbuild/repository.yaml' ]"
luet create-repo --tree "$ROOT_DIR/tests/fixtures/upgrade_integration" \
--output $tmpdir/testbuild \
--packages $tmpdir/testbuild \
--name "test" \
--descr "Test Repo" \
--urls $tmpdir/testrootfs \
--type disk
createst=$?
assertEquals 'create repo successfully' "$createst" "0"
assertTrue 'create repository' "[ -e '$tmpdir/testbuild/repository.yaml' ]"
}
testConfig() {
mkdir $tmpdir/testrootfs
cat <<EOF > $tmpdir/luet.yaml
general:
debug: true
system:
rootfs: $tmpdir/testrootfs
database_path: "/"
database_engine: "boltdb"
config_from_host: true
repositories:
- name: "main"
type: "disk"
enable: true
urls:
- "$tmpdir/testbuild"
EOF
luet config --config $tmpdir/luet.yaml
res=$?
assertEquals 'config test successfully' "$res" "0"
}
testSearch() {
installed=$(luet --config $tmpdir/luet.yaml search --files testaa)
searchst=$?
assertEquals 'search exists successfully' "$searchst" "0"
assertContains 'contains test/a-1.0' "$installed" 'test/a-1.0'
}
testGetSearchLocal() {
luet install -y --config $tmpdir/luet.yaml test/a@1.0
assertTrue 'package installed A' "[ -e '$tmpdir/testrootfs/testaa' ]"
installst=$?
assertEquals 'install test successfully' "$installst" "0"
installed=$(luet --config $tmpdir/luet.yaml database get --files test/a@1.0)
searchst=$?
assertEquals 'search exists successfully' "$searchst" "0"
assertContains 'contains file' "$installed" 'testaa'
installed=$(luet --config $tmpdir/luet.yaml database get test/a@1.0)
searchst=$?
assertEquals 'search exists successfully' "$searchst" "0"
assertNotContains 'contains file' "$installed" 'testaa'
installed=$(luet --config $tmpdir/luet.yaml search --installed --files testaa)
searchst=$?
assertEquals 'search exists successfully' "$searchst" "0"
assertContains 'contains test/a-1.1' "$installed" 'test/a-1.0'
installed=$(luet --config $tmpdir/luet.yaml search --installed --files foo)
searchst=$?
assertEquals 'search exists successfully' "$searchst" "0"
assertNotContains 'contains test/a-1.1' "$installed" 'test/a-1.0'
}
# Load shUnit2.
. "$ROOT_DIR/tests/integration/shunit2"/shunit2

View File

@@ -1,7 +1,10 @@
arch:
- amd64
- ppc64le
language: go
go:
- 1.11
- 1.12.5
- 1.13
- 1.14.1
env:
- GOARCH: amd64
- GOARCH: 386

View File

@@ -17,52 +17,52 @@ go get github.com/briandowns/spinner
## Available Character Sets
(Numbered by their slice index)
index | character set | sample gif
------|---------------|---------------
0 | ```←↖↑↗→↘↓↙``` | ![Sample Gif](gifs/0.gif)
1 | ```▁▃▄▅▆▇█▇▆▅▄▃▁``` | ![Sample Gif](gifs/1.gif)
2 | ```▖▘▝▗``` | ![Sample Gif](gifs/2.gif)
3 | ```┤┘┴└├┌┬┐``` | ![Sample Gif](gifs/3.gif)
4 | ```◢◣◤◥``` | ![Sample Gif](gifs/4.gif)
5 | ```◰◳◲◱``` | ![Sample Gif](gifs/5.gif)
6 | ```◴◷◶◵``` | ![Sample Gif](gifs/6.gif)
7 | ```◐◓◑◒``` | ![Sample Gif](gifs/7.gif)
8 | ```.oO@*``` | ![Sample Gif](gifs/8.gif)
9 | ```|/-\``` | ![Sample Gif](gifs/9.gif)
10 | ```◡◡⊙⊙◠◠``` | ![Sample Gif](gifs/10.gif)
11 | ```⣾⣽⣻⢿⡿⣟⣯⣷``` | ![Sample Gif](gifs/11.gif)
12 | ```>))'> >))'> >))'> >))'> >))'> <'((< <'((< <'((<``` | ![Sample Gif](gifs/12.gif)
13 | ```⠁⠂⠄⡀⢀⠠⠐⠈``` | ![Sample Gif](gifs/13.gif)
14 | ```⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏``` | ![Sample Gif](gifs/14.gif)
15 | ```abcdefghijklmnopqrstuvwxyz``` | ![Sample Gif](gifs/15.gif)
16 | ```▉▊▋▌▍▎▏▎▍▌▋▊▉``` | ![Sample Gif](gifs/16.gif)
17 | ```■□▪▫``` | ![Sample Gif](gifs/17.gif)
18 | ```←↑→↓``` | ![Sample Gif](gifs/18.gif)
19 | ```╫╪``` | ![Sample Gif](gifs/19.gif)
20 | ```⇐⇖⇑⇗⇒⇘⇓⇙``` | ![Sample Gif](gifs/20.gif)
21 | ```⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈``` | ![Sample Gif](gifs/21.gif)
22 | ```⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈``` | ![Sample Gif](gifs/22.gif)
23 | ```⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁``` | ![Sample Gif](gifs/23.gif)
24 | ```⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋``` | ![Sample Gif](gifs/24.gif)
25 | ```ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン``` | ![Sample Gif](gifs/25.gif)
26 | ```. .. ...``` | ![Sample Gif](gifs/26.gif)
27 | ```▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▏▎▍▌▋▊▉█▇▆▅▄▃▂▁``` | ![Sample Gif](gifs/27.gif)
28 | ```.oO°Oo.``` | ![Sample Gif](gifs/28.gif)
29 | ```+x``` | ![Sample Gif](gifs/29.gif)
30 | ```v<^>``` | ![Sample Gif](gifs/30.gif)
31 | ```>>---> >>---> >>---> >>---> >>---> <---<< <---<< <---<< <---<< <---<<``` | ![Sample Gif](gifs/31.gif)
32 | ```| || ||| |||| ||||| |||||| ||||| |||| ||| || |``` | ![Sample Gif](gifs/32.gif)
33 | ```[] [=] [==] [===] [====] [=====] [======] [=======] [========] [=========] [==========]``` | ![Sample Gif](gifs/33.gif)
34 | ```(*---------) (-*--------) (--*-------) (---*------) (----*-----) (-----*----) (------*---) (-------*--) (--------*-) (---------*)``` | ![Sample Gif](gifs/34.gif)
35 | ```█▒▒▒▒▒▒▒▒▒ ███▒▒▒▒▒▒▒ █████▒▒▒▒▒ ███████▒▒▒ ██████████``` | ![Sample Gif](gifs/35.gif)
36 | ```[ ] [=> ] [===> ] [=====> ] [======> ] [========> ] [==========> ] [============> ] [==============> ] [================> ] [==================> ] [===================>]``` | ![Sample Gif](gifs/36.gif)
37 | ```🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛``` | ![Sample Gif](gifs/37.gif)
38 | ```🕐 🕜 🕑 🕝 🕒 🕞 🕓 🕟 🕔 🕠 🕕 🕡 🕖 🕢 🕗 🕣 🕘 🕤 🕙 🕥 🕚 🕦 🕛 🕧``` | ![Sample Gif](gifs/38.gif)
39 | ```🌍 🌎 🌏``` | ![Sample Gif](gifs/39.gif)
40 | ```◜ ◝ ◞ ◟``` | ![Sample Gif](gifs/40.gif)
41 | ```⬒ ⬔ ⬓ ⬕``` | ![Sample Gif](gifs/41.gif)
42 | ```⬖ ⬘ ⬗ ⬙``` | ![Sample Gif](gifs/42.gif)
43 | ```[>>> >] []>>>> [] [] >>>> [] [] >>>> [] [] >>>> [] [] >>>>[] [>> >>]``` | ![Sample Gif](gifs/43.gif)
| index | character set | sample gif |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| 0 | ```←↖↑↗→↘↓↙``` | ![Sample Gif](gifs/0.gif) |
| 1 | ```▁▃▄▅▆▇█▇▆▅▄▃▁``` | ![Sample Gif](gifs/1.gif) |
| 2 | ```▖▘▝▗``` | ![Sample Gif](gifs/2.gif) |
| 3 | ```┤┘┴└├┌┬┐``` | ![Sample Gif](gifs/3.gif) |
| 4 | ```◢◣◤◥``` | ![Sample Gif](gifs/4.gif) |
| 5 | ```◰◳◲◱``` | ![Sample Gif](gifs/5.gif) |
| 6 | ```◴◷◶◵``` | ![Sample Gif](gifs/6.gif) |
| 7 | ```◐◓◑◒``` | ![Sample Gif](gifs/7.gif) |
| 8 | ```.oO@*``` | ![Sample Gif](gifs/8.gif) |
| 9 | ```\|/-\``` | ![Sample Gif](gifs/9.gif) |
| 10 | ```◡◡⊙⊙◠◠``` | ![Sample Gif](gifs/10.gif) |
| 11 | ```⣾⣽⣻⢿⡿⣟⣯⣷``` | ![Sample Gif](gifs/11.gif) |
| 12 | ```>))'> >))'> >))'> >))'> >))'> <'((< <'((< <'((<``` | ![Sample Gif](gifs/12.gif) |
| 13 | ```⠁⠂⠄⡀⢀⠠⠐⠈``` | ![Sample Gif](gifs/13.gif) |
| 14 | ```⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏``` | ![Sample Gif](gifs/14.gif) |
| 15 | ```abcdefghijklmnopqrstuvwxyz``` | ![Sample Gif](gifs/15.gif) |
| 16 | ```▉▊▋▌▍▎▏▎▍▌▋▊▉``` | ![Sample Gif](gifs/16.gif) |
| 17 | ```■□▪▫``` | ![Sample Gif](gifs/17.gif) |
| 18 | ```←↑→↓``` | ![Sample Gif](gifs/18.gif) |
| 19 | ```╫╪``` | ![Sample Gif](gifs/19.gif) |
| 20 | ```⇐⇖⇑⇗⇒⇘⇓⇙``` | ![Sample Gif](gifs/20.gif) |
| 21 | ```⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈``` | ![Sample Gif](gifs/21.gif) |
| 22 | ```⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈``` | ![Sample Gif](gifs/22.gif) |
| 23 | ```⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁``` | ![Sample Gif](gifs/23.gif) |
| 24 | ```⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋``` | ![Sample Gif](gifs/24.gif) |
| 25 | ```ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン``` | ![Sample Gif](gifs/25.gif) |
| 26 | ```. .. ...``` | ![Sample Gif](gifs/26.gif) |
| 27 | ```▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▏▎▍▌▋▊▉█▇▆▅▄▃▂▁``` | ![Sample Gif](gifs/27.gif) |
| 28 | ```.oO°Oo.``` | ![Sample Gif](gifs/28.gif) |
| 29 | ```+x``` | ![Sample Gif](gifs/29.gif) |
| 30 | ```v<^>``` | ![Sample Gif](gifs/30.gif) |
| 31 | ```>>---> >>---> >>---> >>---> >>---> <---<< <---<< <---<< <---<< <---<<``` | ![Sample Gif](gifs/31.gif) |
| 32 | ```\| \|\| \|\|\| \|\|\|\| \|\|\|\|\| \|\|\|\|\|\| \|\|\|\|\| \|\|\|\| \|\|\| \|\| \|``` | ![Sample Gif](gifs/32.gif) |
| 33 | ```[] [=] [==] [===] [====] [=====] [======] [=======] [========] [=========] [==========]``` | ![Sample Gif](gifs/33.gif) |
| 34 | ```(*---------) (-*--------) (--*-------) (---*------) (----*-----) (-----*----) (------*---) (-------*--) (--------*-) (---------*)``` | ![Sample Gif](gifs/34.gif) |
| 35 | ```█▒▒▒▒▒▒▒▒▒ ███▒▒▒▒▒▒▒ █████▒▒▒▒▒ ███████▒▒▒ ██████████``` | ![Sample Gif](gifs/35.gif) |
| 36 | ```[ ] [=> ] [===> ] [=====> ] [======> ] [========> ] [==========> ] [============> ] [==============> ] [================> ] [==================> ] [===================>]``` | ![Sample Gif](gifs/36.gif) |
| 37 | ```🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛``` | ![Sample Gif](gifs/37.gif) |
| 38 | ```🕐 🕜 🕑 🕝 🕒 🕞 🕓 🕟 🕔 🕠 🕕 🕡 🕖 🕢 🕗 🕣 🕘 🕤 🕙 🕥 🕚 🕦 🕛 🕧``` | ![Sample Gif](gifs/38.gif) |
| 39 | ```🌍 🌎 🌏``` | ![Sample Gif](gifs/39.gif) |
| 40 | ```◜ ◝ ◞ ◟``` | ![Sample Gif](gifs/40.gif) |
| 41 | ```⬒ ⬔ ⬓ ⬕``` | ![Sample Gif](gifs/41.gif) |
| 42 | ```⬖ ⬘ ⬗ ⬙``` | ![Sample Gif](gifs/42.gif) |
| 43 | ```[>>> >] []>>>> [] [] >>>> [] [] >>>> [] [] >>>> [] [] >>>>[] [>> >>]``` | ![Sample Gif](gifs/43.gif) |
## Features
@@ -139,7 +139,7 @@ s.Prefix = "prefixed text: " // Prefix text before the spinner
s.Suffix = " :appended text" // Append text after the spinner
```
## Set or change the color of the spinner. Default color is white. This will restart the spinner with the new color.
## Set or change the color of the spinner. Default color is white. The spinner will need to be restarted to pick up the change.
```Go
s.Color("red") // Set the spinner color to red
@@ -240,12 +240,13 @@ fmt.Println(s.Active())
Feature suggested and write up by [dekz](https://github.com/dekz)
Setting the Spinner Writer to Stderr helps show progress to the user, with the enhancement to chain, pipe or redirect the output.
Setting the Spinner Writer to Stderr helps show progress to the user, with the enhancement to chain, pipe or redirect the output.
This is the preferred method of setting a Writer at this time.
```go
s := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
s := spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(os.Stderr))
s.Suffix = " Encrypting data..."
s.Writer = os.Stderr
s.Start()
// Encrypt the data into ciphertext
fmt.Println(os.Stdout, ciphertext)

View File

@@ -1,5 +1,7 @@
module github.com/briandowns/spinner
go 1.14
require (
github.com/fatih/color v1.7.0
github.com/mattn/go-colorable v0.1.2 // indirect

View File

@@ -14,13 +14,13 @@
package spinner
import (
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"
"unicode/utf8"
@@ -161,7 +161,7 @@ var colorAttributeMap = map[string]color.Attribute{
"bgHiWhite": color.BgHiWhite,
}
// validColor will make sure the given color is actually allowed
// validColor will make sure the given color is actually allowed.
func validColor(c string) bool {
if validColors[c] {
return true
@@ -169,8 +169,9 @@ func validColor(c string) bool {
return false
}
// Spinner struct to hold the provided options
// Spinner struct to hold the provided options.
type Spinner struct {
mu *sync.RWMutex //
Delay time.Duration // Delay is the speed of the indicator
chars []string // chars holds the chosen character set
Prefix string // Prefix is the text preppended to the indicator
@@ -178,20 +179,21 @@ type Spinner struct {
FinalMSG string // string displayed after Stop() is called
lastOutput string // last character(set) written
color func(a ...interface{}) string // default color is white
lock *sync.RWMutex //
Writer io.Writer // to make testing better, exported so users have access
Writer io.Writer // to make testing better, exported so users have access. Use `WithWriter` to update after initialization.
active bool // active holds the state of the spinner
stopChan chan struct{} // stopChan is a channel used to stop the indicator
HideCursor bool // hideCursor determines if the cursor is visible
PreUpdate func(s *Spinner) // will be triggered before every spinner update
PostUpdate func(s *Spinner) // will be triggered after every spinner update
}
// New provides a pointer to an instance of Spinner with the supplied options
// New provides a pointer to an instance of Spinner with the supplied options.
func New(cs []string, d time.Duration, options ...Option) *Spinner {
s := &Spinner{
Delay: d,
chars: cs,
color: color.New(color.FgWhite).SprintFunc(),
lock: &sync.RWMutex{},
mu: &sync.RWMutex{},
Writer: color.Output,
active: false,
stopChan: make(chan struct{}, 1),
@@ -204,10 +206,10 @@ func New(cs []string, d time.Duration, options ...Option) *Spinner {
}
// Option is a function that takes a spinner and applies
// a given configuration
// a given configuration.
type Option func(*Spinner)
// Options contains fields to configure the spinner
// Options contains fields to configure the spinner.
type Options struct {
Color string
Suffix string
@@ -215,7 +217,7 @@ type Options struct {
HideCursor bool
}
// WithColor adds the given color to the spinner
// WithColor adds the given color to the spinner.
func WithColor(color string) Option {
return func(s *Spinner) {
s.Color(color)
@@ -223,7 +225,7 @@ func WithColor(color string) Option {
}
// WithSuffix adds the given string to the spinner
// as the suffix
// as the suffix.
func WithSuffix(suffix string) Option {
return func(s *Spinner) {
s.Suffix = suffix
@@ -231,7 +233,7 @@ func WithSuffix(suffix string) Option {
}
// WithFinalMSG adds the given string ot the spinner
// as the final message to be written
// as the final message to be written.
func WithFinalMSG(finalMsg string) Option {
return func(s *Spinner) {
s.FinalMSG = finalMsg
@@ -239,31 +241,42 @@ func WithFinalMSG(finalMsg string) Option {
}
// WithHiddenCursor hides the cursor
// if hideCursor = true given
// if hideCursor = true given.
func WithHiddenCursor(hideCursor bool) Option {
return func(s *Spinner) {
s.HideCursor = hideCursor
}
}
// Active will return whether or not the spinner is currently active
// WithWriter adds the given writer to the spinner. This
// function should be favored over directly assigning to
// the struct value.
func WithWriter(w io.Writer) Option {
return func(s *Spinner) {
s.mu.Lock()
s.Writer = w
s.mu.Unlock()
}
}
// Active will return whether or not the spinner is currently active.
func (s *Spinner) Active() bool {
return s.active
}
// Start will start the indicator
// Start will start the indicator.
func (s *Spinner) Start() {
s.lock.Lock()
s.mu.Lock()
if s.active {
s.lock.Unlock()
s.mu.Unlock()
return
}
if s.HideCursor && runtime.GOOS != "windows" {
// hides the cursor
fmt.Print("\033[?25l")
fmt.Fprint(s.Writer, "\033[?25l")
}
s.active = true
s.lock.Unlock()
s.mu.Unlock()
go func() {
for {
@@ -272,8 +285,17 @@ func (s *Spinner) Start() {
case <-s.stopChan:
return
default:
s.lock.Lock()
s.mu.Lock()
if !s.active {
s.mu.Unlock()
return
}
s.erase()
if s.PreUpdate != nil {
s.PreUpdate(s)
}
var outColor string
if runtime.GOOS == "windows" {
if s.Writer == os.Stderr {
@@ -282,14 +304,18 @@ func (s *Spinner) Start() {
outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
}
} else {
outColor = fmt.Sprintf("%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
}
outPlain := fmt.Sprintf("%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
outPlain := fmt.Sprintf("\r%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
fmt.Fprint(s.Writer, outColor)
s.lastOutput = outPlain
delay := s.Delay
s.lock.Unlock()
if s.PostUpdate != nil {
s.PostUpdate(s)
}
s.mu.Unlock()
time.Sleep(delay)
}
}
@@ -297,40 +323,41 @@ func (s *Spinner) Start() {
}()
}
// Stop stops the indicator
// Stop stops the indicator.
func (s *Spinner) Stop() {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
if s.active {
s.active = false
if s.HideCursor && runtime.GOOS != "windows" {
// makes the cursor visible
fmt.Print("\033[?25h")
fmt.Fprint(s.Writer, "\033[?25h")
}
s.erase()
if s.FinalMSG != "" {
fmt.Fprintf(s.Writer, s.FinalMSG)
fmt.Fprint(s.Writer, s.FinalMSG)
}
s.stopChan <- struct{}{}
}
}
// Restart will stop and start the indicator
// Restart will stop and start the indicator.
func (s *Spinner) Restart() {
s.Stop()
s.Start()
}
// Reverse will reverse the order of the slice assigned to the indicator
// Reverse will reverse the order of the slice assigned to the indicator.
func (s *Spinner) Reverse() {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 {
s.chars[i], s.chars[j] = s.chars[j], s.chars[i]
}
}
// Color will set the struct field for the given color to be used
// Color will set the struct field for the given color to be used. The spinner
// will need to be explicitly restarted.
func (s *Spinner) Color(colors ...string) error {
colorAttributes := make([]color.Attribute, len(colors))
@@ -342,63 +369,55 @@ func (s *Spinner) Color(colors ...string) error {
colorAttributes[index] = colorAttributeMap[c]
}
s.lock.Lock()
s.mu.Lock()
s.color = color.New(colorAttributes...).SprintFunc()
s.lock.Unlock()
s.Restart()
s.mu.Unlock()
return nil
}
// UpdateSpeed will set the indicator delay to the given value
// UpdateSpeed will set the indicator delay to the given value.
func (s *Spinner) UpdateSpeed(d time.Duration) {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
s.Delay = d
}
// UpdateCharSet will change the current character set to the given one
// UpdateCharSet will change the current character set to the given one.
func (s *Spinner) UpdateCharSet(cs []string) {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
s.chars = cs
}
// erase deletes written characters
//
// erase deletes written characters.
// Caller must already hold s.lock.
func (s *Spinner) erase() {
n := utf8.RuneCountInString(s.lastOutput)
if runtime.GOOS == "windows" {
var clearString string
for i := 0; i < n; i++ {
clearString += " "
}
clearString += "\r"
fmt.Fprintf(s.Writer, clearString)
clearString := "\r" + strings.Repeat(" ", n) + "\r"
fmt.Fprint(s.Writer, clearString)
s.lastOutput = ""
return
}
del, _ := hex.DecodeString("7f")
for _, c := range []string{"\b", string(del), "\b", "\033[K"} { // "\033[K" for macOS Terminal
for i := 0; i < n; i++ {
fmt.Fprintf(s.Writer, c)
}
for _, c := range []string{"\b", "\127", "\b", "\033[K"} { // "\033[K" for macOS Terminal
fmt.Fprint(s.Writer, strings.Repeat(c, n))
}
fmt.Fprintf(s.Writer, "\r\033[K") // erases to end of line
s.lastOutput = ""
}
// Lock allows for manual control to lock the spinner
// Lock allows for manual control to lock the spinner.
func (s *Spinner) Lock() {
s.lock.Lock()
s.mu.Lock()
}
// Unlock allows for manual control to unlock the spinner
// Unlock allows for manual control to unlock the spinner.
func (s *Spinner) Unlock() {
s.lock.Unlock()
s.mu.Unlock()
}
// GenerateNumberSequence will generate a slice of integers at the
// provided length and convert them each to a string
// provided length and convert them each to a string.
func GenerateNumberSequence(length int) []string {
numSeq := make([]string, length)
for i := 0; i < length; i++ {

2
vendor/modules.txt vendored
View File

@@ -49,7 +49,7 @@ github.com/asdine/storm/codec/json
github.com/asdine/storm/index
github.com/asdine/storm/internal
github.com/asdine/storm/q
# github.com/briandowns/spinner v1.7.0
# github.com/briandowns/spinner v1.12.1-0.20201220203425-e201aaea0a31
## explicit
github.com/briandowns/spinner
# github.com/cavaliercoder/grab v1.0.1-0.20201108051000-98a5bfe305ec