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
} }
@ -115,7 +115,7 @@ func NewPodmanCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
Config: config, Config: config,
Runtime: runtime, Runtime: runtime,
ErrorChan: errorChan, ErrorChan: errorChan,
InDockerComposeProject: true, InComposeProject: true,
Closers: closers, Closers: closers,
} }
@ -123,12 +123,12 @@ func NewPodmanCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
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

@ -113,7 +113,7 @@ type TranslationSet struct {
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",