mirror of https://github.com/docker/cli.git
Add branch/tag name to the app path (URL fragment) for multiple version support
Support force install if app has already been installed Add default args/envs Support VERSION build arg Signed-off-by: Qiang Li <liqiang@gmail.com>
This commit is contained in:
parent
01db39ab1b
commit
a696827ba7
|
@ -2,16 +2,20 @@ package app
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/container"
|
||||
"github.com/docker/cli/cli/command/image"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
@ -58,6 +62,8 @@ func addInstallFlags(flags *pflag.FlagSet, dest string, trust bool) *AppOptions
|
|||
flags.StringVar(&options.destination, "destination", dest, "Set local host path for app")
|
||||
flags.BoolVar(&options.launch, "launch", false, "Start app after installation")
|
||||
flags.BoolVarP(&options.detach, "detach", "d", false, "Do not wait for app to finish")
|
||||
flags.BoolVar(&options.force, "force", false, "Force install even if the app exists")
|
||||
flags.StringVar(&options.name, "name", "", "App name")
|
||||
|
||||
// build/run flags
|
||||
flags.StringVar(&options.imageIDFile, "iidfile", imageIDFile, "Write the image ID to the file")
|
||||
|
@ -109,18 +115,45 @@ func addInstallFlags(flags *pflag.FlagSet, dest string, trust bool) *AppOptions
|
|||
// `docker cp` flags
|
||||
include(flags, eflags, []string{"archive", "follow-link"})
|
||||
|
||||
// install specific build args
|
||||
addBuildArgs(flags)
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func addBuildArgs(flags *pflag.FlagSet) {
|
||||
flag := flags.Lookup("build-arg")
|
||||
if flag != nil {
|
||||
flag.Value.Set("HOSTOS=" + runtime.GOOS)
|
||||
flag.Value.Set("HOSTARCH=" + runtime.GOARCH)
|
||||
func setBuildArgs(options *AppOptions) error {
|
||||
bopts := options.buildOpts
|
||||
if bopts == nil {
|
||||
return errors.New("build options not set")
|
||||
}
|
||||
|
||||
set := func(n, v string) {
|
||||
bopts.SetBuildArg(n + "=" + v)
|
||||
}
|
||||
|
||||
set("DOCKER_APP_BASE", options._appBase)
|
||||
appPath, err := options.appPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
set("DOCKER_APP_PATH", appPath)
|
||||
|
||||
set("HOSTOS", runtime.GOOS)
|
||||
set("HOSTARCH", runtime.GOARCH)
|
||||
|
||||
version := options.buildVersion()
|
||||
if version != "" {
|
||||
set("VERSION", version)
|
||||
}
|
||||
|
||||
// user info
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
set("USERNAME", u.Username)
|
||||
set("USERHOME", u.HomeDir)
|
||||
set("USERID", u.Uid)
|
||||
set("USERGID", u.Gid)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func installApp(ctx context.Context, adapter cliAdapter, flags *pflag.FlagSet, options *AppOptions) error {
|
||||
|
@ -133,7 +166,7 @@ func installApp(ctx context.Context, adapter cliAdapter, flags *pflag.FlagSet, o
|
|||
return err
|
||||
}
|
||||
|
||||
bin, err := runPostInstall(adapter, dir, options)
|
||||
bin, err := runPostInstall(ctx, adapter, dir, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -146,8 +179,25 @@ func installApp(ctx context.Context, adapter cliAdapter, flags *pflag.FlagSet, o
|
|||
return nil
|
||||
}
|
||||
|
||||
func setDefaultEnv() {
|
||||
if os.Getenv("DOCKER_BUILDKIT") == "" {
|
||||
os.Setenv("DOCKER_BUILDKIT", "1")
|
||||
}
|
||||
|
||||
platform := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
if os.Getenv("DOCKER_DEFAULT_PLATFORM") == "" {
|
||||
os.Setenv("DOCKER_DEFAULT_PLATFORM", platform)
|
||||
}
|
||||
}
|
||||
|
||||
// runInstall calls the build, run, and cp commands
|
||||
func runInstall(ctx context.Context, dockerCli cliAdapter, flags *pflag.FlagSet, options *AppOptions) (string, error) {
|
||||
setDefaultEnv()
|
||||
|
||||
if err := setBuildArgs(options); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
iid, err := buildImage(ctx, dockerCli, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -218,22 +268,51 @@ func copyFiles(ctx context.Context, dockerCli cliAdapter, cid string, options *A
|
|||
return filepath.Join(dir, filepath.Base(options.egress)), nil
|
||||
}
|
||||
|
||||
func runPostInstall(dockerCli cliAdapter, dir string, options *AppOptions) (string, error) {
|
||||
const appExistWarn = `WARNING! This will replace the existing app.
|
||||
Are you sure you want to continue?`
|
||||
|
||||
func runPostInstall(ctx context.Context, dockerCli cliAdapter, dir string, options *AppOptions) (string, error) {
|
||||
if !options.isDockerAppBase() {
|
||||
return "", installCustom(dir, options.destination, options)
|
||||
}
|
||||
|
||||
// for the default destination
|
||||
binPath := options.binPath()
|
||||
if err := os.MkdirAll(binPath, 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
appPath, err := options.appPath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if fileExist(appPath) {
|
||||
if !options.force {
|
||||
r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), appExistWarn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !r {
|
||||
return "", errdefs.Cancelled(errors.New("app install has been canceled"))
|
||||
}
|
||||
}
|
||||
if err := removeApp(dockerCli, binPath, appPath, options); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// for the default destination
|
||||
// if there is only one file, create symlink for the file
|
||||
if fp, err := oneChild(dir); err == nil && fp != "" {
|
||||
binPath := options.binPath()
|
||||
appPath, err := options.appPath()
|
||||
if err != nil {
|
||||
appName := options.name
|
||||
if appName == "" {
|
||||
appName = makeAppName(fp)
|
||||
}
|
||||
if err := validateName(appName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
link, err := installOne(dir, fp, binPath, appPath)
|
||||
link, err := installOne(appName, dir, fp, binPath, appPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -243,13 +322,15 @@ func runPostInstall(dockerCli cliAdapter, dir string, options *AppOptions) (stri
|
|||
|
||||
// if there is a run file, create symlink for the run file.
|
||||
if fp, err := locateFile(dir, runnerName); err == nil && fp != "" {
|
||||
binPath := options.binPath()
|
||||
appPath, err := options.appPath()
|
||||
if err != nil {
|
||||
appName := options.name
|
||||
if appName == "" {
|
||||
appName = makeAppName(appPath)
|
||||
}
|
||||
if err := validateName(appName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
link, err := installRunFile(dir, fp, binPath, appPath)
|
||||
link, err := installRunFile(appName, dir, fp, binPath, appPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -258,10 +339,6 @@ func runPostInstall(dockerCli cliAdapter, dir string, options *AppOptions) (stri
|
|||
}
|
||||
|
||||
// custom install
|
||||
appPath, err := options.appPath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := installCustom(dir, appPath, options); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -270,38 +347,39 @@ func runPostInstall(dockerCli cliAdapter, dir string, options *AppOptions) (stri
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// installOne creates a symlink to the only file in appPath
|
||||
func installOne(egress, runPath, binPath, appPath string) (string, error) {
|
||||
appName := filepath.Base(runPath)
|
||||
if err := validateName(appName); err != nil {
|
||||
return "", err
|
||||
// removeApp removes the existing app
|
||||
func removeApp(dockerCli cliAdapter, binPath, appPath string, options *AppOptions) error {
|
||||
envs, _ := options.makeEnvs()
|
||||
runUninstaller(dockerCli, appPath, envs)
|
||||
|
||||
if err := os.RemoveAll(appPath); err != nil {
|
||||
return err
|
||||
}
|
||||
targets, err := findSymlinks(binPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cleanupSymlink(dockerCli, appPath, targets)
|
||||
return nil
|
||||
}
|
||||
|
||||
// installOne creates a symlink to the only file in appPath
|
||||
func installOne(appName, egress, runPath, binPath, appPath string) (string, error) {
|
||||
link := filepath.Join(binPath, appName)
|
||||
target := filepath.Join(appPath, appName)
|
||||
return install(link, target, egress, binPath, appPath)
|
||||
return install(link, target, egress, appPath)
|
||||
}
|
||||
|
||||
// installRunFile creates a symlink to the run file in appPath
|
||||
// use the base name as the app name
|
||||
func installRunFile(egress, runPath, binPath, appPath string) (string, error) {
|
||||
appName := filepath.Base(appPath)
|
||||
if err := validateName(appName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
func installRunFile(appName, egress, runPath, binPath, appPath string) (string, error) {
|
||||
link := filepath.Join(binPath, appName)
|
||||
target := filepath.Join(appPath, filepath.Base(runPath))
|
||||
return install(link, target, egress, binPath, appPath)
|
||||
return install(link, target, egress, appPath)
|
||||
}
|
||||
|
||||
// instal creates a symlink to the target file
|
||||
func install(link, target, egress, binPath, appPath string) (string, error) {
|
||||
if _, err := os.Stat(appPath); err == nil {
|
||||
return "", fmt.Errorf("app package exists: %s! Try again after removing it", appPath)
|
||||
} else if !os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("installation failed: %w", err)
|
||||
}
|
||||
|
||||
if ok, err := isSymlinkToOK(link, target); err != nil {
|
||||
func install(link, target, egress, appPath string) (string, error) {
|
||||
if ok, err := isSymlinkOK(link, target); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
if !ok {
|
||||
|
@ -314,10 +392,6 @@ func install(link, target, egress, binPath, appPath string) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(binPath, 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(appPath), 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -337,18 +411,6 @@ func install(link, target, egress, binPath, appPath string) (string, error) {
|
|||
}
|
||||
|
||||
func installCustom(dir string, appPath string, options *AppOptions) error {
|
||||
exist := func(p string) bool {
|
||||
_, err := os.Stat(p)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return err == nil
|
||||
}
|
||||
|
||||
if exist(appPath) {
|
||||
return fmt.Errorf("destination exists: %s", appPath)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(appPath), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -358,7 +420,7 @@ func installCustom(dir string, appPath string, options *AppOptions) error {
|
|||
|
||||
// optionally run the installer if it exists
|
||||
installer := filepath.Join(appPath, installerName)
|
||||
if !exist(installer) {
|
||||
if !fileExist(installer) {
|
||||
return nil
|
||||
}
|
||||
if err := os.Chmod(installer, 0o755); err != nil {
|
||||
|
@ -367,3 +429,17 @@ func installCustom(dir string, appPath string, options *AppOptions) error {
|
|||
|
||||
return launch(installer, options)
|
||||
}
|
||||
|
||||
func fileExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// makeAppName derives the app name from the base name of the path
|
||||
// after removing the version and extension
|
||||
func makeAppName(path string) string {
|
||||
n := filepath.Base(path)
|
||||
n = strings.SplitN(n, "@", 2)[0]
|
||||
n = strings.SplitN(n, ".", 2)[0]
|
||||
return n
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
|
@ -64,7 +63,11 @@ func runLaunch(dir string, options *AppOptions) error {
|
|||
if fp, err := oneChild(dir); err == nil && fp != "" {
|
||||
return fp, nil
|
||||
}
|
||||
if fp, err := locateFile(dir, runnerName); err == nil && fp != "" {
|
||||
appName := options.name
|
||||
if appName == "" {
|
||||
appName = runnerName
|
||||
}
|
||||
if fp, err := locateFile(dir, appName); err == nil && fp != "" {
|
||||
return fp, nil
|
||||
}
|
||||
return "", errors.New("no app file found")
|
||||
|
@ -80,15 +83,9 @@ func runLaunch(dir string, options *AppOptions) error {
|
|||
|
||||
// launch copies the current environment and set DOCKER_APP_BASE before spawning the app
|
||||
func launch(app string, options *AppOptions) error {
|
||||
envs := make(map[string]string)
|
||||
|
||||
// copy the current environment
|
||||
for _, v := range os.Environ() {
|
||||
kv := strings.SplitN(v, "=", 2)
|
||||
envs[kv[0]] = kv[1]
|
||||
envs, err := options.makeEnvs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
envs["DOCKER_APP_BASE"] = options._appBase
|
||||
|
||||
return spawn(app, options.launchArgs(), envs, options.detach)
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -14,7 +16,7 @@ import (
|
|||
"github.com/docker/cli/cli/command/image"
|
||||
)
|
||||
|
||||
// runnerName is the executable name for starting the app
|
||||
// runnerName is the default executable app name for starting the app
|
||||
const runnerName = "run"
|
||||
|
||||
// installerName is the executable name for custom installation
|
||||
|
@ -23,7 +25,7 @@ const installerName = "install"
|
|||
// uninstallerName is the executable name for custom installation
|
||||
const uninstallerName = "uninstall"
|
||||
|
||||
// namePattern is for validating egress and app name
|
||||
// namePattern is for validating app name
|
||||
const namePattern = "^[a-zA-Z0-9][a-zA-Z0-9_.+-]+$"
|
||||
|
||||
var nameRegexp = regexp.MustCompile(namePattern)
|
||||
|
@ -35,12 +37,43 @@ func validateName(s string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// semverPattern is for splitting semver from a context path/URL
|
||||
const semverPattern = `@v?\d+(\.\d+)?(\.\d+)?$`
|
||||
|
||||
var semverRegexp = regexp.MustCompile(semverPattern)
|
||||
|
||||
func splitSemver(s string) (string, string) {
|
||||
if semverRegexp.MatchString(s) {
|
||||
idx := strings.LastIndex(s, "@")
|
||||
// unlikely otherwise ignore
|
||||
if idx == -1 {
|
||||
return s, ""
|
||||
}
|
||||
v := s[idx+1:]
|
||||
if v[0] == 'v' {
|
||||
v = v[1:]
|
||||
}
|
||||
return s[:idx], v
|
||||
}
|
||||
return s, ""
|
||||
}
|
||||
|
||||
// defaultAppBase is docker app's base location specified by
|
||||
// DOCKER_APP_BASE environment variable defaulted to ~/.docker-app/
|
||||
// DOCKER_APP_BASE environment variable defaulted to ~/.docker/app/
|
||||
func defaultAppBase() string {
|
||||
if base := os.Getenv("DOCKER_APP_BASE"); base != "" {
|
||||
return filepath.Clean(base)
|
||||
}
|
||||
|
||||
// locate .docker/app starting from the current working directory
|
||||
// for supporting apps on a per project basis
|
||||
wd, err := os.Getwd()
|
||||
if err == nil {
|
||||
if dir, err := locateDir(wd, ".docker"); err == nil {
|
||||
return filepath.Join(dir, "app")
|
||||
}
|
||||
}
|
||||
|
||||
// default ~/.docker/app
|
||||
// ignore error and use the current working directory
|
||||
// if home directory is not available
|
||||
|
@ -48,8 +81,108 @@ func defaultAppBase() string {
|
|||
return filepath.Join(home, ".docker", "app")
|
||||
}
|
||||
|
||||
type commonOptions struct {
|
||||
// command line args
|
||||
_args []string
|
||||
|
||||
// docker app base location, fixed once set
|
||||
_appBase string
|
||||
}
|
||||
|
||||
func (o *commonOptions) setArgs(args []string) {
|
||||
o._args = args
|
||||
}
|
||||
|
||||
// buildContext returns the build context for building image
|
||||
func (o *commonOptions) buildContext() string {
|
||||
if len(o._args) == 0 {
|
||||
return "."
|
||||
}
|
||||
c, _ := splitSemver(o._args[0])
|
||||
return c
|
||||
}
|
||||
|
||||
func (o *commonOptions) buildVersion() string {
|
||||
if len(o._args) == 0 {
|
||||
return ""
|
||||
}
|
||||
_, v := splitSemver(o._args[0])
|
||||
return v
|
||||
}
|
||||
|
||||
// appPath returns the app directory under the default app base
|
||||
func (o *commonOptions) appPath() (string, error) {
|
||||
if len(o._args) == 0 {
|
||||
return "", errors.New("missing args")
|
||||
}
|
||||
return o.makeAppPath(o._args[0])
|
||||
}
|
||||
|
||||
// binPath returns the bin directory under the default app base
|
||||
func (o *commonOptions) binPath() string {
|
||||
return filepath.Join(o._appBase, "bin")
|
||||
}
|
||||
|
||||
// pkgPath returns the pkg directory under the default app base
|
||||
func (o *commonOptions) pkgPath() string {
|
||||
return filepath.Join(o._appBase, "pkg")
|
||||
}
|
||||
|
||||
// makeAppPath builds the default app path
|
||||
// in the format: appBase/pkg/scheme/host/path
|
||||
func (o *commonOptions) makeAppPath(s string) (string, error) {
|
||||
u, err := parseURL(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if u.Path == "" {
|
||||
return "", fmt.Errorf("missing path: %v", u)
|
||||
}
|
||||
p := filepath.Join(o._appBase, "pkg", u.Scheme, u.Host, shortenPath(u.Path))
|
||||
if u.Fragment == "" {
|
||||
return p, nil
|
||||
}
|
||||
return fmt.Sprintf("%s#%s", p, u.Fragment), nil
|
||||
}
|
||||
|
||||
func (o *commonOptions) makeEnvs() (map[string]string, error) {
|
||||
envs := make(map[string]string)
|
||||
|
||||
// copy the current environment
|
||||
for _, v := range os.Environ() {
|
||||
kv := strings.SplitN(v, "=", 2)
|
||||
envs[kv[0]] = kv[1]
|
||||
}
|
||||
|
||||
envs["DOCKER_APP_BASE"] = o._appBase
|
||||
appPath, err := o.appPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envs["DOCKER_APP_PATH"] = appPath
|
||||
|
||||
envs["VERSION"] = o.buildVersion()
|
||||
|
||||
envs["HOSTOS"] = runtime.GOOS
|
||||
envs["HOSTARCH"] = runtime.GOARCH
|
||||
|
||||
// user info
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envs["USERNAME"] = u.Username
|
||||
envs["USERHOME"] = u.HomeDir
|
||||
envs["USERID"] = u.Uid
|
||||
envs["USERGID"] = u.Gid
|
||||
|
||||
return envs, nil
|
||||
}
|
||||
|
||||
// AppOptions holds the options for the `app` subcommands
|
||||
type AppOptions struct {
|
||||
commonOptions
|
||||
|
||||
// flags for install
|
||||
|
||||
// path on local host
|
||||
|
@ -64,6 +197,12 @@ type AppOptions struct {
|
|||
// start exported app
|
||||
launch bool
|
||||
|
||||
// overwrite existing app
|
||||
force bool
|
||||
|
||||
// app name
|
||||
name string
|
||||
|
||||
// the following are existing flags
|
||||
|
||||
// build flags
|
||||
|
@ -82,25 +221,6 @@ type AppOptions struct {
|
|||
runOpts *container.RunOptions
|
||||
containerOpts *container.ContainerOptions
|
||||
copyOpts *container.CopyOptions
|
||||
|
||||
// runtime
|
||||
// command line args
|
||||
_args []string
|
||||
|
||||
// docker app base location, fixed once set
|
||||
_appBase string
|
||||
}
|
||||
|
||||
func (o *AppOptions) setArgs(args []string) {
|
||||
o._args = args
|
||||
}
|
||||
|
||||
// buildContext returns the build context for building image
|
||||
func (o *AppOptions) buildContext() string {
|
||||
if len(o._args) == 0 {
|
||||
return "."
|
||||
}
|
||||
return o._args[0]
|
||||
}
|
||||
|
||||
// runArgs returns the command line args for running the container
|
||||
|
@ -128,19 +248,6 @@ func (o *AppOptions) isDockerAppBase() bool {
|
|||
return strings.HasPrefix(s, o._appBase)
|
||||
}
|
||||
|
||||
// binPath returns the bin directory under the default app base
|
||||
func (o *AppOptions) binPath() string {
|
||||
return filepath.Join(o._appBase, "bin")
|
||||
}
|
||||
|
||||
// appPath returns the app directory under the default app base
|
||||
func (o *AppOptions) appPath() (string, error) {
|
||||
if len(o._args) == 0 {
|
||||
return "", errors.New("missing args")
|
||||
}
|
||||
return makeAppPath(o._appBase, o._args[0])
|
||||
}
|
||||
|
||||
// cacheDir returns a temp cache directory under the default app base
|
||||
// appBase is chosen as the parent directory to avoid issues such as:
|
||||
// permission, disk space, renaming across partitions.
|
||||
|
@ -170,7 +277,9 @@ func (o *AppOptions) containerID() (string, error) {
|
|||
|
||||
func newAppOptions() *AppOptions {
|
||||
return &AppOptions{
|
||||
_appBase: defaultAppBase(),
|
||||
commonOptions: commonOptions{
|
||||
_appBase: defaultAppBase(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,53 +291,17 @@ func validateAppOptions(options *AppOptions) error {
|
|||
return errors.New("egress is required")
|
||||
}
|
||||
|
||||
name := filepath.Base(options.egress)
|
||||
if err := validateName(name); err != nil {
|
||||
return fmt.Errorf("invalid egress path: %s %v", options.egress, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type removeOptions struct {
|
||||
_appBase string
|
||||
}
|
||||
|
||||
// makeAppPath returns the app directory under the default app base
|
||||
// appBase/pkg/scheme/host/path
|
||||
func (o *removeOptions) makeAppPath(s string) (string, error) {
|
||||
return makeAppPath(o._appBase, s)
|
||||
}
|
||||
|
||||
// binPath returns the bin directory under the default app base
|
||||
func (o *removeOptions) binPath() string {
|
||||
return filepath.Join(o._appBase, "bin")
|
||||
}
|
||||
|
||||
// pkgPath returns the pkg directory under the default app base
|
||||
func (o *removeOptions) pkgPath() string {
|
||||
return filepath.Join(o._appBase, "pkg")
|
||||
commonOptions
|
||||
}
|
||||
|
||||
func newRemoveOptions() *removeOptions {
|
||||
return &removeOptions{
|
||||
_appBase: defaultAppBase(),
|
||||
commonOptions: commonOptions{
|
||||
_appBase: defaultAppBase(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// makeAppPath builds the default app path
|
||||
// in the format: appBase/pkg/scheme/host/path
|
||||
func makeAppPath(appBase, s string) (string, error) {
|
||||
u, err := parseURL(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if u.Path == "" {
|
||||
return "", fmt.Errorf("missing path: %v", u)
|
||||
}
|
||||
|
||||
name := filepath.Base(u.Path)
|
||||
if err := validateName(name); err != nil {
|
||||
return "", fmt.Errorf("invalid path: %s %v", u.Path, err)
|
||||
}
|
||||
return filepath.Join(appBase, "pkg", u.Scheme, u.Host, u.Path), nil
|
||||
}
|
||||
|
|
|
@ -39,35 +39,24 @@ func NewRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
|||
// remove symlinks of the app under bin path
|
||||
func runRemove(dockerCli command.Cli, apps []string, options *removeOptions) error {
|
||||
binPath := options.binPath()
|
||||
|
||||
// find all symlinks in binPath to remove later
|
||||
// if they point to the app package
|
||||
targets := make(map[string]string)
|
||||
if links, err := findLinks(binPath); err == nil {
|
||||
for _, link := range links {
|
||||
if target, err := os.Readlink(link); err == nil {
|
||||
targets[target] = link
|
||||
}
|
||||
}
|
||||
targets, err := findSymlinks(binPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var failed []string
|
||||
|
||||
for _, app := range apps {
|
||||
appPath, err := options.makeAppPath(app)
|
||||
options.setArgs([]string{app})
|
||||
appPath, err := options.appPath()
|
||||
if err != nil {
|
||||
failed = append(failed, app)
|
||||
continue
|
||||
}
|
||||
|
||||
// optionally run uninstall if provided
|
||||
uninstaller := filepath.Join(appPath, uninstallerName)
|
||||
if _, err := os.Stat(uninstaller); err == nil {
|
||||
err := spawn(uninstaller, nil, nil, false)
|
||||
if err != nil {
|
||||
fmt.Fprintf(dockerCli.Err(), "%s failed to run: %v\n", uninstaller, err)
|
||||
}
|
||||
}
|
||||
envs, _ := options.makeEnvs()
|
||||
runUninstaller(dockerCli, appPath, envs)
|
||||
|
||||
// remove all files under the app path
|
||||
if err := os.RemoveAll(appPath); err != nil {
|
||||
|
@ -77,16 +66,7 @@ func runRemove(dockerCli command.Cli, apps []string, options *removeOptions) err
|
|||
removeEmptyPath(options.pkgPath(), appPath)
|
||||
fmt.Fprintf(dockerCli.Out(), "app package removed %s\n", appPath)
|
||||
|
||||
// remove symlinks of the app if any
|
||||
for target, link := range targets {
|
||||
if strings.Contains(target, appPath) {
|
||||
if err := os.Remove(link); err != nil {
|
||||
fmt.Fprintf(dockerCli.Err(), "failed to remove %s: %v\n", link, err)
|
||||
} else {
|
||||
fmt.Fprintf(dockerCli.Out(), "app symlink removed %s\n", link)
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanupSymlink(dockerCli, appPath, targets)
|
||||
}
|
||||
|
||||
if len(failed) > 0 {
|
||||
|
@ -94,3 +74,59 @@ func runRemove(dockerCli command.Cli, apps []string, options *removeOptions) err
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// find all symlinks in binPath for removal
|
||||
func findSymlinks(binPath string) (map[string]string, error) {
|
||||
targets := make(map[string]string)
|
||||
readlink := func(link string) (string, error) {
|
||||
target, err := os.Readlink(link)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !filepath.IsAbs(target) {
|
||||
target = filepath.Join(filepath.Dir(link), target)
|
||||
}
|
||||
abs, err := filepath.Abs(target)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return abs, nil
|
||||
}
|
||||
if links, err := findLinks(binPath); err == nil {
|
||||
for _, link := range links {
|
||||
if target, err := readlink(link); err == nil {
|
||||
targets[target] = link
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
// runUninstaller optionally runs uninstall if provided
|
||||
func runUninstaller(dockerCli command.Cli, appPath string, envs map[string]string) {
|
||||
uninstaller := filepath.Join(appPath, uninstallerName)
|
||||
if _, err := os.Stat(uninstaller); err == nil {
|
||||
err := spawn(uninstaller, nil, envs, false)
|
||||
if err != nil {
|
||||
fmt.Fprintf(dockerCli.Err(), "%s failed to run: %v\n", uninstaller, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupSymlink removes symlinks of the app if any
|
||||
func cleanupSymlink(dockerCli command.Cli, appPath string, targets map[string]string) {
|
||||
owns := func(app, target string) bool {
|
||||
return strings.Contains(target, app)
|
||||
}
|
||||
for target, link := range targets {
|
||||
if owns(appPath, target) {
|
||||
if err := os.Remove(link); err != nil {
|
||||
fmt.Fprintf(dockerCli.Err(), "failed to remove %s: %v\n", link, err)
|
||||
} else {
|
||||
fmt.Fprintf(dockerCli.Out(), "app symlink removed %s\n", link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,10 @@ func TestRunRemove(t *testing.T) {
|
|||
|
||||
createApp := func(name string, args []string) ([]string, error) {
|
||||
o := &AppOptions{
|
||||
_appBase: appBase,
|
||||
_args: args,
|
||||
commonOptions: commonOptions{
|
||||
_appBase: appBase,
|
||||
_args: args,
|
||||
},
|
||||
}
|
||||
appPath, err := o.appPath()
|
||||
if err != nil {
|
||||
|
@ -77,7 +79,7 @@ func TestRunRemove(t *testing.T) {
|
|||
expectErr: "",
|
||||
},
|
||||
{
|
||||
name: "a few apps", args: []string{"example.com/org/one", "example.com/org/two", "example.com/org/three"},
|
||||
name: "a few apps", args: []string{"example.com/org/one", "example.com/org/two", "example.com/org/three@v1.2.3"},
|
||||
fakeInstall: func(args []string) []string {
|
||||
var files []string
|
||||
for _, a := range args {
|
||||
|
|
|
@ -11,8 +11,7 @@ import (
|
|||
"syscall"
|
||||
)
|
||||
|
||||
// spawn runs the specified command and returns the PID
|
||||
// of the spawned process
|
||||
// spawn runs the specified command
|
||||
func spawn(bin string, args []string, envMap map[string]string, detach bool) error {
|
||||
toEnv := func() []string {
|
||||
var env []string
|
||||
|
@ -138,10 +137,12 @@ func parseURL(s string) (*url.URL, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// isSymlinkToOK checks if it is ok to create a symlink to the target
|
||||
// isSymlinkOK checks if it is ok to create a symlink to the target
|
||||
// it is ok if the path does not exist
|
||||
// or if the path is a symlink that points to this same target
|
||||
func isSymlinkToOK(path, target string) (bool, error) {
|
||||
// or if the path is a symlink that points to the same target.
|
||||
// it is considered the same target if the symlink is identical up to
|
||||
// the first @ or # sign, i.e. they are the same app but different versions.
|
||||
func isSymlinkOK(path, target string) (bool, error) {
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
@ -158,7 +159,14 @@ func isSymlinkToOK(path, target string) (bool, error) {
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return link == target, nil
|
||||
|
||||
pkg := func(s string) string {
|
||||
s = filepath.Dir(s)
|
||||
s = strings.Split(s, "#")[0]
|
||||
return strings.Split(s, "@")[0]
|
||||
}
|
||||
|
||||
return pkg(link) == pkg(target), nil
|
||||
}
|
||||
|
||||
// splitAtDashDash splits a string array into two parts
|
||||
|
@ -219,3 +227,47 @@ func removeEmptyPath(root, dir string) error {
|
|||
|
||||
return rm(filepath.Dir(dir))
|
||||
}
|
||||
|
||||
// shortenPath removes home directory from path to make it shorter
|
||||
func shortenPath(path string) string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
return strings.ReplaceAll(path, home+"/", "_")
|
||||
}
|
||||
|
||||
// locateDir walks back the path and looks for directory with the given name.
|
||||
// If found, it returns the directory; otherwise an empty string.
|
||||
func locateDir(path, name string) (string, error) {
|
||||
check := func(dir string) (bool, string) {
|
||||
if filepath.Base(dir) == name {
|
||||
return true, dir
|
||||
}
|
||||
child := filepath.Join(dir, name)
|
||||
info, err := os.Stat(child)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
return info.IsDir(), child
|
||||
}
|
||||
|
||||
dir, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for {
|
||||
if found, d := check(dir); found {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == "/" || parent == dir {
|
||||
break
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("not found: %s", name)
|
||||
}
|
||||
|
|
|
@ -189,9 +189,6 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *RunOption
|
|||
detachKeys = runOpts.detachKeys
|
||||
}
|
||||
|
||||
// ctx should not be cancellable here, as this would kill the stream to the container
|
||||
// and we want to keep the stream open until the process in the container exits or until
|
||||
// the user forcefully terminates the CLI.
|
||||
closeFn, err := attachContainer(ctx, dockerCli, containerID, &errCh, config, container.AttachOptions{
|
||||
Stream: true,
|
||||
Stdin: config.AttachStdin,
|
||||
|
|
|
@ -90,6 +90,10 @@ func (o *BuildOptions) SetImageIDFile(imageIDFile string) {
|
|||
o.imageIDFile = imageIDFile
|
||||
}
|
||||
|
||||
func (o *BuildOptions) SetBuildArg(value string) {
|
||||
o.buildArgs.Set(value)
|
||||
}
|
||||
|
||||
func newBuildOptions() BuildOptions {
|
||||
ulimits := make(map[string]*container.Ulimit)
|
||||
return BuildOptions{
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
|
@ -58,13 +58,8 @@ func NewPushCommand(dockerCli command.Cli) *cobra.Command {
|
|||
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Push all tags of an image to the repository")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output")
|
||||
command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled())
|
||||
|
||||
// Don't default to DOCKER_DEFAULT_PLATFORM env variable, always default to
|
||||
// pushing the image as-is. This also avoids forcing the platform selection
|
||||
// on older APIs which don't support it.
|
||||
flags.StringVar(&opts.platform, "platform", "",
|
||||
flags.StringVar(&opts.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"),
|
||||
`Push a platform-specific manifest as a single-platform image to the registry.
|
||||
Image index won't be pushed, meaning that other manifests, including attestations won't be preserved.
|
||||
'os[/arch[/variant]]': Explicit platform (eg. linux/amd64)`)
|
||||
flags.SetAnnotation("platform", "version", []string{"1.46"})
|
||||
|
||||
|
@ -84,9 +79,9 @@ func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error
|
|||
}
|
||||
platform = &p
|
||||
|
||||
printNote(dockerCli, `Using --platform pushes only the specified platform manifest of a multi-platform image index.
|
||||
Other components, like attestations, will not be included.
|
||||
To push the complete multi-platform image, remove the --platform flag.
|
||||
printNote(dockerCli, `Selecting a single platform will only push one matching image manifest from a multi-platform image index.
|
||||
This means that any other components attached to the multi-platform image index (like Buildkit attestations) won't be pushed.
|
||||
If you want to only push a single platform image while preserving the attestations, please use 'docker convert\n'
|
||||
`)
|
||||
}
|
||||
|
||||
|
@ -184,22 +179,9 @@ func handleAux(dockerCli command.Cli) func(jm jsonmessage.JSONMessage) {
|
|||
|
||||
func printNote(dockerCli command.Cli, format string, args ...any) {
|
||||
if dockerCli.Err().IsTerminal() {
|
||||
format = strings.ReplaceAll(format, "--platform", aec.Bold.Apply("--platform"))
|
||||
}
|
||||
|
||||
header := " Info -> "
|
||||
padding := len(header)
|
||||
if dockerCli.Err().IsTerminal() {
|
||||
padding = len("i Info > ")
|
||||
header = aec.Bold.Apply(aec.LightCyanB.Apply(aec.BlackF.Apply("i")) + " " + aec.LightCyanF.Apply("Info → "))
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(dockerCli.Err(), header)
|
||||
s := fmt.Sprintf(format, args...)
|
||||
for idx, line := range strings.Split(s, "\n") {
|
||||
if idx > 0 {
|
||||
_, _ = fmt.Fprint(dockerCli.Err(), strings.Repeat(" ", padding))
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCli.Err(), aec.Italic.Apply(line))
|
||||
_, _ = fmt.Fprint(dockerCli.Err(), aec.WhiteF.Apply(aec.CyanB.Apply("[ NOTE ]"))+" ")
|
||||
} else {
|
||||
_, _ = fmt.Fprint(dockerCli.Err(), "[ NOTE ] ")
|
||||
}
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), aec.Bold.Apply(format)+"\n", args...)
|
||||
}
|
||||
|
|
|
@ -9,12 +9,12 @@ Upload an image to a registry
|
|||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [`-a`](#all-tags), [`--all-tags`](#all-tags) | `bool` | | Push all tags of an image to the repository |
|
||||
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
|
||||
| `--platform` | `string` | | Push a platform-specific manifest as a single-platform image to the registry.<br>Image index won't be pushed, meaning that other manifests, including attestations won't be preserved.<br>'os[/arch[/variant]]': Explicit platform (eg. linux/amd64) |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress verbose output |
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------------|:---------|:--------|:--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [`-a`](#all-tags), [`--all-tags`](#all-tags) | `bool` | | Push all tags of an image to the repository |
|
||||
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
|
||||
| `--platform` | `string` | | Push a platform-specific manifest as a single-platform image to the registry.<br>'os[/arch[/variant]]': Explicit platform (eg. linux/amd64) |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress verbose output |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
|
|
@ -9,12 +9,12 @@ Upload an image to a registry
|
|||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `-a`, `--all-tags` | `bool` | | Push all tags of an image to the repository |
|
||||
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
|
||||
| `--platform` | `string` | | Push a platform-specific manifest as a single-platform image to the registry.<br>Image index won't be pushed, meaning that other manifests, including attestations won't be preserved.<br>'os[/arch[/variant]]': Explicit platform (eg. linux/amd64) |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress verbose output |
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------------------|:---------|:--------|:--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `-a`, `--all-tags` | `bool` | | Push all tags of an image to the repository |
|
||||
| `--disable-content-trust` | `bool` | `true` | Skip image signing |
|
||||
| `--platform` | `string` | | Push a platform-specific manifest as a single-platform image to the registry.<br>'os[/arch[/variant]]': Explicit platform (eg. linux/amd64) |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress verbose output |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
|
|
@ -14,6 +14,8 @@ import (
|
|||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
const defaultArgs = "DOCKER_APP_BASE DOCKER_APP_PATH VERSION HOSTARCH HOSTOS USERGID USERHOME USERID USERNAME"
|
||||
|
||||
func TestInstallOne(t *testing.T) {
|
||||
const buildCtx = "one"
|
||||
const coolApp = "cool"
|
||||
|
@ -25,10 +27,10 @@ func TestInstallOne(t *testing.T) {
|
|||
fs.WithDir(buildCtx,
|
||||
fs.WithFile("Dockerfile", fmt.Sprintf(`
|
||||
FROM %s
|
||||
ARG HOSTOS HOSTARCH
|
||||
ARG %s
|
||||
COPY cool /egress/%s
|
||||
CMD ["echo", "'cool' app successfully built!"]
|
||||
`, fixtures.AlpineImage, coolApp)),
|
||||
`, fixtures.AlpineImage, defaultArgs, coolApp)),
|
||||
fs.WithFile("cool", coolScript, fs.WithMode(0o755)),
|
||||
),
|
||||
)
|
||||
|
@ -74,10 +76,10 @@ func TestInstallMulti(t *testing.T) {
|
|||
fs.WithDir(buildCtx,
|
||||
fs.WithFile("Dockerfile", fmt.Sprintf(`
|
||||
FROM %s
|
||||
ARG HOSTOS HOSTARCH
|
||||
ARG %s
|
||||
COPY . /egress
|
||||
CMD ["echo", "'multi' app successfully built!"]
|
||||
`, fixtures.AlpineImage)),
|
||||
`, fixtures.AlpineImage, defaultArgs)),
|
||||
fs.WithFile(".dockerignore", `
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
|
@ -147,10 +149,10 @@ func TestInstallCustom(t *testing.T) {
|
|||
fs.WithDir(buildCtx,
|
||||
fs.WithFile("Dockerfile", fmt.Sprintf(`
|
||||
FROM %s
|
||||
ARG HOSTOS HOSTARCH
|
||||
ARG %s
|
||||
COPY . /egress
|
||||
CMD ["echo", "'custom' app successfully built!"]
|
||||
`, fixtures.AlpineImage)),
|
||||
`, fixtures.AlpineImage, defaultArgs)),
|
||||
fs.WithFile(".dockerignore", `
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
|
@ -208,10 +210,10 @@ func TestInstallCustomDestination(t *testing.T) {
|
|||
fs.WithDir(buildCtx,
|
||||
fs.WithFile("Dockerfile", fmt.Sprintf(`
|
||||
FROM %s
|
||||
ARG HOSTOS HOSTARCH
|
||||
ARG %s
|
||||
COPY . %s
|
||||
CMD ["echo", "'service' successfully built!"]
|
||||
`, fixtures.AlpineImage, egress)),
|
||||
`, fixtures.AlpineImage, defaultArgs, egress)),
|
||||
fs.WithFile("config", "", fs.WithMode(0o644)),
|
||||
fs.WithFile("install", deployScript, fs.WithMode(0o755)),
|
||||
),
|
||||
|
|
|
@ -34,10 +34,10 @@ func TestLaunchOne(t *testing.T) {
|
|||
fs.WithDir(buildCtx,
|
||||
fs.WithFile("Dockerfile", fmt.Sprintf(`
|
||||
FROM %s
|
||||
ARG HOSTOS HOSTARCH
|
||||
ARG %s
|
||||
COPY cool /egress/%s
|
||||
CMD ["echo", "'cool' app successfully built!"]
|
||||
`, fixtures.AlpineImage, coolApp)),
|
||||
`, fixtures.AlpineImage, defaultArgs, coolApp)),
|
||||
fs.WithFile("cool", coolScript, fs.WithMode(0o755)),
|
||||
),
|
||||
)
|
||||
|
@ -87,10 +87,10 @@ func TestLaunchMulti(t *testing.T) {
|
|||
fs.WithDir(buildCtx,
|
||||
fs.WithFile("Dockerfile", fmt.Sprintf(`
|
||||
FROM %s
|
||||
ARG HOSTOS HOSTARCH
|
||||
ARG %s
|
||||
COPY . /egress
|
||||
CMD ["echo", "'multi' app successfully built!"]
|
||||
`, fixtures.AlpineImage)),
|
||||
`, fixtures.AlpineImage, defaultArgs)),
|
||||
fs.WithFile(".dockerignore", `
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -15,7 +13,6 @@ import (
|
|||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/golden"
|
||||
"gotest.tools/v3/icmd"
|
||||
"gotest.tools/v3/poll"
|
||||
"gotest.tools/v3/skip"
|
||||
)
|
||||
|
||||
|
@ -224,26 +221,3 @@ func TestMountSubvolume(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessTermination(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
cmd := icmd.Command("docker", "run", "--rm", "-i", fixtures.AlpineImage,
|
||||
"sh", "-c", "echo 'starting trap'; trap 'echo got signal; exit 0;' TERM; while true; do sleep 10; done")
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
|
||||
result := icmd.StartCmd(cmd).Assert(t, icmd.Success)
|
||||
|
||||
poll.WaitOn(t, func(t poll.LogT) poll.Result {
|
||||
if strings.Contains(result.Stdout(), "starting trap") {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("waiting for process to trap signal")
|
||||
}, poll.WithDelay(1*time.Second), poll.WithTimeout(5*time.Second))
|
||||
|
||||
assert.NilError(t, result.Cmd.Process.Signal(syscall.SIGTERM))
|
||||
|
||||
icmd.WaitOnCmd(time.Second*10, result).Assert(t, icmd.Expected{
|
||||
ExitCode: 0,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue