Compare commits

..

15 Commits

Author SHA1 Message Date
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
19 changed files with 404 additions and 163 deletions

View File

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

View File

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

2
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/Sabayon/pkgs-checker v0.7.2 github.com/Sabayon/pkgs-checker v0.7.2
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/asdine/storm v0.0.0-20190418133842-e0f77eada154 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/cavaliercoder/grab v1.0.1-0.20201108051000-98a5bfe305ec
github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc
github.com/crillab/gophersat v1.3.2-0.20201023142334-3fc2ac466765 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.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/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/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.12.1-0.20201220203425-e201aaea0a31 h1:yInAg9pE5qGec5eQ7XdfOTTaGwGxD3bKFVjmD6VKkwc=
github.com/briandowns/spinner v1.7.0/go.mod h1://Zf9tMcxfRUA36V23M6YGEAv+kECGfvpnLTnb8n4XQ= 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/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/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= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=

View File

@@ -16,8 +16,14 @@
package backend package backend
import ( import (
"github.com/google/go-containerregistry/pkg/crane" "os/exec"
"github.com/mudler/luet/pkg/compiler" "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 ( const (
@@ -41,3 +47,40 @@ func NewBackend(s string) compiler.CompilerBackend {
} }
return 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 // TODO: Missing still: labels, and build args expansion
func (*SimpleDocker) BuildImage(opts compiler.CompilerBackendOptions) error { func (*SimpleDocker) BuildImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName 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) Info(":whale2: Building image " + name)
cmd := exec.Command("docker", buildarg...) cmd := exec.Command("docker", buildarg...)
cmd.Dir = path cmd.Dir = opts.SourcePath
out, err := cmd.CombinedOutput() err := runCommand(cmd)
if err != nil { if err != nil {
return errors.Wrap(err, "Failed building image: "+string(out)) return err
} }
Info(":whale: Building image " + name + " done") Info(":whale: Building image " + name + " done")
if os.Getenv("DOCKER_SQUASH") == "true" { if os.Getenv("DOCKER_SQUASH") == "true" {
Info(":whale: Squashing image " + name) Info(":whale: Squashing image " + name)
var client *docker.Client var client *docker.Client
Spinner(22)
defer SpinnerStop()
client, err = docker.NewClientFromEnv() client, err = docker.NewClientFromEnv()
if err != nil { if err != nil {
return errors.Wrap(err, "could not connect to the Docker daemon") 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 { if err != nil {
return errors.Wrap(err, "Failed squashing image") return errors.Wrap(err, "Failed squashing image")
} }
Info(":whale: Squashing image " + name + " done")
}
if config.LuetCfg.GetGeneral().ShowBuildOutput { Info(":whale: Squashing image " + name + " done")
Info(string(out))
} else {
Debug(string(out))
} }
return nil return nil
@@ -101,11 +92,16 @@ func (*SimpleDocker) DownloadImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName name := opts.ImageName
buildarg := []string{"pull", name} buildarg := []string{"pull", name}
Debug(":whale: Downloading image " + name) Debug(":whale: Downloading image " + name)
Spinner(22)
defer SpinnerStop()
cmd := exec.Command("docker", buildarg...) cmd := exec.Command("docker", buildarg...)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return errors.Wrap(err, "Failed pulling image: "+string(out)) return errors.Wrap(err, "Failed pulling image: "+string(out))
} }
Info(":whale: Downloaded image:", name) Info(":whale: Downloaded image:", name)
return nil return nil
} }
@@ -142,6 +138,10 @@ func (*SimpleDocker) RemoveImage(opts compiler.CompilerBackendOptions) error {
func (*SimpleDocker) Push(opts compiler.CompilerBackendOptions) error { func (*SimpleDocker) Push(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName name := opts.ImageName
pusharg := []string{"push", name} pusharg := []string{"push", name}
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("docker", pusharg...).CombinedOutput() out, err := exec.Command("docker", pusharg...).CombinedOutput()
if err != nil { if err != nil {
return errors.Wrap(err, "Failed pushing image: "+string(out)) 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} buildarg := []string{"save", name, "-o", path}
Debug(":whale: Saving image " + name) Debug(":whale: Saving image " + name)
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("docker", buildarg...).CombinedOutput() out, err := exec.Command("docker", buildarg...).CombinedOutput()
if err != nil { if err != nil {
return errors.Wrap(err, "Failed exporting image: "+string(out)) 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 defer os.RemoveAll(tempexport) // clean up
imageExport := filepath.Join(tempexport, "image.tar") imageExport := filepath.Join(tempexport, "image.tar")
Spinner(22)
defer SpinnerStop()
if err := b.ExportImage(compiler.CompilerBackendOptions{ImageName: name, Destination: imageExport}); err != nil { if err := b.ExportImage(compiler.CompilerBackendOptions{ImageName: name, Destination: imageExport}); err != nil {
return errors.Wrap(err, "failed while extracting rootfs for "+name) return errors.Wrap(err, "failed while extracting rootfs for "+name)
} }
SpinnerStop()
src := imageExport src := imageExport
if src == "" && opts.ImageName != "" { if src == "" && opts.ImageName != "" {

View File

@@ -35,26 +35,20 @@ func NewSimpleImgBackend() compiler.CompilerBackend {
// TODO: Missing still: labels, and build args expansion // TODO: Missing still: labels, and build args expansion
func (*SimpleImg) BuildImage(opts compiler.CompilerBackendOptions) error { func (*SimpleImg) BuildImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName name := opts.ImageName
path := opts.SourcePath
context := opts.Context
if context == "" { buildarg := genBuildCommand(opts)
context = "."
}
dockerfileName := opts.DockerFileName
buildarg := []string{"build", "-f", dockerfileName, "-t", name, context}
Spinner(22)
defer SpinnerStop()
Info(":tea: Building image " + name) 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 { if err != nil {
return errors.Wrap(err, "Failed building image: "+string(out)) return err
} }
Info(":tea: Building image " + name + " done") Info(":tea: Building image " + name + " done")
return nil return nil
} }
@@ -73,11 +67,13 @@ func (*SimpleImg) RemoveImage(opts compiler.CompilerBackendOptions) error {
} }
func (*SimpleImg) DownloadImage(opts compiler.CompilerBackendOptions) error { func (*SimpleImg) DownloadImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName name := opts.ImageName
buildarg := []string{"pull", name} buildarg := []string{"pull", name}
Debug(":tea: Downloading image " + name) Debug(":tea: Downloading image " + name)
Spinner(22)
defer SpinnerStop()
cmd := exec.Command("img", buildarg...) cmd := exec.Command("img", buildarg...)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { if err != nil {
@@ -138,6 +134,10 @@ func (*SimpleImg) ExportImage(opts compiler.CompilerBackendOptions) error {
path := opts.Destination path := opts.Destination
buildarg := []string{"save", "-o", path, name} buildarg := []string{"save", "-o", path, name}
Debug(":tea: Saving image " + name) Debug(":tea: Saving image " + name)
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("img", buildarg...).CombinedOutput() out, err := exec.Command("img", buildarg...).CombinedOutput()
if err != nil { if err != nil {
return errors.Wrap(err, "Failed exporting image: "+string(out)) return errors.Wrap(err, "Failed exporting image: "+string(out))
@@ -158,8 +158,13 @@ func (s *SimpleImg) ExtractRootfs(opts compiler.CompilerBackendOptions, keepPerm
} }
os.RemoveAll(path) os.RemoveAll(path)
buildarg := []string{"unpack", "-o", path, name} buildarg := []string{"unpack", "-o", path, name}
Debug(":tea: Extracting image " + name) Debug(":tea: Extracting image " + name)
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("img", buildarg...).CombinedOutput() out, err := exec.Command("img", buildarg...).CombinedOutput()
if err != nil { if err != nil {
return errors.Wrap(err, "Failed extracting image: "+string(out)) 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 CompressionType CompressionImplementation
Options CompilerOptions Options CompilerOptions
SolverOptions solver.Options SolverOptions solver.Options
BackedArgs []string
} }
func NewLuetCompiler(backend CompilerBackend, db pkg.PackageDatabase, opt *CompilerOptions, solvopts solver.Options) Compiler { 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, SolverOptions: solvopts,
} }
} }
func (cs *LuetCompiler) SetBackendArgs(args []string) {
cs.BackedArgs = args
}
func (cs *LuetCompiler) SetConcurrency(i int) { func (cs *LuetCompiler) SetConcurrency(i int) {
cs.Concurrency = i 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) { func (cs *LuetCompiler) CompileParallel(keepPermissions bool, ps CompilationSpecs) ([]Artifact, []error) {
Spinner(22)
defer SpinnerStop()
all := make(chan CompilationSpec) all := make(chan CompilationSpec)
artifacts := []Artifact{} artifacts := []Artifact{}
mutex := &sync.Mutex{} mutex := &sync.Mutex{}
@@ -383,12 +384,14 @@ func (cs *LuetCompiler) buildPackageImage(image, buildertaggedImage, packageImag
SourcePath: buildDir, SourcePath: buildDir,
DockerFileName: p.GetPackage().GetFingerPrint() + "-builder.dockerfile", DockerFileName: p.GetPackage().GetFingerPrint() + "-builder.dockerfile",
Destination: p.Rel(p.GetPackage().GetFingerPrint() + "-builder.image.tar"), Destination: p.Rel(p.GetPackage().GetFingerPrint() + "-builder.image.tar"),
BackendArgs: cs.BackedArgs,
} }
runnerOpts = CompilerBackendOptions{ runnerOpts = CompilerBackendOptions{
ImageName: packageImage, ImageName: packageImage,
SourcePath: buildDir, SourcePath: buildDir,
DockerFileName: p.GetPackage().GetFingerPrint() + ".dockerfile", DockerFileName: p.GetPackage().GetFingerPrint() + ".dockerfile",
Destination: p.Rel(p.GetPackage().GetFingerPrint() + ".image.tar"), Destination: p.Rel(p.GetPackage().GetFingerPrint() + ".image.tar"),
BackendArgs: cs.BackedArgs,
} }
buildAndPush := func(opts CompilerBackendOptions) error { buildAndPush := func(opts CompilerBackendOptions) error {

View File

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

View File

@@ -71,6 +71,10 @@ func (c *DockerClient) DownloadArtifact(artifact compiler.Artifact) (compiler.Ar
//var u *url.URL = nil //var u *url.URL = nil
var err error var err error
var temp string var temp string
Spinner(22)
defer SpinnerStop()
var resultingArtifact compiler.Artifact var resultingArtifact compiler.Artifact
artifactName := path.Base(artifact.GetPath()) artifactName := path.Base(artifact.GetPath())
cacheFile := filepath.Join(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), artifactName) cacheFile := filepath.Join(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), artifactName)
@@ -102,9 +106,9 @@ func (c *DockerClient) DownloadArtifact(artifact compiler.Artifact) (compiler.Ar
defer os.RemoveAll(temp) defer os.RemoveAll(temp)
for _, uri := range c.RepoData.Urls { 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().GetFingerPrint())
Info("Downloading image", imageName)
// imageName := fmt.Sprintf("%s/%s", uri, artifact.GetCompileSpec().GetPackage().GetPackageImageName()) // imageName := fmt.Sprintf("%s/%s", uri, artifact.GetCompileSpec().GetPackage().GetPackageImageName())
err = downloadAndExtractDockerImage(imageName, temp) err = downloadAndExtractDockerImage(imageName, temp)

View File

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

View File

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

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

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

View File

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

View File

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

View File

@@ -14,13 +14,13 @@
package spinner package spinner
import ( import (
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
"unicode/utf8" "unicode/utf8"
@@ -161,7 +161,7 @@ var colorAttributeMap = map[string]color.Attribute{
"bgHiWhite": color.BgHiWhite, "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 { func validColor(c string) bool {
if validColors[c] { if validColors[c] {
return true return true
@@ -169,8 +169,9 @@ func validColor(c string) bool {
return false return false
} }
// Spinner struct to hold the provided options // Spinner struct to hold the provided options.
type Spinner struct { type Spinner struct {
mu *sync.RWMutex //
Delay time.Duration // Delay is the speed of the indicator Delay time.Duration // Delay is the speed of the indicator
chars []string // chars holds the chosen character set chars []string // chars holds the chosen character set
Prefix string // Prefix is the text preppended to the indicator 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 FinalMSG string // string displayed after Stop() is called
lastOutput string // last character(set) written lastOutput string // last character(set) written
color func(a ...interface{}) string // default color is white color func(a ...interface{}) string // default color is white
lock *sync.RWMutex // Writer io.Writer // to make testing better, exported so users have access. Use `WithWriter` to update after initialization.
Writer io.Writer // to make testing better, exported so users have access
active bool // active holds the state of the spinner active bool // active holds the state of the spinner
stopChan chan struct{} // stopChan is a channel used to stop the indicator stopChan chan struct{} // stopChan is a channel used to stop the indicator
HideCursor bool // hideCursor determines if the cursor is visible 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 { func New(cs []string, d time.Duration, options ...Option) *Spinner {
s := &Spinner{ s := &Spinner{
Delay: d, Delay: d,
chars: cs, chars: cs,
color: color.New(color.FgWhite).SprintFunc(), color: color.New(color.FgWhite).SprintFunc(),
lock: &sync.RWMutex{}, mu: &sync.RWMutex{},
Writer: color.Output, Writer: color.Output,
active: false, active: false,
stopChan: make(chan struct{}, 1), 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 // Option is a function that takes a spinner and applies
// a given configuration // a given configuration.
type Option func(*Spinner) type Option func(*Spinner)
// Options contains fields to configure the spinner // Options contains fields to configure the spinner.
type Options struct { type Options struct {
Color string Color string
Suffix string Suffix string
@@ -215,7 +217,7 @@ type Options struct {
HideCursor bool HideCursor bool
} }
// WithColor adds the given color to the spinner // WithColor adds the given color to the spinner.
func WithColor(color string) Option { func WithColor(color string) Option {
return func(s *Spinner) { return func(s *Spinner) {
s.Color(color) s.Color(color)
@@ -223,7 +225,7 @@ func WithColor(color string) Option {
} }
// WithSuffix adds the given string to the spinner // WithSuffix adds the given string to the spinner
// as the suffix // as the suffix.
func WithSuffix(suffix string) Option { func WithSuffix(suffix string) Option {
return func(s *Spinner) { return func(s *Spinner) {
s.Suffix = suffix s.Suffix = suffix
@@ -231,7 +233,7 @@ func WithSuffix(suffix string) Option {
} }
// WithFinalMSG adds the given string ot the spinner // 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 { func WithFinalMSG(finalMsg string) Option {
return func(s *Spinner) { return func(s *Spinner) {
s.FinalMSG = finalMsg s.FinalMSG = finalMsg
@@ -239,31 +241,42 @@ func WithFinalMSG(finalMsg string) Option {
} }
// WithHiddenCursor hides the cursor // WithHiddenCursor hides the cursor
// if hideCursor = true given // if hideCursor = true given.
func WithHiddenCursor(hideCursor bool) Option { func WithHiddenCursor(hideCursor bool) Option {
return func(s *Spinner) { return func(s *Spinner) {
s.HideCursor = hideCursor 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 { func (s *Spinner) Active() bool {
return s.active return s.active
} }
// Start will start the indicator // Start will start the indicator.
func (s *Spinner) Start() { func (s *Spinner) Start() {
s.lock.Lock() s.mu.Lock()
if s.active { if s.active {
s.lock.Unlock() s.mu.Unlock()
return return
} }
if s.HideCursor && runtime.GOOS != "windows" { if s.HideCursor && runtime.GOOS != "windows" {
// hides the cursor // hides the cursor
fmt.Print("\033[?25l") fmt.Fprint(s.Writer, "\033[?25l")
} }
s.active = true s.active = true
s.lock.Unlock() s.mu.Unlock()
go func() { go func() {
for { for {
@@ -272,8 +285,17 @@ func (s *Spinner) Start() {
case <-s.stopChan: case <-s.stopChan:
return return
default: default:
s.lock.Lock() s.mu.Lock()
if !s.active {
s.mu.Unlock()
return
}
s.erase() s.erase()
if s.PreUpdate != nil {
s.PreUpdate(s)
}
var outColor string var outColor string
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
if s.Writer == os.Stderr { 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) outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
} }
} else { } 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) fmt.Fprint(s.Writer, outColor)
s.lastOutput = outPlain s.lastOutput = outPlain
delay := s.Delay delay := s.Delay
s.lock.Unlock()
if s.PostUpdate != nil {
s.PostUpdate(s)
}
s.mu.Unlock()
time.Sleep(delay) time.Sleep(delay)
} }
} }
@@ -297,40 +323,41 @@ func (s *Spinner) Start() {
}() }()
} }
// Stop stops the indicator // Stop stops the indicator.
func (s *Spinner) Stop() { func (s *Spinner) Stop() {
s.lock.Lock() s.mu.Lock()
defer s.lock.Unlock() defer s.mu.Unlock()
if s.active { if s.active {
s.active = false s.active = false
if s.HideCursor && runtime.GOOS != "windows" { if s.HideCursor && runtime.GOOS != "windows" {
// makes the cursor visible // makes the cursor visible
fmt.Print("\033[?25h") fmt.Fprint(s.Writer, "\033[?25h")
} }
s.erase() s.erase()
if s.FinalMSG != "" { if s.FinalMSG != "" {
fmt.Fprintf(s.Writer, s.FinalMSG) fmt.Fprint(s.Writer, s.FinalMSG)
} }
s.stopChan <- struct{}{} s.stopChan <- struct{}{}
} }
} }
// Restart will stop and start the indicator // Restart will stop and start the indicator.
func (s *Spinner) Restart() { func (s *Spinner) Restart() {
s.Stop() s.Stop()
s.Start() 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() { func (s *Spinner) Reverse() {
s.lock.Lock() s.mu.Lock()
defer s.lock.Unlock() defer s.mu.Unlock()
for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 { 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] 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 { func (s *Spinner) Color(colors ...string) error {
colorAttributes := make([]color.Attribute, len(colors)) colorAttributes := make([]color.Attribute, len(colors))
@@ -342,63 +369,55 @@ func (s *Spinner) Color(colors ...string) error {
colorAttributes[index] = colorAttributeMap[c] colorAttributes[index] = colorAttributeMap[c]
} }
s.lock.Lock() s.mu.Lock()
s.color = color.New(colorAttributes...).SprintFunc() s.color = color.New(colorAttributes...).SprintFunc()
s.lock.Unlock() s.mu.Unlock()
s.Restart()
return nil 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) { func (s *Spinner) UpdateSpeed(d time.Duration) {
s.lock.Lock() s.mu.Lock()
defer s.lock.Unlock() defer s.mu.Unlock()
s.Delay = d 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) { func (s *Spinner) UpdateCharSet(cs []string) {
s.lock.Lock() s.mu.Lock()
defer s.lock.Unlock() defer s.mu.Unlock()
s.chars = cs s.chars = cs
} }
// erase deletes written characters // erase deletes written characters.
//
// Caller must already hold s.lock. // Caller must already hold s.lock.
func (s *Spinner) erase() { func (s *Spinner) erase() {
n := utf8.RuneCountInString(s.lastOutput) n := utf8.RuneCountInString(s.lastOutput)
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
var clearString string clearString := "\r" + strings.Repeat(" ", n) + "\r"
for i := 0; i < n; i++ { fmt.Fprint(s.Writer, clearString)
clearString += " "
}
clearString += "\r"
fmt.Fprintf(s.Writer, clearString)
s.lastOutput = "" s.lastOutput = ""
return return
} }
del, _ := hex.DecodeString("7f") for _, c := range []string{"\b", "\127", "\b", "\033[K"} { // "\033[K" for macOS Terminal
for _, c := range []string{"\b", string(del), "\b", "\033[K"} { // "\033[K" for macOS Terminal fmt.Fprint(s.Writer, strings.Repeat(c, n))
for i := 0; i < n; i++ {
fmt.Fprintf(s.Writer, c)
}
} }
fmt.Fprintf(s.Writer, "\r\033[K") // erases to end of line
s.lastOutput = "" s.lastOutput = ""
} }
// Lock allows for manual control to lock the spinner // Lock allows for manual control to lock the spinner.
func (s *Spinner) Lock() { 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() { func (s *Spinner) Unlock() {
s.lock.Unlock() s.mu.Unlock()
} }
// GenerateNumberSequence will generate a slice of integers at the // 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 { func GenerateNumberSequence(length int) []string {
numSeq := make([]string, length) numSeq := make([]string, length)
for i := 0; i < length; i++ { 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/index
github.com/asdine/storm/internal github.com/asdine/storm/internal
github.com/asdine/storm/q github.com/asdine/storm/q
# github.com/briandowns/spinner v1.7.0 # github.com/briandowns/spinner v1.12.1-0.20201220203425-e201aaea0a31
## explicit ## explicit
github.com/briandowns/spinner github.com/briandowns/spinner
# github.com/cavaliercoder/grab v1.0.1-0.20201108051000-98a5bfe305ec # github.com/cavaliercoder/grab v1.0.1-0.20201108051000-98a5bfe305ec