completed phase 5

This commit is contained in:
christophe-duc 2026-01-07 13:23:48 -04:00
parent b24ed8cc71
commit 38f28b1209
20 changed files with 158 additions and 151 deletions

View file

@ -65,23 +65,23 @@ logs:
since: '60m' # set to '' to show all logs since: '60m' # set to '' to show all logs
tail: '' # set to 200 to show last 200 lines of logs tail: '' # set to 200 to show last 200 lines of logs
commandTemplates: commandTemplates:
dockerCompose: docker compose # Determines the Docker Compose command to run, referred to as .DockerCompose in commandTemplates podmanCompose: podman-compose # Determines the compose command to run, referred to as .PodmanCompose in commandTemplates
restartService: '{{ .DockerCompose }} restart {{ .Service.Name }}' restartService: '{{ .PodmanCompose }} restart {{ .Service.Name }}'
up: '{{ .DockerCompose }} up -d' up: '{{ .PodmanCompose }} up -d'
down: '{{ .DockerCompose }} down' down: '{{ .PodmanCompose }} down'
downWithVolumes: '{{ .DockerCompose }} down --volumes' downWithVolumes: '{{ .PodmanCompose }} down --volumes'
upService: '{{ .DockerCompose }} up -d {{ .Service.Name }}' upService: '{{ .PodmanCompose }} up -d {{ .Service.Name }}'
startService: '{{ .DockerCompose }} start {{ .Service.Name }}' startService: '{{ .PodmanCompose }} start {{ .Service.Name }}'
stopService: '{{ .DockerCompose }} stop {{ .Service.Name }}' stopService: '{{ .PodmanCompose }} stop {{ .Service.Name }}'
serviceLogs: '{{ .DockerCompose }} logs --since=60m --follow {{ .Service.Name }}' serviceLogs: '{{ .PodmanCompose }} logs --since=60m --follow {{ .Service.Name }}'
viewServiceLogs: '{{ .DockerCompose }} logs --follow {{ .Service.Name }}' viewServiceLogs: '{{ .PodmanCompose }} logs --follow {{ .Service.Name }}'
rebuildService: '{{ .DockerCompose }} up -d --build {{ .Service.Name }}' rebuildService: '{{ .PodmanCompose }} up -d --build {{ .Service.Name }}'
recreateService: '{{ .DockerCompose }} up -d --force-recreate {{ .Service.Name }}' recreateService: '{{ .PodmanCompose }} up -d --force-recreate {{ .Service.Name }}'
allLogs: '{{ .DockerCompose }} logs --tail=300 --follow' allLogs: '{{ .PodmanCompose }} logs --tail=300 --follow'
viewAlLogs: '{{ .DockerCompose }} logs' viewAlLogs: '{{ .PodmanCompose }} logs'
dockerComposeConfig: '{{ .DockerCompose }} config' composeConfig: '{{ .PodmanCompose }} config'
checkDockerComposeConfig: '{{ .DockerCompose }} config --quiet' checkComposeConfig: '{{ .PodmanCompose }} config --quiet'
serviceTop: '{{ .DockerCompose }} top {{ .Service.Name }}' serviceTop: '{{ .PodmanCompose }} top {{ .Service.Name }}'
oS: oS:
openCommand: open {{filename}} openCommand: open {{filename}}
openLinkCommand: open {{link}} openLinkCommand: open {{link}}
@ -124,12 +124,12 @@ customCommands:
containers: containers:
- name: bash - name: bash
attach: true attach: true
command: 'docker exec -it {{ .Container.ID }} bash' command: 'podman exec -it {{ .Container.ID }} bash'
serviceNames: [] serviceNames: []
``` ```
You may use the following go templates (such as `{{ .Container.ID }}` above) in your commands: You may use the following go templates (such as `{{ .Container.ID }}` above) in your commands:
- `{{ .DockerCompose }}`: the docker compose command (default: `docker-compose`) - `{{ .PodmanCompose }}`: the compose command (default: `podman-compose`)
- [`{{ .Container }}`](https://pkg.go.dev/github.com/christophe-duc/lazypodman@v0.20.0/pkg/commands#Container) and its fields. For example: `{{ .Container.Container.ImageID }}` - [`{{ .Container }}`](https://pkg.go.dev/github.com/christophe-duc/lazypodman@v0.20.0/pkg/commands#Container) and its fields. For example: `{{ .Container.Container.ImageID }}`
- [`{{ .Service }}`](https://pkg.go.dev/github.com/christophe-duc/lazypodman@v0.20.0/pkg/commands#Service) and its fields. For example: `{{ .Service.Name }}` - [`{{ .Service }}`](https://pkg.go.dev/github.com/christophe-duc/lazypodman@v0.20.0/pkg/commands#Service) and its fields. For example: `{{ .Service.Name }}`

View file

@ -31,7 +31,7 @@ type PodmanCommand struct {
Tr *i18n.TranslationSet Tr *i18n.TranslationSet
Config *config.AppConfig Config *config.AppConfig
Runtime ContainerRuntime Runtime ContainerRuntime
InDockerComposeProject bool InComposeProject bool
ErrorChan chan error ErrorChan chan error
ContainerMutex deadlock.Mutex ContainerMutex deadlock.Mutex
ServiceMutex deadlock.Mutex ServiceMutex deadlock.Mutex
@ -49,7 +49,7 @@ type LimitedPodmanCommand interface {
// CommandObject is what we pass to our template resolvers when we are running a custom command. // CommandObject is what we pass to our template resolvers when we are running a custom command.
// We do not guarantee that all fields will be populated: just the ones that make sense for the current context // We do not guarantee that all fields will be populated: just the ones that make sense for the current context
type CommandObject struct { type CommandObject struct {
DockerCompose string PodmanCompose string
Service *Service Service *Service
Container *Container Container *Container
Image *Image Image *Image
@ -59,7 +59,7 @@ type CommandObject struct {
// NewCommandObject takes a command object and returns a default command object with the passed command object merged in // NewCommandObject takes a command object and returns a default command object with the passed command object merged in
func (c *PodmanCommand) NewCommandObject(obj CommandObject) CommandObject { func (c *PodmanCommand) NewCommandObject(obj CommandObject) CommandObject {
defaultObj := CommandObject{DockerCompose: c.Config.UserConfig.CommandTemplates.DockerCompose} defaultObj := CommandObject{PodmanCompose: c.Config.UserConfig.CommandTemplates.PodmanCompose}
_ = mergo.Merge(&defaultObj, obj) _ = mergo.Merge(&defaultObj, obj)
return defaultObj return defaultObj
} }
@ -112,23 +112,23 @@ func NewPodmanCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
Log: log, Log: log,
OSCommand: osCommand, OSCommand: osCommand,
Tr: tr, Tr: tr,
Config: config, Config: config,
Runtime: runtime, Runtime: runtime,
ErrorChan: errorChan, ErrorChan: errorChan,
InDockerComposeProject: true, InComposeProject: true,
Closers: closers, Closers: closers,
} }
podmanCommand.setComposeCommand(config) podmanCommand.setComposeCommand(config)
err = osCommand.RunCommand( err = osCommand.RunCommand(
utils.ApplyTemplate( utils.ApplyTemplate(
config.UserConfig.CommandTemplates.CheckDockerComposeConfig, config.UserConfig.CommandTemplates.CheckComposeConfig,
podmanCommand.NewCommandObject(CommandObject{}), podmanCommand.NewCommandObject(CommandObject{}),
), ),
) )
if err != nil { if err != nil {
podmanCommand.InDockerComposeProject = false podmanCommand.InComposeProject = false
log.Warn(err.Error()) log.Warn(err.Error())
} }
@ -138,31 +138,31 @@ func NewPodmanCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
// setComposeCommand detects and sets the appropriate compose command // setComposeCommand detects and sets the appropriate compose command
func (c *PodmanCommand) setComposeCommand(config *config.AppConfig) { func (c *PodmanCommand) setComposeCommand(config *config.AppConfig) {
// If user has explicitly set a compose command, respect it // If user has explicitly set a compose command, respect it
if config.UserConfig.CommandTemplates.DockerCompose != "docker compose" && if config.UserConfig.CommandTemplates.PodmanCompose != "podman-compose" &&
config.UserConfig.CommandTemplates.DockerCompose != "" { config.UserConfig.CommandTemplates.PodmanCompose != "" {
return return
} }
// Try podman-compose first // Try podman-compose first
if err := c.OSCommand.RunCommand("podman-compose version"); err == nil { if err := c.OSCommand.RunCommand("podman-compose version"); err == nil {
config.UserConfig.CommandTemplates.DockerCompose = "podman-compose" config.UserConfig.CommandTemplates.PodmanCompose = "podman-compose"
return return
} }
// Try podman compose (built-in, if available) // Try podman compose (built-in, if available)
if err := c.OSCommand.RunCommand("podman compose version"); err == nil { if err := c.OSCommand.RunCommand("podman compose version"); err == nil {
config.UserConfig.CommandTemplates.DockerCompose = "podman compose" config.UserConfig.CommandTemplates.PodmanCompose = "podman compose"
return return
} }
// Fall back to docker-compose for compatibility // Fall back to docker-compose for compatibility
if err := c.OSCommand.RunCommand("docker-compose version"); err == nil { if err := c.OSCommand.RunCommand("docker-compose version"); err == nil {
config.UserConfig.CommandTemplates.DockerCompose = "docker-compose" config.UserConfig.CommandTemplates.PodmanCompose = "docker-compose"
return return
} }
// Default to podman-compose // Default to podman-compose
config.UserConfig.CommandTemplates.DockerCompose = "podman-compose" config.UserConfig.CommandTemplates.PodmanCompose = "podman-compose"
} }
func (c *PodmanCommand) Close() error { func (c *PodmanCommand) Close() error {
@ -448,11 +448,11 @@ func (c *PodmanCommand) GetContainers(existingContainers []*Container) ([]*Conta
// GetServices gets services // GetServices gets services
func (c *PodmanCommand) GetServices() ([]*Service, error) { func (c *PodmanCommand) GetServices() ([]*Service, error) {
if !c.InDockerComposeProject { if !c.InComposeProject {
return nil, nil return nil, nil
} }
composeCommand := c.Config.UserConfig.CommandTemplates.DockerCompose composeCommand := c.Config.UserConfig.CommandTemplates.PodmanCompose
output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s config --services", composeCommand)) output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s config --services", composeCommand))
if err != nil { if err != nil {
return nil, err return nil, err
@ -520,11 +520,11 @@ func (c *PodmanCommand) ViewAllLogs() (*exec.Cmd, error) {
return cmd, nil return cmd, nil
} }
// DockerComposeConfig returns the result of 'compose config' // ComposeConfig returns the result of 'compose config'
func (c *PodmanCommand) DockerComposeConfig() string { func (c *PodmanCommand) ComposeConfig() string {
output, err := c.OSCommand.RunCommandWithOutput( output, err := c.OSCommand.RunCommandWithOutput(
utils.ApplyTemplate( utils.ApplyTemplate(
c.OSCommand.Config.UserConfig.CommandTemplates.DockerComposeConfig, c.OSCommand.Config.UserConfig.CommandTemplates.ComposeConfig,
c.NewCommandObject(CommandObject{}), c.NewCommandObject(CommandObject{}),
), ),
) )

View file

@ -42,26 +42,33 @@ func NewSSHHandler(oSCommand CmdKiller) *SSHHandler {
} }
} }
// HandleSSHDockerHost overrides the DOCKER_HOST environment variable // HandleSSHDockerHost overrides the CONTAINER_HOST (or DOCKER_HOST for compatibility)
// to point towards a local unix socket tunneled over SSH to the specified ssh host. // environment variable to point towards a local unix socket tunneled over SSH.
func (self *SSHHandler) HandleSSHDockerHost() (io.Closer, error) { func (self *SSHHandler) HandleSSHDockerHost() (io.Closer, error) {
const key = "DOCKER_HOST" // Check CONTAINER_HOST first (Podman standard), then DOCKER_HOST for compatibility
key := "CONTAINER_HOST"
hostValue := self.getenv(key)
if hostValue == "" {
key = "DOCKER_HOST"
hostValue = self.getenv(key)
}
ctx := context.Background() ctx := context.Background()
u, err := url.Parse(self.getenv(key)) u, err := url.Parse(hostValue)
if err != nil { if err != nil {
// if no or an invalid docker host is specified, continue nominally // if no or an invalid container host is specified, continue nominally
return noopCloser{}, nil return noopCloser{}, nil
} }
// if the docker host scheme is "ssh", forward the docker socket before creating the client // if the container host scheme is "ssh", forward the socket before creating the client
if u.Scheme == "ssh" { if u.Scheme == "ssh" {
tunnel, err := self.createDockerHostTunnel(ctx, u.Host) tunnel, err := self.createDockerHostTunnel(ctx, u.Host)
if err != nil { if err != nil {
return noopCloser{}, fmt.Errorf("tunnel ssh docker host: %w", err) return noopCloser{}, fmt.Errorf("tunnel ssh container host: %w", err)
} }
err = self.setenv(key, tunnel.socketPath) err = self.setenv(key, tunnel.socketPath)
if err != nil { if err != nil {
return noopCloser{}, fmt.Errorf("override DOCKER_HOST to tunneled socket: %w", err) return noopCloser{}, fmt.Errorf("override %s to tunneled socket: %w", key, err)
} }
return tunnel, nil return tunnel, nil
@ -90,15 +97,15 @@ func (self *SSHHandler) createDockerHostTunnel(ctx context.Context, remoteHost s
if err != nil { if err != nil {
return nil, fmt.Errorf("create ssh tunnel tmp file: %w", err) return nil, fmt.Errorf("create ssh tunnel tmp file: %w", err)
} }
localSocket := path.Join(socketDir, "dockerhost.sock") localSocket := path.Join(socketDir, "podman.sock")
cmd, err := self.tunnelSSH(ctx, remoteHost, localSocket) cmd, err := self.tunnelSSH(ctx, remoteHost, localSocket)
if err != nil { if err != nil {
return nil, fmt.Errorf("tunnel docker host over ssh: %w", err) return nil, fmt.Errorf("tunnel container host over ssh: %w", err)
} }
// set a reasonable timeout, then wait for the socket to dial successfully // set a reasonable timeout, then wait for the socket to dial successfully
// before attempting to create a new docker client // before attempting to create a new container client
const socketTunnelTimeout = 8 * time.Second const socketTunnelTimeout = 8 * time.Second
ctx, cancel := context.WithTimeout(ctx, socketTunnelTimeout) ctx, cancel := context.WithTimeout(ctx, socketTunnelTimeout)
defer cancel() defer cancel()
@ -108,7 +115,7 @@ func (self *SSHHandler) createDockerHostTunnel(ctx context.Context, remoteHost s
return nil, fmt.Errorf("ssh tunneled socket never became available: %w", err) return nil, fmt.Errorf("ssh tunneled socket never became available: %w", err)
} }
// construct the new DOCKER_HOST url with the proper scheme // construct the new CONTAINER_HOST url with the proper scheme
newDockerHostURL := url.URL{Scheme: "unix", Path: localSocket} newDockerHostURL := url.URL{Scheme: "unix", Path: localSocket}
return &tunneledDockerHost{ return &tunneledDockerHost{
socketPath: newDockerHostURL.String(), socketPath: newDockerHostURL.String(),
@ -149,7 +156,7 @@ func (self *SSHHandler) tryDial(ctx context.Context, socketPath string) error {
} }
func (self *SSHHandler) tunnelSSH(ctx context.Context, host, localSocket string) (*exec.Cmd, error) { func (self *SSHHandler) tunnelSSH(ctx context.Context, host, localSocket string) (*exec.Cmd, error) {
cmd := exec.CommandContext(ctx, "ssh", "-L", localSocket+":/var/run/docker.sock", host, "-N") cmd := exec.CommandContext(ctx, "ssh", "-L", localSocket+":/run/podman/podman.sock", host, "-N")
self.oSCommand.PrepareForChildren(cmd) self.oSCommand.PrepareForChildren(cmd)
err := self.startCmd(cmd) err := self.startCmd(cmd)
if err != nil { if err != nil {

View file

@ -41,7 +41,7 @@ type UserConfig struct {
// CustomCommands determines what shows up in your custom commands menu when // CustomCommands determines what shows up in your custom commands menu when
// you press 'c'. You can use go templates to access three items on the // you press 'c'. You can use go templates to access three items on the
// struct: the DockerCompose command (defaulted to 'docker-compose'), the // struct: the DockerCompose command (defaulted to 'podman-compose'), the
// Service if present, and the Container if present. The struct types for // Service if present, and the Container if present. The struct types for
// those are found in the commands package // those are found in the commands package
CustomCommands CustomCommands `yaml:"customCommands,omitempty"` CustomCommands CustomCommands `yaml:"customCommands,omitempty"`
@ -166,14 +166,15 @@ type CommandTemplatesConfig struct {
// downs and removes volumes // downs and removes volumes
DownWithVolumes string `yaml:"downWithVolumes,omitempty"` DownWithVolumes string `yaml:"downWithVolumes,omitempty"`
// DockerCompose is for your docker-compose command. You may want to combine a // PodmanCompose is for your compose command. You may want to combine a
// few different docker-compose.yml files together, in which case you can set // few different compose.yml files together, in which case you can set
// this to "docker compose -f foo/docker-compose.yml -f // this to "podman-compose -f foo/docker-compose.yml -f
// bah/docker-compose.yml". The reason that the other docker-compose command // bah/docker-compose.yml". The reason that the other compose command
// templates all start with {{ .DockerCompose }} is so that they can make use // templates all start with {{ .PodmanCompose }} is so that they can make use
// of whatever you've set in this value rather than you having to copy and // of whatever you've set in this value rather than you having to copy and
// paste it to all the other commands // paste it to all the other commands. Auto-detected in order: podman-compose,
DockerCompose string `yaml:"dockerCompose,omitempty"` // podman compose, docker-compose.
PodmanCompose string `yaml:"podmanCompose,omitempty"`
// StopService is the command for stopping a service // StopService is the command for stopping a service
StopService string `yaml:"stopService,omitempty"` StopService string `yaml:"stopService,omitempty"`
@ -191,7 +192,7 @@ type CommandTemplatesConfig struct {
ViewServiceLogs string `yaml:"viewServiceLogs,omitempty"` ViewServiceLogs string `yaml:"viewServiceLogs,omitempty"`
// RebuildService is the command for rebuilding a service. Defaults to // RebuildService is the command for rebuilding a service. Defaults to
// something along the lines of `{{ .DockerCompose }} up --build {{ // something along the lines of `{{ .PodmanCompose }} up --build {{
// .Service.Name }}` // .Service.Name }}`
RebuildService string `yaml:"rebuildService,omitempty"` RebuildService string `yaml:"rebuildService,omitempty"`
@ -207,16 +208,16 @@ type CommandTemplatesConfig struct {
// ViewAllLogs is the command we use when you want to see all logs in a subprocess with no filtering // ViewAllLogs is the command we use when you want to see all logs in a subprocess with no filtering
ViewAllLogs string `yaml:"viewAlLogs,omitempty"` ViewAllLogs string `yaml:"viewAlLogs,omitempty"`
// DockerComposeConfig is the command for viewing the config of your docker // ComposeConfig is the command for viewing the config of your compose
// compose. It basically prints out the yaml from your docker-compose.yml // project. It basically prints out the yaml from your docker-compose.yml
// file(s) // file(s)
DockerComposeConfig string `yaml:"dockerComposeConfig,omitempty"` ComposeConfig string `yaml:"composeConfig,omitempty"`
// CheckDockerComposeConfig is what we use to check whether we are in a // CheckComposeConfig is what we use to check whether we are in a
// docker-compose context. If the command returns an error then we clearly // compose context. If the command returns an error then we clearly
// aren't in a docker-compose config and we then just hide the services panel // aren't in a compose config and we then just hide the services panel
// and only show containers // and only show containers
CheckDockerComposeConfig string `yaml:"checkDockerComposeConfig,omitempty"` CheckComposeConfig string `yaml:"checkComposeConfig,omitempty"`
// ServiceTop is the command for viewing the processes under a given service // ServiceTop is the command for viewing the processes under a given service
ServiceTop string `yaml:"serviceTop,omitempty"` ServiceTop string `yaml:"serviceTop,omitempty"`
@ -326,7 +327,7 @@ type CustomCommand struct {
Shell bool `yaml:"shell"` Shell bool `yaml:"shell"`
// Command is the command we want to run. We can use the go templates here as // Command is the command we want to run. We can use the go templates here as
// well. One example might be `{{ .DockerCompose }} exec {{ .Service.Name }} // well. One example might be `{{ .PodmanCompose }} exec {{ .Service.Name }}
// /bin/sh` // /bin/sh`
Command string `yaml:"command"` Command string `yaml:"command"`
@ -385,23 +386,23 @@ func GetDefaultConfig() UserConfig {
Tail: "", Tail: "",
}, },
CommandTemplates: CommandTemplatesConfig{ CommandTemplates: CommandTemplatesConfig{
DockerCompose: "docker compose", PodmanCompose: "podman-compose",
RestartService: "{{ .DockerCompose }} restart {{ .Service.Name }}", RestartService: "{{ .PodmanCompose }} restart {{ .Service.Name }}",
StartService: "{{ .DockerCompose }} start {{ .Service.Name }}", StartService: "{{ .PodmanCompose }} start {{ .Service.Name }}",
Up: "{{ .DockerCompose }} up -d", Up: "{{ .PodmanCompose }} up -d",
Down: "{{ .DockerCompose }} down", Down: "{{ .PodmanCompose }} down",
DownWithVolumes: "{{ .DockerCompose }} down --volumes", DownWithVolumes: "{{ .PodmanCompose }} down --volumes",
UpService: "{{ .DockerCompose }} up -d {{ .Service.Name }}", UpService: "{{ .PodmanCompose }} up -d {{ .Service.Name }}",
RebuildService: "{{ .DockerCompose }} up -d --build {{ .Service.Name }}", RebuildService: "{{ .PodmanCompose }} up -d --build {{ .Service.Name }}",
RecreateService: "{{ .DockerCompose }} up -d --force-recreate {{ .Service.Name }}", RecreateService: "{{ .PodmanCompose }} up -d --force-recreate {{ .Service.Name }}",
StopService: "{{ .DockerCompose }} stop {{ .Service.Name }}", StopService: "{{ .PodmanCompose }} stop {{ .Service.Name }}",
ServiceLogs: "{{ .DockerCompose }} logs --since=60m --follow {{ .Service.Name }}", ServiceLogs: "{{ .PodmanCompose }} logs --since=60m --follow {{ .Service.Name }}",
ViewServiceLogs: "{{ .DockerCompose }} logs --follow {{ .Service.Name }}", ViewServiceLogs: "{{ .PodmanCompose }} logs --follow {{ .Service.Name }}",
AllLogs: "{{ .DockerCompose }} logs --tail=300 --follow", AllLogs: "{{ .PodmanCompose }} logs --tail=300 --follow",
ViewAllLogs: "{{ .DockerCompose }} logs", ViewAllLogs: "{{ .PodmanCompose }} logs",
DockerComposeConfig: "{{ .DockerCompose }} config", ComposeConfig: "{{ .PodmanCompose }} config",
CheckDockerComposeConfig: "{{ .DockerCompose }} config --quiet", CheckComposeConfig: "{{ .PodmanCompose }} config --quiet",
ServiceTop: "{{ .DockerCompose }} top {{ .Service.Name }}", ServiceTop: "{{ .PodmanCompose }} top {{ .Service.Name }}",
}, },
CustomCommands: CustomCommands{ CustomCommands: CustomCommands{
Containers: []CustomCommand{}, Containers: []CustomCommand{},
@ -413,42 +414,42 @@ func GetDefaultConfig() UserConfig {
Services: []CustomCommand{ Services: []CustomCommand{
{ {
Name: "up", Name: "up",
Command: "{{ .DockerCompose }} up -d", Command: "{{ .PodmanCompose }} up -d",
}, },
{ {
Name: "up (attached)", Name: "up (attached)",
Command: "{{ .DockerCompose }} up", Command: "{{ .PodmanCompose }} up",
Attach: true, Attach: true,
}, },
{ {
Name: "stop", Name: "stop",
Command: "{{ .DockerCompose }} stop", Command: "{{ .PodmanCompose }} stop",
}, },
{ {
Name: "pull", Name: "pull",
Command: "{{ .DockerCompose }} pull", Command: "{{ .PodmanCompose }} pull",
Attach: true, Attach: true,
}, },
{ {
Name: "build", Name: "build",
Command: "{{ .DockerCompose }} build --parallel --force-rm", Command: "{{ .PodmanCompose }} build --parallel --force-rm",
Attach: true, Attach: true,
}, },
{ {
Name: "down", Name: "down",
Command: "{{ .DockerCompose }} down", Command: "{{ .PodmanCompose }} down",
}, },
{ {
Name: "down with volumes", Name: "down with volumes",
Command: "{{ .DockerCompose }} down --volumes", Command: "{{ .PodmanCompose }} down --volumes",
}, },
{ {
Name: "down with images", Name: "down with images",
Command: "{{ .DockerCompose }} down --rmi all", Command: "{{ .PodmanCompose }} down --rmi all",
}, },
{ {
Name: "down with volumes and images", Name: "down with volumes and images",
Command: "{{ .DockerCompose }} down --volumes --rmi all", Command: "{{ .PodmanCompose }} down --volumes --rmi all",
}, },
}, },
Containers: []CustomCommand{}, Containers: []CustomCommand{},
@ -502,9 +503,9 @@ func NewAppConfig(name, version, commit, date string, buildSource string, debugg
return nil, err return nil, err
} }
// Pass compose files as individual -f flags to docker compose // Pass compose files as individual -f flags to compose command
if len(composeFiles) > 0 { if len(composeFiles) > 0 {
userConfig.CommandTemplates.DockerCompose += " -f " + strings.Join(composeFiles, " -f ") userConfig.CommandTemplates.PodmanCompose += " -f " + strings.Join(composeFiles, " -f ")
} }
appConfig := &AppConfig{ appConfig := &AppConfig{

View file

@ -7,43 +7,43 @@ import (
"github.com/jesseduffield/yaml" "github.com/jesseduffield/yaml"
) )
func TestDockerComposeCommandNoFiles(t *testing.T) { func TestPodmanComposeCommandNoFiles(t *testing.T) {
composeFiles := []string{} composeFiles := []string{}
conf, err := NewAppConfig("name", "version", "commit", "date", "buildSource", false, composeFiles, "projectDir") conf, err := NewAppConfig("name", "version", "commit", "date", "buildSource", false, composeFiles, "projectDir")
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %s", err) t.Fatalf("Unexpected error: %s", err)
} }
actual := conf.UserConfig.CommandTemplates.DockerCompose actual := conf.UserConfig.CommandTemplates.PodmanCompose
expected := "docker compose" expected := "podman-compose"
if actual != expected { if actual != expected {
t.Fatalf("Expected %s but got %s", expected, actual) t.Fatalf("Expected %s but got %s", expected, actual)
} }
} }
func TestDockerComposeCommandSingleFile(t *testing.T) { func TestPodmanComposeCommandSingleFile(t *testing.T) {
composeFiles := []string{"one.yml"} composeFiles := []string{"one.yml"}
conf, err := NewAppConfig("name", "version", "commit", "date", "buildSource", false, composeFiles, "projectDir") conf, err := NewAppConfig("name", "version", "commit", "date", "buildSource", false, composeFiles, "projectDir")
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %s", err) t.Fatalf("Unexpected error: %s", err)
} }
actual := conf.UserConfig.CommandTemplates.DockerCompose actual := conf.UserConfig.CommandTemplates.PodmanCompose
expected := "docker compose -f one.yml" expected := "podman-compose -f one.yml"
if actual != expected { if actual != expected {
t.Fatalf("Expected %s but got %s", expected, actual) t.Fatalf("Expected %s but got %s", expected, actual)
} }
} }
func TestDockerComposeCommandMultipleFiles(t *testing.T) { func TestPodmanComposeCommandMultipleFiles(t *testing.T) {
composeFiles := []string{"one.yml", "two.yml", "three.yml"} composeFiles := []string{"one.yml", "two.yml", "three.yml"}
conf, err := NewAppConfig("name", "version", "commit", "date", "buildSource", false, composeFiles, "projectDir") conf, err := NewAppConfig("name", "version", "commit", "date", "buildSource", false, composeFiles, "projectDir")
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %s", err) t.Fatalf("Unexpected error: %s", err)
} }
actual := conf.UserConfig.CommandTemplates.DockerCompose actual := conf.UserConfig.CommandTemplates.PodmanCompose
expected := "docker compose -f one.yml -f two.yml -f three.yml" expected := "podman-compose -f one.yml -f two.yml -f three.yml"
if actual != expected { if actual != expected {
t.Fatalf("Expected %s but got %s", expected, actual) t.Fatalf("Expected %s but got %s", expected, actual)
} }

View file

@ -282,7 +282,7 @@ func (gui *Gui) refreshContainersAndServices() error {
} }
func (gui *Gui) renderContainersAndServices() error { func (gui *Gui) renderContainersAndServices() error {
if gui.PodmanCommand.InDockerComposeProject { if gui.PodmanCommand.InComposeProject {
if err := gui.Panels.Services.RerenderList(); err != nil { if err := gui.Panels.Services.RerenderList(); err != nil {
return err return err
} }
@ -449,7 +449,7 @@ func (gui *Gui) containerExecShell(container *commands.Container) error {
}) })
// TODO: use SDK // TODO: use SDK
resolvedCommand := utils.ApplyTemplate("docker exec -it {{ .Container.ID }} /bin/sh -c 'eval $(grep ^$(id -un): /etc/passwd | cut -d : -f 7-)'", commandObject) resolvedCommand := utils.ApplyTemplate("podman exec -it {{ .Container.ID }} /bin/sh -c 'eval $(grep ^$(id -un): /etc/passwd | cut -d : -f 7-)'", commandObject)
// attach and return the subprocess error // attach and return the subprocess error
cmd := gui.OSCommand.ExecutableFromString(resolvedCommand) cmd := gui.OSCommand.ExecutableFromString(resolvedCommand)
return gui.runSubprocess(cmd) return gui.runSubprocess(cmd)

View file

@ -445,7 +445,7 @@ func (gui *Gui) ShouldRefresh(key string) bool {
} }
func (gui *Gui) initiallyFocusedViewName() string { func (gui *Gui) initiallyFocusedViewName() string {
if gui.PodmanCommand.InDockerComposeProject { if gui.PodmanCommand.InComposeProject {
return "services" return "services"
} }
return "containers" return "containers"

View file

@ -23,7 +23,7 @@ func (gui *Gui) getProjectPanel() *panels.SideListPanel[*commands.Project] {
return &panels.SideListPanel[*commands.Project]{ return &panels.SideListPanel[*commands.Project]{
ContextState: &panels.ContextState[*commands.Project]{ ContextState: &panels.ContextState[*commands.Project]{
GetMainTabs: func() []panels.MainTab[*commands.Project] { GetMainTabs: func() []panels.MainTab[*commands.Project] {
if gui.PodmanCommand.InDockerComposeProject { if gui.PodmanCommand.InComposeProject {
return []panels.MainTab[*commands.Project]{ return []panels.MainTab[*commands.Project]{
{ {
Key: "logs", Key: "logs",
@ -32,8 +32,8 @@ func (gui *Gui) getProjectPanel() *panels.SideListPanel[*commands.Project] {
}, },
{ {
Key: "config", Key: "config",
Title: gui.Tr.DockerComposeConfigTitle, Title: gui.Tr.ComposeConfigTitle,
Render: gui.renderDockerComposeConfig, Render: gui.renderComposeConfig,
}, },
{ {
Key: "credits", Key: "credits",
@ -79,7 +79,7 @@ func (gui *Gui) refreshProject() error {
func (gui *Gui) getProjectName() string { func (gui *Gui) getProjectName() string {
projectName := path.Base(gui.Config.ProjectDir) projectName := path.Base(gui.Config.ProjectDir)
if gui.PodmanCommand.InDockerComposeProject { if gui.PodmanCommand.InComposeProject {
for _, service := range gui.Panels.Services.List.GetAllItems() { for _, service := range gui.Panels.Services.List.GetAllItems() {
container := service.Container container := service.Container
if container != nil && container.DetailsLoaded() { if container != nil && container.DetailsLoaded() {
@ -144,9 +144,9 @@ func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc {
}) })
} }
func (gui *Gui) renderDockerComposeConfig(_project *commands.Project) tasks.TaskFunc { func (gui *Gui) renderComposeConfig(_project *commands.Project) tasks.TaskFunc {
return gui.NewSimpleRenderStringTask(func() string { return gui.NewSimpleRenderStringTask(func() string {
return utils.ColoredYamlString(gui.PodmanCommand.DockerComposeConfig()) return utils.ColoredYamlString(gui.PodmanCommand.ComposeConfig())
}) })
} }

View file

@ -78,7 +78,7 @@ func (gui *Gui) getServicesPanel() *panels.SideListPanel[*commands.Service] {
return presentation.GetServiceDisplayStrings(&gui.Config.UserConfig.Gui, service) return presentation.GetServiceDisplayStrings(&gui.Config.UserConfig.Gui, service)
}, },
Hide: func() bool { Hide: func() bool {
return !gui.PodmanCommand.InDockerComposeProject return !gui.PodmanCommand.InComposeProject
}, },
} }
} }
@ -148,7 +148,7 @@ func (gui *Gui) handleServiceRemoveMenu(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
composeCommand := gui.Config.UserConfig.CommandTemplates.DockerCompose composeCommand := gui.Config.UserConfig.CommandTemplates.PodmanCompose
options := []*commandOption{ options := []*commandOption{
{ {

View file

@ -4,7 +4,6 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/docker/docker/api/types/container"
"github.com/christophe-duc/lazypodman/pkg/commands" "github.com/christophe-duc/lazypodman/pkg/commands"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -14,28 +13,28 @@ func sampleContainers() []*commands.Container {
{ {
ID: "1", ID: "1",
Name: "1", Name: "1",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "exited", State: "exited",
}, },
}, },
{ {
ID: "2", ID: "2",
Name: "2", Name: "2",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "running", State: "running",
}, },
}, },
{ {
ID: "3", ID: "3",
Name: "3", Name: "3",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "running", State: "running",
}, },
}, },
{ {
ID: "4", ID: "4",
Name: "4", Name: "4",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "created", State: "created",
}, },
}, },
@ -47,28 +46,28 @@ func expectedPerStatusContainers() []*commands.Container {
{ {
ID: "2", ID: "2",
Name: "2", Name: "2",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "running", State: "running",
}, },
}, },
{ {
ID: "3", ID: "3",
Name: "3", Name: "3",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "running", State: "running",
}, },
}, },
{ {
ID: "1", ID: "1",
Name: "1", Name: "1",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "exited", State: "exited",
}, },
}, },
{ {
ID: "4", ID: "4",
Name: "4", Name: "4",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "created", State: "created",
}, },
}, },
@ -80,28 +79,28 @@ func expectedLegacySortedContainers() []*commands.Container {
{ {
ID: "1", ID: "1",
Name: "1", Name: "1",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "exited", State: "exited",
}, },
}, },
{ {
ID: "2", ID: "2",
Name: "2", Name: "2",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "running", State: "running",
}, },
}, },
{ {
ID: "3", ID: "3",
Name: "3", Name: "3",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "running", State: "running",
}, },
}, },
{ {
ID: "4", ID: "4",
Name: "4", Name: "4",
Container: container.Summary{ Summary: commands.ContainerSummary{
State: "created", State: "created",
}, },
}, },
@ -110,8 +109,8 @@ func expectedLegacySortedContainers() []*commands.Container {
func assertEqualContainers(t *testing.T, left *commands.Container, right *commands.Container) { func assertEqualContainers(t *testing.T, left *commands.Container, right *commands.Container) {
t.Helper() t.Helper()
assert.Equal(t, left.Container.State, right.Container.State) assert.Equal(t, left.Summary.State, right.Summary.State)
assert.Equal(t, left.Container.ID, right.Container.ID) assert.Equal(t, left.Summary.ID, right.Summary.ID)
assert.Equal(t, left.Name, right.Name) assert.Equal(t, left.Name, right.Name)
} }

View file

@ -128,7 +128,7 @@ func (gui *Gui) createAllViews() error {
gui.Views.Containers.Highlight = true gui.Views.Containers.Highlight = true
gui.Views.Containers.SelBgColor = selectedLineBgColor gui.Views.Containers.SelBgColor = selectedLineBgColor
if gui.Config.UserConfig.Gui.ShowAllContainers || !gui.PodmanCommand.InDockerComposeProject { if gui.Config.UserConfig.Gui.ShowAllContainers || !gui.PodmanCommand.InComposeProject {
gui.Views.Containers.Title = gui.Tr.ContainersTitle gui.Views.Containers.Title = gui.Tr.ContainersTitle
} else { } else {
gui.Views.Containers.Title = gui.Tr.StandaloneContainersTitle gui.Views.Containers.Title = gui.Tr.StandaloneContainersTitle

View file

@ -95,7 +95,7 @@ func chineseSet() TranslationSet {
LogsTitle: "日志", LogsTitle: "日志",
ConfigTitle: "配置", ConfigTitle: "配置",
EnvTitle: "环境变量", EnvTitle: "环境变量",
DockerComposeConfigTitle: "Docker-Compose配置", ComposeConfigTitle: "Compose配置",
TopTitle: "系统资源管理", TopTitle: "系统资源管理",
StatsTitle: "统计信息", StatsTitle: "统计信息",
CreditsTitle: "关于我们", CreditsTitle: "关于我们",
@ -113,7 +113,7 @@ func chineseSet() TranslationSet {
NoServices: "没有服务", NoServices: "没有服务",
ConfirmQuit: "您确定要退出吗?", ConfirmQuit: "您确定要退出吗?",
ConfirmUpProject: "您确定要“up”的docker compose项目吗", ConfirmUpProject: "您确定要启动您的compose项目吗",
MustForceToRemoveContainer: "您无法删除正在运行的容器,除非您强制执行。您想强制执行吗?", MustForceToRemoveContainer: "您无法删除正在运行的容器,除非您强制执行。您想强制执行吗?",
NotEnoughSpace: "空间不足,无法渲染面板", NotEnoughSpace: "空间不足,无法渲染面板",
ConfirmPruneImages: "您确定要删除所有未使用的镜像吗?", ConfirmPruneImages: "您确定要删除所有未使用的镜像吗?",

View file

@ -68,7 +68,7 @@ func dutchSet() TranslationSet {
LogsTitle: "Logs", LogsTitle: "Logs",
ConfigTitle: "Config", ConfigTitle: "Config",
EnvTitle: "Env", EnvTitle: "Env",
DockerComposeConfigTitle: "Docker-Compose Configuratie", ComposeConfigTitle: "Compose Configuratie",
TopTitle: "Top", TopTitle: "Top",
StatsTitle: "Stats", StatsTitle: "Stats",
CreditsTitle: "Over", CreditsTitle: "Over",

View file

@ -110,10 +110,10 @@ type TranslationSet struct {
OpenInBrowser string OpenInBrowser string
SortContainersByState string SortContainersByState string
LogsTitle string LogsTitle string
ConfigTitle string ConfigTitle string
EnvTitle string EnvTitle string
DockerComposeConfigTitle string ComposeConfigTitle string
StatsTitle string StatsTitle string
CreditsTitle string CreditsTitle string
ContainerConfigTitle string ContainerConfigTitle string
@ -232,7 +232,7 @@ func englishSet() TranslationSet {
LogsTitle: "Logs", LogsTitle: "Logs",
ConfigTitle: "Config", ConfigTitle: "Config",
EnvTitle: "Env", EnvTitle: "Env",
DockerComposeConfigTitle: "Docker-Compose Config", ComposeConfigTitle: "Compose Config",
TopTitle: "Top", TopTitle: "Top",
StatsTitle: "Stats", StatsTitle: "Stats",
CreditsTitle: "About", CreditsTitle: "About",
@ -250,7 +250,7 @@ func englishSet() TranslationSet {
NoServices: "No services", NoServices: "No services",
ConfirmQuit: "Are you sure you want to quit?", ConfirmQuit: "Are you sure you want to quit?",
ConfirmUpProject: "Are you sure you want to 'up' your docker compose project?", ConfirmUpProject: "Are you sure you want to 'up' your compose project?",
MustForceToRemoveContainer: "You cannot remove a running container unless you force it. Do you want to force it?", MustForceToRemoveContainer: "You cannot remove a running container unless you force it. Do you want to force it?",
NotEnoughSpace: "Not enough space to render panels", NotEnoughSpace: "Not enough space to render panels",
ConfirmPruneImages: "Are you sure you want to prune all unused images?", ConfirmPruneImages: "Are you sure you want to prune all unused images?",

View file

@ -85,7 +85,7 @@ func frenchSet() TranslationSet {
LogsTitle: "Journaux", LogsTitle: "Journaux",
ConfigTitle: "Config", ConfigTitle: "Config",
EnvTitle: "Env", EnvTitle: "Env",
DockerComposeConfigTitle: "Config Docker-Compose", ComposeConfigTitle: "Config Compose",
TopTitle: "Top", TopTitle: "Top",
StatsTitle: "Statistiques", StatsTitle: "Statistiques",
CreditsTitle: "À propos", CreditsTitle: "À propos",

View file

@ -67,7 +67,7 @@ func germanSet() TranslationSet {
LogsTitle: "Protokoll", LogsTitle: "Protokoll",
ConfigTitle: "Konfiguration", ConfigTitle: "Konfiguration",
EnvTitle: "Env", EnvTitle: "Env",
DockerComposeConfigTitle: "Docker-Compose Konfiguration", ComposeConfigTitle: "Compose Konfiguration",
TopTitle: "Top", TopTitle: "Top",
StatsTitle: "Statistiken", StatsTitle: "Statistiken",
CreditsTitle: "Über Uns", CreditsTitle: "Über Uns",

View file

@ -67,7 +67,7 @@ func polishSet() TranslationSet {
LogsTitle: "Logi", LogsTitle: "Logi",
ConfigTitle: "Konfiguracja", ConfigTitle: "Konfiguracja",
EnvTitle: "Env", EnvTitle: "Env",
DockerComposeConfigTitle: "Konfiguracja docker-compose", ComposeConfigTitle: "Konfiguracja compose",
TopTitle: "Top", TopTitle: "Top",
StatsTitle: "Staty", StatsTitle: "Staty",
CreditsTitle: "O", CreditsTitle: "O",

View file

@ -95,7 +95,7 @@ func portugueseSet() TranslationSet {
LogsTitle: "Registros", LogsTitle: "Registros",
ConfigTitle: "Config", ConfigTitle: "Config",
EnvTitle: "Env", EnvTitle: "Env",
DockerComposeConfigTitle: "Docker-Compose Config", ComposeConfigTitle: "Compose Config",
TopTitle: "Topo", TopTitle: "Topo",
StatsTitle: "Estatísticas", StatsTitle: "Estatísticas",
CreditsTitle: "Sobre", CreditsTitle: "Sobre",
@ -113,7 +113,7 @@ func portugueseSet() TranslationSet {
NoServices: "Sem serviços", NoServices: "Sem serviços",
ConfirmQuit: "Tem certeza que deseja sair?", ConfirmQuit: "Tem certeza que deseja sair?",
ConfirmUpProject: "Tem certeza que deseja 'iniciar' seu projeto docker compose?", ConfirmUpProject: "Tem certeza que deseja 'iniciar' seu projeto compose?",
MustForceToRemoveContainer: "Você não pode remover um contêiner em execução a menos que o force. Deseja forçar?", MustForceToRemoveContainer: "Você não pode remover um contêiner em execução a menos que o force. Deseja forçar?",
NotEnoughSpace: "Sem espaço suficiente para renderizar os painéis", NotEnoughSpace: "Sem espaço suficiente para renderizar os painéis",
ConfirmPruneImages: "Tem certeza que deseja eliminar todas as imagens não utilizadas?", ConfirmPruneImages: "Tem certeza que deseja eliminar todas as imagens não utilizadas?",

View file

@ -90,7 +90,7 @@ func spanishSet() TranslationSet {
LogsTitle: "Logs", LogsTitle: "Logs",
ConfigTitle: "Configuración", ConfigTitle: "Configuración",
EnvTitle: "Variables de entorno", EnvTitle: "Variables de entorno",
DockerComposeConfigTitle: "Docker-Compose Config", ComposeConfigTitle: "Compose Config",
TopTitle: "Top", TopTitle: "Top",
StatsTitle: "Estadísticas", StatsTitle: "Estadísticas",
CreditsTitle: "Acerca", CreditsTitle: "Acerca",
@ -108,7 +108,7 @@ func spanishSet() TranslationSet {
NoServices: "Sin servicios", NoServices: "Sin servicios",
ConfirmQuit: "¿Realmente quieres salir?", ConfirmQuit: "¿Realmente quieres salir?",
ConfirmUpProject: "¿Realmente quieres levantar tu proyecto docker compose?", ConfirmUpProject: "¿Realmente quieres levantar tu proyecto compose?",
MustForceToRemoveContainer: "No puedes borrar un contenedor en ejecución a menos de que lo fuerces, ¿quieres hacerlo?", MustForceToRemoveContainer: "No puedes borrar un contenedor en ejecución a menos de que lo fuerces, ¿quieres hacerlo?",
NotEnoughSpace: "No hay suficiente espacio para renderizar los paneles", NotEnoughSpace: "No hay suficiente espacio para renderizar los paneles",
ConfirmPruneImages: "¿Realmente quieres limpiar todas tus imágenes?", ConfirmPruneImages: "¿Realmente quieres limpiar todas tus imágenes?",

View file

@ -67,7 +67,7 @@ func turkishSet() TranslationSet {
LogsTitle: "Kayitlar", LogsTitle: "Kayitlar",
ConfigTitle: "Ayarlar", ConfigTitle: "Ayarlar",
EnvTitle: "Env", EnvTitle: "Env",
DockerComposeConfigTitle: "Docker-Compose Ayar", ComposeConfigTitle: "Compose Ayar",
TopTitle: "Top", TopTitle: "Top",
StatsTitle: "Durumlar", StatsTitle: "Durumlar",
CreditsTitle: "Hakkinda", CreditsTitle: "Hakkinda",