mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2026-08-24 10:14:12 -05:00
feat(docker): add container info
This commit is contained in:
committed by
Jan De Dobbeleer
parent
4f19175e0c
commit
4a6a376110
+93
-2
@@ -2,7 +2,9 @@ package segments
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"slices"
|
||||
|
||||
@@ -12,8 +14,12 @@ import (
|
||||
const (
|
||||
// FetchContext is the property used to fetch the current docker context
|
||||
FetchContext options.Option = "fetch_context"
|
||||
|
||||
// DefaultDockerContext is the default docker context name
|
||||
defaultDockerContext = "default"
|
||||
// DockerCommand is the property used to specify the docker command to use
|
||||
DockerCommand options.Option = "docker_command"
|
||||
// Filter is the property used to specify a filter to apply to docker ps results in environment mode, see https://docs.docker.com/reference/cli/docker/container/ls/#filter
|
||||
Filter options.Option = "filter"
|
||||
)
|
||||
|
||||
type DockerConfig struct {
|
||||
@@ -22,8 +28,19 @@ type DockerConfig struct {
|
||||
|
||||
type Docker struct {
|
||||
Base
|
||||
command *cmd
|
||||
Context string
|
||||
Containers []Container
|
||||
}
|
||||
|
||||
Context string
|
||||
type Container struct {
|
||||
ID string
|
||||
Image string
|
||||
Command string
|
||||
Created string
|
||||
Status string
|
||||
Ports string
|
||||
Names string
|
||||
}
|
||||
|
||||
func (d *Docker) Template() string {
|
||||
@@ -59,6 +76,7 @@ func (d *Docker) Enabled() bool {
|
||||
extensions = d.options.StringArray(LanguageExtensions, extensions)
|
||||
|
||||
displayMode := d.options.String(DisplayMode, DisplayModeContext)
|
||||
|
||||
switch displayMode {
|
||||
case DisplayModeContext:
|
||||
return d.fetchContext()
|
||||
@@ -73,6 +91,40 @@ func (d *Docker) Enabled() bool {
|
||||
}
|
||||
|
||||
return true
|
||||
case DisplayModeEnvironment:
|
||||
// always fetch context first
|
||||
_ = d.fetchContext()
|
||||
|
||||
if d.Context == "" {
|
||||
d.Context = defaultStr
|
||||
}
|
||||
|
||||
dockerCommand := d.options.String(DockerCommand, "docker")
|
||||
if !d.env.HasCommand(dockerCommand) {
|
||||
return false
|
||||
}
|
||||
|
||||
filter := d.options.String(Filter, "")
|
||||
// Use Go template formatting with tab separation
|
||||
format := `{{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}`
|
||||
args := []string{"ps", "--format", format}
|
||||
if len(filter) > 0 {
|
||||
args = append(args, "--filter", filter)
|
||||
}
|
||||
|
||||
d.command = &cmd{
|
||||
executable: dockerCommand,
|
||||
args: args,
|
||||
}
|
||||
|
||||
containers, err := d.fetchContainers()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
d.Containers = containers
|
||||
|
||||
return len(d.Containers) > 0
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -111,3 +163,42 @@ func (d *Docker) fetchContext() bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *Docker) fetchContainers() ([]Container, error) {
|
||||
if d.command == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
output, err := d.env.RunCommand(d.command.executable, d.command.args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
if len(lines) == 0 || (len(lines) == 1 && lines[0] == "") {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
containers := make([]Container, 0, len(lines))
|
||||
|
||||
for i, line := range lines {
|
||||
line = strings.TrimRight(line, "\r")
|
||||
fields := strings.Split(line, "\t")
|
||||
|
||||
if len(fields) != 7 {
|
||||
return nil, fmt.Errorf("invalid docker ps output on line %d: expected 7 fields, got %d", i+1, len(fields))
|
||||
}
|
||||
|
||||
containers = append(containers, Container{
|
||||
ID: fields[0],
|
||||
Image: fields[1],
|
||||
Command: fields[2],
|
||||
Created: fields[3],
|
||||
Status: fields[4],
|
||||
Ports: fields[5],
|
||||
Names: fields[6],
|
||||
})
|
||||
}
|
||||
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
@@ -90,3 +90,111 @@ func TestDockerFiles(t *testing.T) {
|
||||
assert.Equal(t, tc.ExpectedEnabled, docker.Enabled(), tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerEnvironment(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
ExpectedContext string
|
||||
Filter string
|
||||
DockerCommand string
|
||||
CommandOutput string
|
||||
ExpectedEnabled bool
|
||||
HasCommand bool
|
||||
}{
|
||||
{
|
||||
Case: "Docker command not found",
|
||||
ExpectedEnabled: false,
|
||||
HasCommand: false,
|
||||
},
|
||||
{
|
||||
Case: "No running containers",
|
||||
ExpectedEnabled: false,
|
||||
HasCommand: true,
|
||||
CommandOutput: "",
|
||||
},
|
||||
{
|
||||
Case: "One running container",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
CommandOutput: "c1\timage1\tcmd1\tcreated1\tstatus1\tports1\tnames1",
|
||||
ExpectedContext: "1 running (c1)",
|
||||
},
|
||||
{
|
||||
Case: "Multiple running containers",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
CommandOutput: "c1\timage1\tcmd1\tcreated1\tstatus1\tports1\tnames1\nc2\timage2\tcmd2\tcreated2\tstatus2\tports2\tnames2",
|
||||
ExpectedContext: "2 running (c1)",
|
||||
},
|
||||
{
|
||||
Case: "Multiple running containers with windows line endings",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
CommandOutput: "c1\timage1\tcmd1\tcreated1\tstatus1\tports1\tnames1\r\nc2\timage2\tcmd2\tcreated2\tstatus2\tports2\tnames2\r\n",
|
||||
ExpectedContext: "2 running (c1)",
|
||||
},
|
||||
{
|
||||
Case: "Output with empty line",
|
||||
ExpectedEnabled: false,
|
||||
HasCommand: true,
|
||||
CommandOutput: "c1\timage1\tcmd1\tcreated1\tstatus1\tports1\tnames1\n\nc2\timage2\tcmd2\tcreated2\tstatus2\tports2\tnames2",
|
||||
},
|
||||
{
|
||||
Case: "Filter applied",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
Filter: "name=service1",
|
||||
CommandOutput: "c1\timage1\tcmd1\tcreated1\tstatus1\tports1\tnames1",
|
||||
ExpectedContext: "1 running (c1)",
|
||||
},
|
||||
{
|
||||
Case: "Custom docker command",
|
||||
ExpectedEnabled: true,
|
||||
HasCommand: true,
|
||||
DockerCommand: "podman",
|
||||
CommandOutput: "c1\timage1\tcmd1\tcreated1\tstatus1\tports1\tnames1",
|
||||
ExpectedContext: "1 running (c1)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.Case, func(t *testing.T) {
|
||||
env := new(mock.Environment)
|
||||
env.On("Getenv", mock_.Anything).Return("")
|
||||
env.On("Home").Return("")
|
||||
env.On("FileContent", mock_.Anything).Return("")
|
||||
|
||||
dockerCommand := "docker"
|
||||
if len(tc.DockerCommand) > 0 {
|
||||
dockerCommand = tc.DockerCommand
|
||||
}
|
||||
|
||||
env.On("HasCommand", dockerCommand).Return(tc.HasCommand)
|
||||
|
||||
format := `{{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}`
|
||||
args := []string{"ps", "--format", format}
|
||||
if len(tc.Filter) > 0 {
|
||||
args = append(args, "--filter", tc.Filter)
|
||||
}
|
||||
|
||||
env.On("RunCommand", dockerCommand, args).Return(tc.CommandOutput, nil)
|
||||
|
||||
props := options.Map{
|
||||
DisplayMode: DisplayModeEnvironment,
|
||||
Filter: tc.Filter,
|
||||
}
|
||||
if len(tc.DockerCommand) > 0 {
|
||||
props[DockerCommand] = tc.DockerCommand
|
||||
}
|
||||
|
||||
docker := &Docker{}
|
||||
docker.Init(props, env)
|
||||
|
||||
assert.Equal(t, tc.ExpectedEnabled, docker.Enabled())
|
||||
if tc.ExpectedEnabled {
|
||||
template := `{{ len .Containers }} running ({{ (index .Containers 0).ID }})`
|
||||
assert.Equal(t, tc.ExpectedContext, renderTemplate(env, template, docker))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+14
-1
@@ -1615,7 +1615,8 @@
|
||||
"$ref": "#/definitions/display_mode",
|
||||
"enum": [
|
||||
"files",
|
||||
"context"
|
||||
"context",
|
||||
"environment"
|
||||
]
|
||||
},
|
||||
"extensions": {
|
||||
@@ -1632,6 +1633,18 @@
|
||||
"title": "Fetch Context",
|
||||
"description": "Fetch the Docker context",
|
||||
"default": true
|
||||
},
|
||||
"docker_command": {
|
||||
"type": "string",
|
||||
"title": "Docker Command",
|
||||
"description": "Command used to call Docker",
|
||||
"default": "docker"
|
||||
},
|
||||
"filter": {
|
||||
"type": "string",
|
||||
"title": "Filter",
|
||||
"description": "Filter passed to docker ps. See https://docs.docker.com/reference/cli/docker/container/ls/#filter",
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
"unevaluatedProperties": false
|
||||
|
||||
@@ -6,7 +6,7 @@ sidebar_label: Docker
|
||||
|
||||
## What
|
||||
|
||||
Display the current [Docker][docker] context. Will not be active when using the default context.
|
||||
Display the current [Docker][docker] context, or a list of running containers when `display_mode` is set to `environment`.
|
||||
|
||||
## Sample Configuration
|
||||
|
||||
@@ -20,16 +20,23 @@ import Config from "@site/src/components/Config.js";
|
||||
foreground: "#000000",
|
||||
background: "#0B59E7",
|
||||
template: " \uf308 {{ .Context }} ",
|
||||
options: {
|
||||
display_mode: "environment",
|
||||
docker_command: "docker",
|
||||
filter: "name=oh-my-posh-db-1"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
## Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --------------- | :--------: | :------------------------------------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `display_mode` | `string` | `context` | <ul><li>`files`: the segment is only displayed when a file `extensions` listed is present</li><li>`context`: displays the segment when a Docker context active</li></ul> |
|
||||
| `fetch_context` | `boolean` | `true` | also fetch the current active Docker context when in the `files` display mode |
|
||||
| `extensions` | `[]string` | `compose.yml, compose.yaml, docker-compose.yml, docker-compose.yaml, Dockerfile` | allows to override the default list of file extensions to validate |
|
||||
| Name | Type | Default | Description |
|
||||
| ---------------- | ---------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `display_mode` | `string` | `context` | `files`, `context`, or `environment` |
|
||||
| `fetch_context` | `boolean` | `true` | fetch context in `files` mode |
|
||||
| `docker_command` | `string` | `docker` | command used in `environment` mode |
|
||||
| `filter` | `string` | | passed to `docker ps`, see [the filter documentation](https://docs.docker.com/engine/reference/commandline/ps/#filtering) |
|
||||
| `extensions` | `[]string` | compose files and Dockerfile | overrides the file checks |
|
||||
|
||||
## Template ([info][templates])
|
||||
|
||||
@@ -43,10 +50,22 @@ import Config from "@site/src/components/Config.js";
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---------- | -------- | -------------------------- |
|
||||
| `.Context` | `string` | the current active context |
|
||||
| Name | Type | Description |
|
||||
| ------------- | ------------- | ------------------------------------------------------------- |
|
||||
| `.Context` | `string` | current active context |
|
||||
| `.Containers` | `[]Container` | running containers from `docker ps` (`environment` mode only) |
|
||||
|
||||
#### Container
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---------- | -------- | ----------------- |
|
||||
| `.ID` | `string` | container ID |
|
||||
| `.Image` | `string` | container image |
|
||||
| `.Command` | `string` | container command |
|
||||
| `.Created` | `string` | created time |
|
||||
| `.Status` | `string` | container status |
|
||||
| `.Ports` | `string` | published ports |
|
||||
| `.Names` | `string` | container name |
|
||||
|
||||
[docker]: https://www.docker.com/
|
||||
[go-text-template]: https://golang.org/pkg/text/template/
|
||||
[templates]: /docs/configuration/templates
|
||||
|
||||
Reference in New Issue
Block a user