diff options
Diffstat (limited to 'pkg')
30 files changed, 748 insertions, 265 deletions
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 0f85aa717..f0d07f492 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -72,25 +72,26 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { query := struct { AddHosts string `schema:"extrahosts"` AdditionalCapabilities string `schema:"addcaps"` + AllPlatforms bool `schema:"allplatforms"` Annotations string `schema:"annotations"` AppArmor string `schema:"apparmor"` - AllPlatforms bool `schema:"allplatforms"` BuildArgs string `schema:"buildargs"` CacheFrom string `schema:"cachefrom"` + CgroupParent string `schema:"cgroupparent"` // nolint Compression uint64 `schema:"compression"` ConfigureNetwork string `schema:"networkmode"` - CpuPeriod uint64 `schema:"cpuperiod"` // nolint - CpuQuota int64 `schema:"cpuquota"` // nolint - CpuSetCpus string `schema:"cpusetcpus"` // nolint - CpuSetMems string `schema:"cpusetmems"` // nolint - CpuShares uint64 `schema:"cpushares"` // nolint - CgroupParent string `schema:"cgroupparent"` // nolint + CpuPeriod uint64 `schema:"cpuperiod"` // nolint + CpuQuota int64 `schema:"cpuquota"` // nolint + CpuSetCpus string `schema:"cpusetcpus"` // nolint + CpuSetMems string `schema:"cpusetmems"` // nolint + CpuShares uint64 `schema:"cpushares"` // nolint DNSOptions string `schema:"dnsoptions"` DNSSearch string `schema:"dnssearch"` DNSServers string `schema:"dnsservers"` Devices string `schema:"devices"` Dockerfile string `schema:"dockerfile"` DropCapabilities string `schema:"dropcaps"` + Envs []string `schema:"setenv"` Excludes string `schema:"excludes"` ForceRm bool `schema:"forcerm"` From string `schema:"from"` @@ -108,6 +109,8 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { Memory int64 `schema:"memory"` NamespaceOptions string `schema:"nsoptions"` NoCache bool `schema:"nocache"` + OSFeatures []string `schema:"osfeature"` + OSVersion string `schema:"osversion"` OutputFormat string `schema:"outputformat"` Platform []string `schema:"platform"` Pull bool `schema:"pull"` @@ -117,16 +120,16 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { Rm bool `schema:"rm"` RusageLogFile string `schema:"rusagelogfile"` Seccomp string `schema:"seccomp"` + Secrets string `schema:"secrets"` SecurityOpt string `schema:"securityopt"` ShmSize int `schema:"shmsize"` Squash bool `schema:"squash"` + TLSVerify bool `schema:"tlsVerify"` Tags []string `schema:"t"` Target string `schema:"target"` Timestamp int64 `schema:"timestamp"` - TLSVerify bool `schema:"tlsVerify"` Ulimits string `schema:"ulimits"` UnsetEnvs []string `schema:"unsetenv"` - Secrets string `schema:"secrets"` }{ Dockerfile: "Dockerfile", IdentityLabel: true, @@ -544,6 +547,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { ContextDirectory: contextDirectory, Devices: devices, DropCapabilities: dropCaps, + Envs: query.Envs, Err: auxout, Excludes: excludes, ForceRmIntermediateCtrs: query.ForceRm, @@ -558,6 +562,8 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { MaxPullPushRetries: 3, NamespaceOptions: nsoptions, NoCache: query.NoCache, + OSFeatures: query.OSFeatures, + OSVersion: query.OSVersion, Out: stdout, Output: output, OutputFormat: format, @@ -569,8 +575,8 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { ReportWriter: reporter, RusageLogFile: query.RusageLogFile, Squash: query.Squash, - Target: query.Target, SystemContext: systemContext, + Target: query.Target, UnsetEnvs: query.UnsetEnvs, } diff --git a/pkg/api/handlers/decoder.go b/pkg/api/handlers/decoder.go index 5e8f12960..fbe03d97b 100644 --- a/pkg/api/handlers/decoder.go +++ b/pkg/api/handlers/decoder.go @@ -21,6 +21,7 @@ func NewAPIDecoder() *schema.Decoder { d.RegisterConverter(map[string][]string{}, convertURLValuesString) d.RegisterConverter(time.Time{}, convertTimeString) d.RegisterConverter(define.ContainerStatus(0), convertContainerStatusString) + d.RegisterConverter(map[string]string{}, convertStringMap) var Signal syscall.Signal d.RegisterConverter(Signal, convertSignal) @@ -48,6 +49,15 @@ func convertURLValuesString(query string) reflect.Value { return reflect.ValueOf(f) } +func convertStringMap(query string) reflect.Value { + res := make(map[string]string) + err := json.Unmarshal([]byte(query), &res) + if err != nil { + logrus.Infof("convertStringMap: Failed to Unmarshal %s: %s", query, err.Error()) + } + return reflect.ValueOf(res) +} + func convertContainerStatusString(query string) reflect.Value { result, err := define.StringToContainerStatus(query) if err != nil { diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go index ca9ada761..b71afc28c 100644 --- a/pkg/api/handlers/libpod/play.go +++ b/pkg/api/handlers/libpod/play.go @@ -70,6 +70,16 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { password = authConf.Password } + logDriver := query.LogDriver + if logDriver == "" { + config, err := runtime.GetConfig() + if err != nil { + utils.Error(w, http.StatusInternalServerError, err) + return + } + query.LogDriver = config.Containers.LogDriver + } + containerEngine := abi.ContainerEngine{Libpod: runtime} options := entities.PlayKubeOptions{ Annotations: query.Annotations, diff --git a/pkg/api/handlers/libpod/secrets.go b/pkg/api/handlers/libpod/secrets.go index 8708e630c..3ea2c2ea8 100644 --- a/pkg/api/handlers/libpod/secrets.go +++ b/pkg/api/handlers/libpod/secrets.go @@ -1,9 +1,7 @@ package libpod import ( - "encoding/json" "net/http" - "reflect" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/pkg/api/handlers/utils" @@ -20,12 +18,6 @@ func CreateSecret(w http.ResponseWriter, r *http.Request) { decoder = r.Context().Value(api.DecoderKey).(*schema.Decoder) ) - decoder.RegisterConverter(map[string]string{}, func(str string) reflect.Value { - res := make(map[string]string) - json.Unmarshal([]byte(str), &res) - return reflect.ValueOf(res) - }) - query := struct { Name string `schema:"name"` Driver string `schema:"driver"` diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index a906a01f1..7f5537fb4 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -20,7 +20,6 @@ import ( "github.com/containers/podman/v4/pkg/api/server/idle" "github.com/containers/podman/v4/pkg/api/types" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/coreos/go-systemd/v22/activation" "github.com/coreos/go-systemd/v22/daemon" "github.com/gorilla/mux" "github.com/gorilla/schema" @@ -65,25 +64,7 @@ func NewServerWithSettings(runtime *libpod.Runtime, listener net.Listener, opts } func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.ServiceOptions) (*APIServer, error) { - // If listener not provided try socket activation protocol - if listener == nil { - if _, found := os.LookupEnv("LISTEN_PID"); !found { - return nil, fmt.Errorf("no service listener provided and socket activation protocol is not active") - } - - listeners, err := activation.Listeners() - if err != nil { - return nil, fmt.Errorf("cannot retrieve file descriptors from systemd: %w", err) - } - if len(listeners) != 1 { - return nil, fmt.Errorf("wrong number of file descriptors for socket activation protocol (%d != 1)", len(listeners)) - } - listener = listeners[0] - // note that activation.Listeners() return nil when it cannot listen on the fd (i.e. udp connection) - if listener == nil { - return nil, fmt.Errorf("unexpected fd received from systemd: cannot listen on it") - } - } + logrus.Infof("API service listening on %q. URI: %q", listener.Addr(), runtime.RemoteURI()) if opts.CorsHeaders == "" { logrus.Debug("CORS Headers were not set") } else { diff --git a/pkg/bindings/generator/generator.go b/pkg/bindings/generator/generator.go index e69973be1..06be52451 100644 --- a/pkg/bindings/generator/generator.go +++ b/pkg/bindings/generator/generator.go @@ -171,7 +171,7 @@ func main() { } // go import file - goimport := exec.Command("goimports", "-w", out.Name()) + goimport := exec.Command("../../../test/tools/build/goimports", "-w", out.Name()) goimport.Stderr = os.Stdout if err := goimport.Run(); err != nil { fmt.Println(err) diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index 9e0a0d798..51dcd2aa5 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -216,6 +216,12 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO if t := options.Output; len(t) > 0 { params.Set("output", t) } + if t := options.OSVersion; len(t) > 0 { + params.Set("osversion", t) + } + for _, t := range options.OSFeatures { + params.Set("osfeature", t) + } var platform string if len(options.OS) > 0 { platform = options.OS @@ -303,6 +309,10 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO params.Set("ulimits", string(ulimitsJSON)) } + for _, env := range options.Envs { + params.Add("setenv", env) + } + for _, uenv := range options.UnsetEnvs { params.Add("unsetenv", uenv) } diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 1e25e0872..a19edcbf0 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -122,6 +122,7 @@ type PodCreateOptions struct { CreateCommand []string `json:"create_command,omitempty"` Devices []string `json:"devices,omitempty"` DeviceReadBPs []string `json:"device_read_bps,omitempty"` + ExitPolicy string `json:"exit_policy,omitempty"` Hostname string `json:"hostname,omitempty"` Infra bool `json:"infra,omitempty"` InfraImage string `json:"infra_image,omitempty"` @@ -319,6 +320,7 @@ func ToPodSpecGen(s specgen.PodSpecGenerator, p *PodCreateOptions) (*specgen.Pod } s.Pid = out s.Hostname = p.Hostname + s.ExitPolicy = p.ExitPolicy s.Labels = p.Labels s.Devices = p.Devices s.SecurityOpt = p.SecurityOpt diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index b56c36015..5ca678d6f 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -641,7 +641,11 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st } restoreOptions.CheckpointImageID = img.ID() mountPoint, err := img.Mount(ctx, nil, "") - defer img.Unmount(true) + defer func() { + if err := img.Unmount(true); err != nil { + logrus.Errorf("Failed to unmount image: %v", err) + } + }() if err != nil { return nil, err } @@ -1544,6 +1548,12 @@ func (ic *ContainerEngine) ContainerClone(ctx context.Context, ctrCloneOpts enti return nil, err } + if len(spec.Networks) > 0 && pod.SharesNet() { + logrus.Warning("resetting network config, cannot specify a network other than the pod's when sharing the net namespace") + spec.Networks = nil + spec.NetworkOptions = nil + } + allNamespaces := []struct { isShared bool value *specgen.Namespace diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 0da07bab8..b3ded7db6 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -197,7 +197,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY return nil, errors.Errorf("pod does not have a name") } - podOpt := entities.PodCreateOptions{Infra: true, Net: &entities.NetOptions{NoHosts: options.NoHosts}} + podOpt := entities.PodCreateOptions{ + Infra: true, + Net: &entities.NetOptions{NoHosts: options.NoHosts}, + ExitPolicy: string(config.PodExitPolicyStop), + } podOpt, err = kube.ToPodOpt(ctx, podName, podOpt, podYAML) if err != nil { return nil, err diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 8e96e4154..17df0e3f8 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -6,6 +6,7 @@ import ( "net/url" "os" "os/exec" + "path/filepath" "github.com/containers/common/pkg/cgroups" "github.com/containers/common/pkg/config" @@ -27,27 +28,40 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) { if err != nil { return nil, err } + info.Host.RemoteSocket = &define.RemoteSocket{Path: ic.Libpod.RemoteURI()} - socketPath, err := util.SocketPath() + // `podman system connection add` invokes podman via ssh to fill in connection string. Here + // we are reporting the default systemd activation socket path as we cannot know if a future + // service may be run with another URI. + if ic.Libpod.RemoteURI() == "" { + xdg := "/run" + if path, err := util.GetRuntimeDir(); err != nil { + // Info is as good as we can guess... + return info, err + } else if path != "" { + xdg = path + } + + uri := url.URL{ + Scheme: "unix", + Path: filepath.Join(xdg, "podman", "podman.sock"), + } + ic.Libpod.SetRemoteURI(uri.String()) + info.Host.RemoteSocket.Path = uri.Path + } + + uri, err := url.Parse(ic.Libpod.RemoteURI()) if err != nil { return nil, err } - rs := define.RemoteSocket{ - Path: socketPath, - Exists: false, - } - // Check if the socket exists - if fi, err := os.Stat(socketPath); err == nil { - if fi.Mode()&os.ModeSocket != 0 { - rs.Exists = true - } + if uri.Scheme == "unix" { + _, err := os.Stat(uri.Path) + info.Host.RemoteSocket.Exists = err == nil + } else { + info.Host.RemoteSocket.Exists = true } - // TODO - // it was suggested future versions of this could perform - // a ping on the socket for greater confidence the socket is - // actually active. - info.Host.RemoteSocket = &rs + return info, err } diff --git a/pkg/domain/utils/utils_test.go b/pkg/domain/utils/utils_test.go new file mode 100644 index 000000000..952a4b5be --- /dev/null +++ b/pkg/domain/utils/utils_test.go @@ -0,0 +1,76 @@ +package utils + +import ( + "net/url" + "sort" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestToLibpodFilters(t *testing.T) { + good := url.Values{} + good.Set("apple", "red") + good.Set("banana", "yellow") + good.Set("pear", "") + goodResult := []string{"apple=red", "banana=yellow", "pear="} + sort.Strings(goodResult) + + empty := url.Values{} + type args struct { + f url.Values + } + tests := []struct { + name string + args args + wantFilters []string + }{ + { + name: "GoodURLValue", + args: args{ + f: good, + }, + wantFilters: goodResult, + }, + { + name: "Empty", + args: args{ + f: empty, + }, + wantFilters: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.ElementsMatchf(t, ToLibpodFilters(tt.args.f), tt.wantFilters, "ToLibpodFilters() = %v, want %v", ToLibpodFilters(tt.args.f), tt.wantFilters) + }) + } +} + +func TestToURLValues(t *testing.T) { + good := url.Values{} + good.Set("apple", "red") + good.Set("banana", "yellow") + good.Set("pear", "") + goodResult := []string{"apple=red", "banana=yellow", "pear="} + + type args struct { + f []string + } + tests := []struct { + name string + args args + wantFilters url.Values + }{ + { + name: "Good", + args: args{goodResult}, + wantFilters: good, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.EqualValuesf(t, ToURLValues(tt.args.f), tt.wantFilters, "ToURLValues() = %v, want %v", ToURLValues(tt.args.f), tt.wantFilters) + }) + } +} diff --git a/pkg/env/env_test.go b/pkg/env/env_test.go new file mode 100644 index 000000000..c77061ecf --- /dev/null +++ b/pkg/env/env_test.go @@ -0,0 +1,162 @@ +package env + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSlice(t *testing.T) { + goodMap := make(map[string]string, 0) + goodMap["apple"] = "red" + goodMap["banana"] = "yellow" + goodMap["pear"] = "" + goodResult := []string{"apple=red", "banana=yellow", "pear"} + type args struct { + m map[string]string + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "Good", + args: args{ + m: goodMap, + }, + want: goodResult, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.ElementsMatchf(t, Slice(tt.args.m), tt.want, "Slice() = %v, want %v", Slice(tt.args.m), tt.want) + }) + } +} + +func TestJoin(t *testing.T) { + firstMap := make(map[string]string, 0) + firstMap["apple"] = "red" + secondMap := make(map[string]string, 0) + secondMap["banana"] = "yellow" + goodResult := make(map[string]string, 0) + goodResult["apple"] = "red" + goodResult["banana"] = "yellow" + overrideResult := make(map[string]string, 0) + overrideResult["apple"] = "green" + overrideResult["banana"] = "yellow" + overrideMap := make(map[string]string, 0) + overrideMap["banana"] = "yellow" + overrideMap["apple"] = "green" + type args struct { + base map[string]string + override map[string]string + } + tests := []struct { + name string + args args + want map[string]string + }{ + { + name: "GoodJoin", + args: args{ + base: firstMap, + override: secondMap, + }, + want: goodResult, + }, + { + name: "GoodOverride", + args: args{ + base: firstMap, + override: overrideMap, + }, + want: overrideResult, + }, + { + name: "EmptyOverride", + args: args{ + base: firstMap, + override: nil, + }, + want: firstMap, + }, + { + name: "EmptyBase", + args: args{ + base: nil, + override: firstMap, + }, + want: firstMap, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Join(tt.args.base, tt.args.override) + assert.EqualValuesf(t, got, tt.want, "Join() = %v, want %v", got, tt.want) + }) + } +} + +func Test_parseEnv(t *testing.T) { + good := make(map[string]string) + + type args struct { + env map[string]string + line string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Good", + args: args{ + env: good, + line: "apple=red", + }, + wantErr: false, + }, + { + name: "GoodNoValue", + args: args{ + env: good, + line: "apple=", + }, + wantErr: false, + }, + { + name: "GoodNoKeyNoValue", + args: args{ + env: good, + line: "=", + }, + wantErr: true, + }, + { + name: "BadNoKey", + args: args{ + env: good, + line: "=foobar", + }, + wantErr: true, + }, + { + name: "BadOnlyDelim", + args: args{ + env: good, + line: "=", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := parseEnv(tt.args.env, tt.args.line); (err != nil) != tt.wantErr { + t.Errorf("parseEnv() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/hooks/hooks_test.go b/pkg/hooks/hooks_test.go index fb3c63250..d5d0c2a32 100644 --- a/pkg/hooks/hooks_test.go +++ b/pkg/hooks/hooks_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io/ioutil" - "os" "path/filepath" "runtime" "testing" @@ -20,11 +19,7 @@ var path string func TestGoodNew(t *testing.T) { ctx := context.Background() - dir, err := ioutil.TempDir("", "hooks-test-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() for i, name := range []string{ "01-my-hook.json", @@ -36,7 +31,7 @@ func TestGoodNew(t *testing.T) { if i == 0 { extraStages = ", \"poststart\", \"poststop\"" } - err = ioutil.WriteFile(jsonPath, []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\", \"timeout\": %d}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"%s]}", path, i+1, extraStages)), 0644) + err := ioutil.WriteFile(jsonPath, []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\", \"timeout\": %d}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"%s]}", path, i+1, extraStages)), 0644) if err != nil { t.Fatal(err) } @@ -92,14 +87,10 @@ func TestGoodNew(t *testing.T) { func TestBadNew(t *testing.T) { ctx := context.Background() - dir, err := ioutil.TempDir("", "hooks-test-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() jsonPath := filepath.Join(dir, "a.json") - err = ioutil.WriteFile(jsonPath, []byte("{\"version\": \"-1\"}"), 0644) + err := ioutil.WriteFile(jsonPath, []byte("{\"version\": \"-1\"}"), 0644) if err != nil { t.Fatal(err) } diff --git a/pkg/hooks/monitor_test.go b/pkg/hooks/monitor_test.go index eed02e033..1067d2920 100644 --- a/pkg/hooks/monitor_test.go +++ b/pkg/hooks/monitor_test.go @@ -15,11 +15,7 @@ import ( func TestMonitorOneDirGood(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - dir, err := ioutil.TempDir("", "hooks-test-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() manager, err := New(ctx, []string{dir}, []string{}) if err != nil { @@ -114,17 +110,8 @@ func TestMonitorOneDirGood(t *testing.T) { func TestMonitorTwoDirGood(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - primaryDir, err := ioutil.TempDir("", "hooks-test-primary-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(primaryDir) - - fallbackDir, err := ioutil.TempDir("", "hooks-test-fallback-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(fallbackDir) + primaryDir := t.TempDir() + fallbackDir := t.TempDir() manager, err := New(ctx, []string{fallbackDir, primaryDir}, []string{}) if err != nil { diff --git a/pkg/hooks/read_test.go b/pkg/hooks/read_test.go index 6e6c190bb..381d66bbe 100644 --- a/pkg/hooks/read_test.go +++ b/pkg/hooks/read_test.go @@ -29,14 +29,10 @@ func TestUnknownPath(t *testing.T) { } func TestGoodFile(t *testing.T) { - dir, err := ioutil.TempDir("", "hooks-test-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() jsonPath := filepath.Join(dir, "hook.json") - err = ioutil.WriteFile(jsonPath, []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}", path)), 0644) + err := ioutil.WriteFile(jsonPath, []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}", path)), 0644) if err != nil { t.Fatal(err) } @@ -59,14 +55,10 @@ func TestGoodFile(t *testing.T) { } func TestBadFile(t *testing.T) { - dir, err := ioutil.TempDir("", "hooks-test-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() path := filepath.Join(dir, "hook.json") - err = ioutil.WriteFile(path, []byte("{\"version\": \"1.0.0\", \"hook\": \"not-a-string\"}"), 0644) + err := ioutil.WriteFile(path, []byte("{\"version\": \"1.0.0\", \"hook\": \"not-a-string\"}"), 0644) if err != nil { t.Fatal(err) } @@ -121,13 +113,9 @@ func TestInvalidCurrentJSON(t *testing.T) { } func TestGoodDir(t *testing.T) { - dir, err := ioutil.TempDir("", "hooks-test-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() - err = ioutil.WriteFile(filepath.Join(dir, "README"), []byte("not a hook"), 0644) + err := ioutil.WriteFile(filepath.Join(dir, "README"), []byte("not a hook"), 0644) if err != nil { t.Fatal(err) } @@ -172,14 +160,10 @@ func TestUnknownDir(t *testing.T) { } func TestBadDir(t *testing.T) { - dir, err := ioutil.TempDir("", "hooks-test-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() jsonPath := filepath.Join(dir, "a.json") - err = ioutil.WriteFile(jsonPath, []byte("{\"version\": \"-1\"}"), 0644) + err := ioutil.WriteFile(jsonPath, []byte("{\"version\": \"-1\"}"), 0644) if err != nil { t.Fatal(err) } @@ -193,14 +177,10 @@ func TestBadDir(t *testing.T) { } func TestHookExecutableDoesNotExit(t *testing.T) { - dir, err := ioutil.TempDir("", "hooks-test-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) + dir := t.TempDir() jsonPath := filepath.Join(dir, "hook.json") - err = ioutil.WriteFile(jsonPath, []byte("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"/does/not/exist\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}"), 0644) + err := ioutil.WriteFile(jsonPath, []byte("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"/does/not/exist\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}"), 0644) if err != nil { t.Fatal(err) } diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 9a0ce757a..d34776714 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -52,6 +52,7 @@ type Provider interface { List(opts ListOptions) ([]*ListResponse, error) IsValidVMName(name string) (bool, error) CheckExclusiveActiveVM() (bool, string, error) + RemoveAndCleanMachines() error } type RemoteConnectionType string @@ -170,11 +171,11 @@ func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url // GetDataDir returns the filepath where vm images should // live for podman-machine. func GetDataDir(vmType string) (string, error) { - data, err := homedir.GetDataHome() + dataDirPrefix, err := DataDirPrefix() if err != nil { return "", err } - dataDir := filepath.Join(data, "containers", "podman", "machine", vmType) + dataDir := filepath.Join(dataDirPrefix, vmType) if _, err := os.Stat(dataDir); !os.IsNotExist(err) { return dataDir, nil } @@ -182,14 +183,24 @@ func GetDataDir(vmType string) (string, error) { return dataDir, mkdirErr } +// DataDirPrefix returns the path prefix for all machine data files +func DataDirPrefix() (string, error) { + data, err := homedir.GetDataHome() + if err != nil { + return "", err + } + dataDir := filepath.Join(data, "containers", "podman", "machine") + return dataDir, nil +} + // GetConfigDir returns the filepath to where configuration // files for podman-machine should live func GetConfDir(vmType string) (string, error) { - conf, err := homedir.GetConfigHome() + confDirPrefix, err := ConfDirPrefix() if err != nil { return "", err } - confDir := filepath.Join(conf, "containers", "podman", "machine", vmType) + confDir := filepath.Join(confDirPrefix, vmType) if _, err := os.Stat(confDir); !os.IsNotExist(err) { return confDir, nil } @@ -197,6 +208,16 @@ func GetConfDir(vmType string) (string, error) { return confDir, mkdirErr } +// ConfDirPrefix returns the path prefix for all machine config files +func ConfDirPrefix() (string, error) { + conf, err := homedir.GetConfigHome() + if err != nil { + return "", err + } + confDir := filepath.Join(conf, "containers", "podman", "machine") + return confDir, nil +} + // ResourceConfig describes physical attributes of the machine type ResourceConfig struct { // CPUs to be assigned to the VM diff --git a/pkg/machine/e2e/inspect_test.go b/pkg/machine/e2e/inspect_test.go index e282dd21d..b34285dd8 100644 --- a/pkg/machine/e2e/inspect_test.go +++ b/pkg/machine/e2e/inspect_test.go @@ -43,9 +43,11 @@ var _ = Describe("podman machine stop", func() { Expect(foo2).To(Exit(0)) inspect := new(inspectMachine) + inspect = inspect.withFormat("{{.Name}}") inspectSession, err := mb.setName("foo1").setCmd(inspect).run() Expect(err).To(BeNil()) Expect(inspectSession).To(Exit(0)) + Expect(inspectSession.Bytes()).To(ContainSubstring("foo1")) type fakeInfos struct { Status string @@ -56,13 +58,13 @@ var _ = Describe("podman machine stop", func() { Expect(err).ToNot(HaveOccurred()) Expect(len(infos)).To(Equal(2)) - //rm := new(rmMachine) - //// Must manually clean up due to multiple names - //for _, name := range []string{"foo1", "foo2"} { + // rm := new(rmMachine) + // // Must manually clean up due to multiple names + // for _, name := range []string{"foo1", "foo2"} { // mb.setName(name).setCmd(rm.withForce()).run() // mb.names = []string{} - //} - //mb.names = []string{} + // } + // mb.names = []string{} }) }) diff --git a/pkg/machine/e2e/machine_test.go b/pkg/machine/e2e/machine_test.go index 2b3b60b2b..657014b05 100644 --- a/pkg/machine/e2e/machine_test.go +++ b/pkg/machine/e2e/machine_test.go @@ -23,14 +23,20 @@ func TestMain(m *testing.M) { const ( defaultStream string = "podman-testing" - tmpDir string = "/var/tmp" ) var ( + tmpDir = "/var/tmp" fqImageName string suiteImageName string ) +func init() { + if value, ok := os.LookupEnv("TMPDIR"); ok { + tmpDir = value + } +} + // TestLibpod ginkgo master function func TestMachine(t *testing.T) { RegisterFailHandler(Fail) @@ -70,7 +76,8 @@ var _ = SynchronizedAfterSuite(func() {}, }) func setup() (string, *machineTestBuilder) { - homeDir, err := ioutil.TempDir("/var/tmp", "podman_test") + // Set TMPDIR if this needs a new directory + homeDir, err := ioutil.TempDir("", "podman_test") if err != nil { Fail(fmt.Sprintf("failed to create home directory: %q", err)) } diff --git a/pkg/machine/qemu/config_test.go b/pkg/machine/qemu/config_test.go index 0fbb5b3bf..4d96ec6e7 100644 --- a/pkg/machine/qemu/config_test.go +++ b/pkg/machine/qemu/config_test.go @@ -52,25 +52,23 @@ func TestMachineFile_GetPath(t *testing.T) { func TestNewMachineFile(t *testing.T) { empty := "" - homedir, err := os.MkdirTemp("/tmp", "homedir") - if err != nil { - panic(err) - } - defer os.RemoveAll(homedir) - longTemp, err := os.MkdirTemp("/tmp", "tmpdir") - if err != nil { - panic(err) - } - defer os.RemoveAll(longTemp) + homedir := t.TempDir() + longTemp := t.TempDir() oldhome := os.Getenv("HOME") os.Setenv("HOME", homedir) //nolint: tenv defer os.Setenv("HOME", oldhome) p := "/var/tmp/podman/my.sock" longp := filepath.Join(longTemp, utils.RandomString(100), "my.sock") - os.MkdirAll(filepath.Dir(longp), 0755) - f, _ := os.Create(longp) - f.Close() + err := os.MkdirAll(filepath.Dir(longp), 0755) + if err != nil { + panic(err) + } + f, err := os.Create(longp) + if err != nil { + panic(err) + } + _ = f.Close() sym := "my.sock" longSym := filepath.Join(homedir, ".podman", sym) @@ -120,14 +118,15 @@ func TestNewMachineFile(t *testing.T) { }, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { - got, err := machine.NewMachineFile(tt.args.path, tt.args.symlink) //nolint: scopelint - if (err != nil) != tt.wantErr { //nolint: scopelint - t.Errorf("NewMachineFile() error = %v, wantErr %v", err, tt.wantErr) //nolint: scopelint + got, err := machine.NewMachineFile(tt.args.path, tt.args.symlink) + if (err != nil) != tt.wantErr { + t.Errorf("NewMachineFile() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { //nolint: scopelint - t.Errorf("NewMachineFile() got = %v, want %v", got, tt.want) //nolint: scopelint + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewMachineFile() got = %v, want %v", got, tt.want) } }) } diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 35eea5fb4..6e36b0886 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -484,12 +484,11 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { if err := v.writeConfig(); err != nil { return fmt.Errorf("writing JSON file: %w", err) } - defer func() error { + defer func() { v.Starting = false if err := v.writeConfig(); err != nil { - return fmt.Errorf("writing JSON file: %w", err) + logrus.Errorf("Writing JSON file: %v", err) } - return nil }() if v.isIncompatible() { logrus.Errorf("machine %q is incompatible with this release of podman and needs to be recreated, starting for recovery only", v.Name) @@ -526,10 +525,11 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { time.Sleep(wait) wait++ } - defer qemuSocketConn.Close() if err != nil { return err } + defer qemuSocketConn.Close() + fd, err := qemuSocketConn.(*net.UnixConn).File() if err != nil { return err @@ -1544,3 +1544,79 @@ func (v *MachineVM) editCmdLine(flag string, value string) { v.CmdLine = append(v.CmdLine, []string{flag, value}...) } } + +// RemoveAndCleanMachines removes all machine and cleans up any other files associatied with podman machine +func (p *Provider) RemoveAndCleanMachines() error { + var ( + vm machine.VM + listResponse []*machine.ListResponse + opts machine.ListOptions + destroyOptions machine.RemoveOptions + ) + destroyOptions.Force = true + var prevErr error + + listResponse, err := p.List(opts) + if err != nil { + return err + } + + for _, mach := range listResponse { + vm, err = p.LoadVMByName(mach.Name) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + _, remove, err := vm.Remove(mach.Name, destroyOptions) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + if err := remove(); err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + } + + // Clean leftover files in data dir + dataDir, err := machine.DataDirPrefix() + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + err := os.RemoveAll(dataDir) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + + // Clean leftover files in conf dir + confDir, err := machine.ConfDirPrefix() + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + err := os.RemoveAll(confDir) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + return prevErr +} diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index addcb7c8a..57fb36fc9 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -18,7 +18,6 @@ import ( "strings" "time" - "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/utils" "github.com/containers/storage/pkg/homedir" @@ -153,20 +152,22 @@ const ( type Provider struct{} type MachineVM struct { - // IdentityPath is the fq path to the ssh priv key - IdentityPath string + // ConfigPath is the path to the configuration file + ConfigPath string + // Created contains the original created time instead of querying the file mod time + Created time.Time // ImageStream is the version of fcos being used ImageStream string // ImagePath is the fq path to ImagePath string + // LastUp contains the last recorded uptime + LastUp time.Time // Name of the vm Name string - // SSH port for user networking - Port int - // RemoteUsername of the vm user - RemoteUsername string // Whether this machine should run in a rootful or rootless manner Rootful bool + // SSH identity, username, etc + machine.SSHConfig } type ExitCodeError struct { @@ -181,16 +182,22 @@ func GetWSLProvider() machine.Provider { return wslProvider } -// NewMachine initializes an instance of a virtual machine based on the qemu -// virtualization. +// NewMachine initializes an instance of a wsl machine func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { vm := new(MachineVM) if len(opts.Name) > 0 { vm.Name = opts.Name } + configPath, err := getConfigPath(opts.Name) + if err != nil { + return vm, err + } + vm.ConfigPath = configPath vm.ImagePath = opts.ImagePath vm.RemoteUsername = opts.Username + vm.Created = time.Now() + vm.LastUp = vm.Created // Add a random port for ssh port, err := utils.GetRandomPort() @@ -202,25 +209,69 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { return vm, nil } +func getConfigPath(name string) (string, error) { + vmConfigDir, err := machine.GetConfDir(vmtype) + if err != nil { + return "", err + } + return filepath.Join(vmConfigDir, name+".json"), nil +} + // LoadByName reads a json file that describes a known qemu vm // and returns a vm instance func (p *Provider) LoadVMByName(name string) (machine.VM, error) { - vm := new(MachineVM) - vmConfigDir, err := machine.GetConfDir(vmtype) + configPath, err := getConfigPath(name) if err != nil { return nil, err } - b, err := ioutil.ReadFile(filepath.Join(vmConfigDir, name+".json")) - if os.IsNotExist(err) { - return nil, errors.Wrap(machine.ErrNoSuchVM, name) - } + + vm, err := readAndMigrate(configPath, name) + return vm, err +} + +// readAndMigrate returns the content of the VM's +// configuration file in json +func readAndMigrate(configPath string, name string) (*MachineVM, error) { + vm := new(MachineVM) + b, err := os.ReadFile(configPath) if err != nil { - return nil, err + if errors.Is(err, os.ErrNotExist) { + return nil, errors.Wrap(machine.ErrNoSuchVM, name) + } + return vm, err } err = json.Unmarshal(b, vm) + if err == nil && vm.Created.IsZero() { + err = vm.migrate40(configPath) + } return vm, err } +func (v *MachineVM) migrate40(configPath string) error { + v.ConfigPath = configPath + fi, err := os.Stat(configPath) + if err != nil { + return err + } + v.Created = fi.ModTime() + v.LastUp = getLegacyLastStart(v) + return v.writeConfig() +} + +func getLegacyLastStart(vm *MachineVM) time.Time { + vmDataDir, err := machine.GetDataDir(vmtype) + if err != nil { + return vm.Created + } + distDir := filepath.Join(vmDataDir, "wsldist") + start := filepath.Join(distDir, vm.Name, "laststart") + info, err := os.Stat(start) + if err != nil { + return vm.Created + } + return info.ModTime() +} + // Init writes the json configuration file to the filesystem for // other verbs (start, stop) func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { @@ -289,12 +340,7 @@ func downloadDistro(v *MachineVM, opts machine.InitOptions) error { } func (v *MachineVM) writeConfig() error { - vmConfigDir, err := machine.GetConfDir(vmtype) - if err != nil { - return err - } - - jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json" + jsonFile := v.ConfigPath b, err := json.MarshalIndent(v, "", " ") if err != nil { @@ -810,7 +856,8 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { } } - return markStart(name) + _, _, err = v.updateTimeStamps(true) + return err } func launchWinProxy(v *MachineVM) (bool, string, error) { @@ -1005,6 +1052,8 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { return errors.Errorf("%q is not running", v.Name) } + _, _, _ = v.updateTimeStamps(true) + if err := stopWinProxy(v); err != nil { fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error()) } @@ -1032,10 +1081,12 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { return nil } -// TODO: We need to rename isRunning to State(); I do not have a -// windows system to test this on. func (v *MachineVM) State(bypass bool) (machine.Status, error) { - return "", define.ErrNotImplemented + if v.isRunning() { + return machine.Running, nil + } + + return machine.Stopped, nil } func stopWinProxy(v *MachineVM) error { @@ -1210,14 +1261,9 @@ func GetVMInfos() ([]*machine.ListResponse, error) { var listed []*machine.ListResponse if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error { - vm := new(MachineVM) if strings.HasSuffix(d.Name(), ".json") { - fullPath := filepath.Join(vmConfigDir, d.Name()) - b, err := ioutil.ReadFile(fullPath) - if err != nil { - return err - } - err = json.Unmarshal(b, vm) + path := filepath.Join(vmConfigDir, d.Name()) + vm, err := readAndMigrate(path, strings.TrimSuffix(d.Name(), ".json")) if err != nil { return err } @@ -1229,15 +1275,13 @@ func GetVMInfos() ([]*machine.ListResponse, error) { listEntry.CPUs, _ = getCPUs(vm) listEntry.Memory, _ = getMem(vm) listEntry.DiskSize = getDiskSize(vm) - fi, err := os.Stat(fullPath) - if err != nil { - return err - } - listEntry.CreatedAt = fi.ModTime() - listEntry.LastUp = getLastStart(vm, fi.ModTime()) - if vm.isRunning() { - listEntry.Running = true - } + listEntry.RemoteUsername = vm.RemoteUsername + listEntry.Port = vm.Port + listEntry.IdentityPath = vm.IdentityPath + + running := vm.isRunning() + listEntry.CreatedAt, listEntry.LastUp, _ = vm.updateTimeStamps(running) + listEntry.Running = running listed = append(listed, listEntry) } @@ -1248,6 +1292,16 @@ func GetVMInfos() ([]*machine.ListResponse, error) { return listed, err } +func (vm *MachineVM) updateTimeStamps(updateLast bool) (time.Time, time.Time, error) { + var err error + if updateLast { + vm.LastUp = time.Now() + err = vm.writeConfig() + } + + return vm.Created, vm.LastUp, err +} + func getDiskSize(vm *MachineVM) uint64 { vmDataDir, err := machine.GetDataDir(vmtype) if err != nil { @@ -1262,36 +1316,6 @@ func getDiskSize(vm *MachineVM) uint64 { return uint64(info.Size()) } -func markStart(name string) error { - vmDataDir, err := machine.GetDataDir(vmtype) - if err != nil { - return err - } - distDir := filepath.Join(vmDataDir, "wsldist") - start := filepath.Join(distDir, name, "laststart") - file, err := os.Create(start) - if err != nil { - return err - } - file.Close() - - return nil -} - -func getLastStart(vm *MachineVM, created time.Time) time.Time { - vmDataDir, err := machine.GetDataDir(vmtype) - if err != nil { - return created - } - distDir := filepath.Join(vmDataDir, "wsldist") - start := filepath.Join(distDir, vm.Name, "laststart") - info, err := os.Stat(start) - if err != nil { - return created - } - return info.ModTime() -} - func getCPUs(vm *MachineVM) (uint64, error) { dist := toDist(vm.Name) if run, _ := isWSLRunning(dist); !run { @@ -1388,6 +1412,109 @@ func (v *MachineVM) setRootful(rootful bool) error { return nil } +// Inspect returns verbose detail about the machine func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { - return nil, define.ErrNotImplemented + state, err := v.State(false) + if err != nil { + return nil, err + } + + created, lastUp, _ := v.updateTimeStamps(state == machine.Running) + + return &machine.InspectInfo{ + ConfigPath: machine.VMFile{Path: v.ConfigPath}, + Created: created, + Image: machine.ImageConfig{ + ImagePath: machine.VMFile{Path: v.ImagePath}, + ImageStream: v.ImageStream, + }, + LastUp: lastUp, + Name: v.Name, + Resources: v.getResources(), + SSHConfig: v.SSHConfig, + State: state, + }, nil +} + +func (v *MachineVM) getResources() (resources machine.ResourceConfig) { + resources.CPUs, _ = getCPUs(v) + resources.Memory, _ = getMem(v) + resources.DiskSize = getDiskSize(v) + return +} + +// RemoveAndCleanMachines removes all machine and cleans up any other files associatied with podman machine +func (p *Provider) RemoveAndCleanMachines() error { + var ( + vm machine.VM + listResponse []*machine.ListResponse + opts machine.ListOptions + destroyOptions machine.RemoveOptions + ) + destroyOptions.Force = true + var prevErr error + + listResponse, err := p.List(opts) + if err != nil { + return err + } + + for _, mach := range listResponse { + vm, err = p.LoadVMByName(mach.Name) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + _, remove, err := vm.Remove(mach.Name, destroyOptions) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + if err := remove(); err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + } + + // Clean leftover files in data dir + dataDir, err := machine.DataDirPrefix() + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + err := os.RemoveAll(dataDir) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + + // Clean leftover files in conf dir + confDir, err := machine.ConfDirPrefix() + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } else { + err := os.RemoveAll(confDir) + if err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + prevErr = err + } + } + return prevErr } diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 831c1d7b9..d8008b10b 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -3,6 +3,7 @@ package generate import ( "context" "encoding/json" + "fmt" "os" "strings" "time" @@ -352,7 +353,10 @@ func ConfigToSpec(rt *libpod.Runtime, specg *specgen.SpecGenerator, contaierID s if err != nil { return nil, nil, err } - conf := c.Config() + conf := c.ConfigWithNetworks() + if conf == nil { + return nil, nil, fmt.Errorf("failed to get config for container %s", c.ID()) + } tmpSystemd := conf.Systemd tmpMounts := conf.Mounts @@ -501,6 +505,8 @@ func ConfigToSpec(rt *libpod.Runtime, specg *specgen.SpecGenerator, contaierID s _, mounts := c.SortUserVolumes(c.Spec()) specg.Mounts = mounts specg.HostDeviceList = conf.DeviceHostSrc + specg.Networks = conf.Networks + mapSecurityConfig(conf, specg) if c.IsInfra() { // if we are creating this spec for a pod's infra ctr, map the compatible options diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 88a452cbb..04e24d625 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -175,13 +175,15 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return nil, nil, nil, errors.New("the given container could not be retrieved") } conf := c.Config() - out, err := json.Marshal(conf.Spec.Linux) - if err != nil { - return nil, nil, nil, err - } - err = json.Unmarshal(out, runtimeSpec.Linux) - if err != nil { - return nil, nil, nil, err + if conf != nil && conf.Spec != nil && conf.Spec.Linux != nil { + out, err := json.Marshal(conf.Spec.Linux) + if err != nil { + return nil, nil, nil, err + } + err = json.Unmarshal(out, runtimeSpec.Linux) + if err != nil { + return nil, nil, nil, err + } } if s.ResourceLimits != nil { switch { diff --git a/pkg/specgen/generate/kube/play_test.go b/pkg/specgen/generate/kube/play_test.go index 448522c2a..e01d62b08 100644 --- a/pkg/specgen/generate/kube/play_test.go +++ b/pkg/specgen/generate/kube/play_test.go @@ -3,9 +3,7 @@ package kube import ( "encoding/json" "fmt" - "io/ioutil" "math" - "os" "runtime" "strconv" "testing" @@ -39,9 +37,7 @@ func createSecrets(t *testing.T, d string) *secrets.SecretsManager { } func TestEnvVarsFrom(t *testing.T) { - d, err := ioutil.TempDir("", "secrets") - assert.NoError(t, err) - defer os.RemoveAll(d) + d := t.TempDir() secretsManager := createSecrets(t, d) tests := []struct { @@ -191,9 +187,7 @@ func TestEnvVarsFrom(t *testing.T) { } func TestEnvVarValue(t *testing.T) { - d, err := ioutil.TempDir("", "secrets") - assert.NoError(t, err) - defer os.RemoveAll(d) + d := t.TempDir() secretsManager := createSecrets(t, d) stringNumCPUs := strconv.Itoa(runtime.NumCPU()) diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index a3408b402..fce32d688 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -197,6 +197,8 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er options = append(options, libpod.WithPodHostname(p.Hostname)) } + options = append(options, libpod.WithPodExitPolicy(p.ExitPolicy)) + return options, nil } diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 759caa0c0..1bb64448f 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -19,6 +19,8 @@ type PodBasicConfig struct { // all containers in the pod as long as the UTS namespace is shared. // Optional. Hostname string `json:"hostname,omitempty"` + // ExitPolicy determines the pod's exit and stop behaviour. + ExitPolicy string `json:"exit_policy,omitempty"` // Labels are key-value pairs that are used to add metadata to pods. // Optional. Labels map[string]string `json:"labels,omitempty"` diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go index cd1486a82..4043fbfcb 100644 --- a/pkg/systemd/generate/pods.go +++ b/pkg/systemd/generate/pods.go @@ -256,6 +256,16 @@ func generatePodInfo(pod *libpod.Pod, options entities.GenerateSystemdOptions) ( return &info, nil } +// Unless already specified, the pod's exit policy to "stop". +func setPodExitPolicy(cmd []string) []string { + for _, arg := range cmd { + if strings.HasPrefix(arg, "--exit-policy=") || arg == "--exit-policy" { + return cmd + } + } + return append(cmd, "--exit-policy=stop") +} + // executePodTemplate executes the pod template on the specified podInfo. Note // that the podInfo is also post processed and completed, which allows for an // easier unit testing. @@ -355,6 +365,7 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions) } startCommand = append(startCommand, podCreateArgs...) + startCommand = setPodExitPolicy(startCommand) startCommand = escapeSystemdArguments(startCommand) info.ExecStartPre1 = "/bin/rm -f {{{{.PIDFile}}}} {{{{.PodIDFile}}}}" diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go index dcb18780c..59f217256 100644 --- a/pkg/systemd/generate/pods_test.go +++ b/pkg/systemd/generate/pods_test.go @@ -7,6 +7,28 @@ import ( "github.com/stretchr/testify/assert" ) +func TestSetPodExitPolicy(t *testing.T) { + tests := []struct { + input, expected []string + }{ + { + []string{"podman", "pod", "create"}, + []string{"podman", "pod", "create", "--exit-policy=stop"}, + }, + { + []string{"podman", "pod", "create", "--exit-policy=continue"}, + []string{"podman", "pod", "create", "--exit-policy=continue"}, + }, + { + []string{"podman", "pod", "create", "--exit-policy", "continue"}, + []string{"podman", "pod", "create", "--exit-policy", "continue"}, + }, + } + for _, test := range tests { + assert.Equalf(t, test.expected, setPodExitPolicy(test.input), "%v", test.input) + } +} + func TestValidateRestartPolicyPod(t *testing.T) { type podInfo struct { restart string @@ -252,7 +274,7 @@ Environment=PODMAN_SYSTEMD_UNIT=%n Restart=on-failure TimeoutStopSec=70 ExecStartPre=/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id -ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo "bar=arg with space" --replace +ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo "bar=arg with space" --replace --exit-policy=stop ExecStart=/usr/bin/podman pod start --pod-id-file %t/pod-123abc.pod-id ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/pod-123abc.pod-id -t 10 ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/pod-123abc.pod-id @@ -280,7 +302,7 @@ Environment=PODMAN_SYSTEMD_UNIT=%n Restart=on-failure TimeoutStopSec=70 ExecStartPre=/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id -ExecStartPre=/usr/bin/podman --events-backend none --runroot /root pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo "bar=arg with space" --replace +ExecStartPre=/usr/bin/podman --events-backend none --runroot /root pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo "bar=arg with space" --replace --exit-policy=stop ExecStart=/usr/bin/podman --events-backend none --runroot /root pod start --pod-id-file %t/pod-123abc.pod-id ExecStop=/usr/bin/podman --events-backend none --runroot /root pod stop --ignore --pod-id-file %t/pod-123abc.pod-id -t 10 ExecStopPost=/usr/bin/podman --events-backend none --runroot /root pod rm --ignore -f --pod-id-file %t/pod-123abc.pod-id @@ -308,7 +330,7 @@ Environment=PODMAN_SYSTEMD_UNIT=%n Restart=on-failure TimeoutStopSec=70 ExecStartPre=/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id -ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo --replace +ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo --replace --exit-policy=stop ExecStart=/usr/bin/podman pod start --pod-id-file %t/pod-123abc.pod-id ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/pod-123abc.pod-id -t 10 ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/pod-123abc.pod-id @@ -336,7 +358,7 @@ Environment=PODMAN_SYSTEMD_UNIT=%n Restart=on-failure TimeoutStopSec=70 ExecStartPre=/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id -ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo --label key={{someval}} --replace +ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo --label key={{someval}} --exit-policy=continue --replace ExecStart=/usr/bin/podman pod start --pod-id-file %t/pod-123abc.pod-id ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/pod-123abc.pod-id -t 10 ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/pod-123abc.pod-id @@ -581,7 +603,7 @@ WantedBy=default.target GraphRoot: "/var/lib/containers/storage", RunRoot: "/var/run/containers/storage", RequiredServices: []string{"container-1", "container-2"}, - CreateCommand: []string{"podman", "pod", "create", "--name", "foo", "--label", "key={{someval}}"}, + CreateCommand: []string{"podman", "pod", "create", "--name", "foo", "--label", "key={{someval}}", "--exit-policy=continue"}, }, podNewLabelWithCurlyBraces, true, diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 9842a0f73..a0bf8b50d 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -731,29 +731,6 @@ func IDtoolsToRuntimeSpec(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxI return convertedIDMap } -var socketPath string - -func SetSocketPath(path string) { - socketPath = path -} - -func SocketPath() (string, error) { - if socketPath != "" { - return socketPath, nil - } - xdg, err := GetRuntimeDir() - if err != nil { - return "", err - } - if len(xdg) == 0 { - // If no xdg is returned, assume root socket - xdg = "/run" - } - - // Glue the socket path together - return filepath.Join(xdg, "podman", "podman.sock"), nil -} - func LookupUser(name string) (*user.User, error) { // Assume UID look up first, if it fails lookup by username if u, err := user.LookupId(name); err == nil { |