-
Notifications
You must be signed in to change notification settings - Fork 5.8k
feat: add Docker Desktop Logs view hints and navigation shortcut #13721
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,101 @@ | ||||||||||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||||||||||
| Copyright 2020 Docker Compose CLI authors | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||||||||||||||||||||||||||||||||
| you may not use this file except in compliance with the License. | ||||||||||||||||||||||||||||||||||
| You may obtain a copy of the License at | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Unless required by applicable law or agreed to in writing, software | ||||||||||||||||||||||||||||||||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||||||||||||||||||||||||||||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||||||||||||||||||||||||||
| See the License for the specific language governing permissions and | ||||||||||||||||||||||||||||||||||
| limitations under the License. | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| package compose | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||||||
| "io" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| "github.com/docker/cli/cli-plugins/hooks" | ||||||||||||||||||||||||||||||||||
| "github.com/docker/cli/cli-plugins/metadata" | ||||||||||||||||||||||||||||||||||
| "github.com/spf13/cobra" | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const deepLink = "docker-desktop://dashboard/logs" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const composeLogsHint = "Filter, search, and stream logs from all your Compose services\nin one place with Docker Desktop's Logs view. " + deepLink | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const dockerLogsHint = "View and search logs for all containers in one place\nwith Docker Desktop's Logs view. " + deepLink | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // hookHint defines a hint that can be returned by the hooks handler. | ||||||||||||||||||||||||||||||||||
| // When checkFlags is nil, the hint is always returned for the matching command. | ||||||||||||||||||||||||||||||||||
| // When checkFlags is set, the hint is only returned if the check passes. | ||||||||||||||||||||||||||||||||||
| type hookHint struct { | ||||||||||||||||||||||||||||||||||
| template string | ||||||||||||||||||||||||||||||||||
| checkFlags func(flags map[string]string) bool | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // hooksHints maps hook root commands to their hint definitions. | ||||||||||||||||||||||||||||||||||
| var hooksHints = map[string]hookHint{ | ||||||||||||||||||||||||||||||||||
| // standalone "docker logs" (not a compose subcommand) | ||||||||||||||||||||||||||||||||||
| "logs": {template: dockerLogsHint}, | ||||||||||||||||||||||||||||||||||
| "compose logs": {template: composeLogsHint}, | ||||||||||||||||||||||||||||||||||
| "compose up": { | ||||||||||||||||||||||||||||||||||
| template: composeLogsHint, | ||||||||||||||||||||||||||||||||||
| checkFlags: func(flags map[string]string) bool { | ||||||||||||||||||||||||||||||||||
| // Only show the hint when running in detached mode | ||||||||||||||||||||||||||||||||||
| _, hasDetach := flags["detach"] | ||||||||||||||||||||||||||||||||||
| _, hasD := flags["d"] | ||||||||||||||||||||||||||||||||||
| return hasDetach || hasD | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // HooksCommand returns the hidden subcommand that the Docker CLI invokes | ||||||||||||||||||||||||||||||||||
| // after command execution when the compose plugin has hooks configured. | ||||||||||||||||||||||||||||||||||
| // Docker Desktop is responsible for registering which commands trigger hooks | ||||||||||||||||||||||||||||||||||
| // and for gating on feature flags/settings — the hook handler simply | ||||||||||||||||||||||||||||||||||
| // responds with the appropriate hint message. | ||||||||||||||||||||||||||||||||||
| func HooksCommand() *cobra.Command { | ||||||||||||||||||||||||||||||||||
| return &cobra.Command{ | ||||||||||||||||||||||||||||||||||
| Use: metadata.HookSubcommandName, | ||||||||||||||||||||||||||||||||||
| Hidden: true, | ||||||||||||||||||||||||||||||||||
| // Override PersistentPreRunE to prevent the parent's PersistentPreRunE | ||||||||||||||||||||||||||||||||||
| // (plugin initialization) from running for hook invocations. | ||||||||||||||||||||||||||||||||||
| PersistentPreRunE: func(*cobra.Command, []string) error { return nil }, | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+63
to
+69
|
||||||||||||||||||||||||||||||||||
| func HooksCommand() *cobra.Command { | |
| return &cobra.Command{ | |
| Use: metadata.HookSubcommandName, | |
| Hidden: true, | |
| // Override PersistentPreRunE to prevent the parent's PersistentPreRunE | |
| // (plugin initialization) from running for hook invocations. | |
| PersistentPreRunE: func(*cobra.Command, []string) error { return nil }, | |
| // | |
| // Note: a child command's PersistentPreRunE does not prevent a parent's | |
| // PersistentPreRunE from running in Cobra. Any optimization to skip parent | |
| // plugin initialization for hook invocations must be implemented where the | |
| // parent command is configured. | |
| func HooksCommand() *cobra.Command { | |
| return &cobra.Command{ | |
| Use: metadata.HookSubcommandName, | |
| Hidden: true, |
glours marked this conversation as resolved.
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| /* | ||
| Copyright 2020 Docker Compose CLI authors | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package compose | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/json" | ||
| "testing" | ||
|
|
||
| "github.com/docker/cli/cli-plugins/hooks" | ||
| "gotest.tools/v3/assert" | ||
| ) | ||
|
|
||
| func TestHandleHook_NoArgs(t *testing.T) { | ||
| var buf bytes.Buffer | ||
| err := handleHook(nil, &buf) | ||
| assert.NilError(t, err) | ||
| assert.Equal(t, buf.String(), "") | ||
| } | ||
|
|
||
| func TestHandleHook_InvalidJSON(t *testing.T) { | ||
| var buf bytes.Buffer | ||
| err := handleHook([]string{"not json"}, &buf) | ||
| assert.NilError(t, err) | ||
| assert.Equal(t, buf.String(), "") | ||
| } | ||
|
|
||
| func TestHandleHook_UnknownCommand(t *testing.T) { | ||
| data := marshalHookData(t, hooks.Request{ | ||
| RootCmd: "compose push", | ||
| }) | ||
| var buf bytes.Buffer | ||
| err := handleHook([]string{data}, &buf) | ||
| assert.NilError(t, err) | ||
| assert.Equal(t, buf.String(), "") | ||
| } | ||
|
|
||
| func TestHandleHook_LogsCommand(t *testing.T) { | ||
| tests := []struct { | ||
| rootCmd string | ||
| wantHint string | ||
| }{ | ||
| {rootCmd: "compose logs", wantHint: composeLogsHint}, | ||
| {rootCmd: "logs", wantHint: dockerLogsHint}, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.rootCmd, func(t *testing.T) { | ||
| data := marshalHookData(t, hooks.Request{ | ||
| RootCmd: tt.rootCmd, | ||
| }) | ||
| var buf bytes.Buffer | ||
| err := handleHook([]string{data}, &buf) | ||
| assert.NilError(t, err) | ||
|
|
||
| msg := unmarshalResponse(t, buf.Bytes()) | ||
| assert.Equal(t, msg.Type, hooks.NextSteps) | ||
| assert.Equal(t, msg.Template, tt.wantHint) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestHandleHook_ComposeUpDetached(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| flags map[string]string | ||
| wantHint bool | ||
| }{ | ||
| { | ||
| name: "with --detach flag", | ||
| flags: map[string]string{"detach": ""}, | ||
| wantHint: true, | ||
| }, | ||
| { | ||
| name: "with -d flag", | ||
| flags: map[string]string{"d": ""}, | ||
| wantHint: true, | ||
| }, | ||
| { | ||
| name: "without detach flag", | ||
| flags: map[string]string{"build": ""}, | ||
| wantHint: false, | ||
| }, | ||
| { | ||
| name: "no flags", | ||
| flags: map[string]string{}, | ||
| wantHint: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| data := marshalHookData(t, hooks.Request{ | ||
| RootCmd: "compose up", | ||
| Flags: tt.flags, | ||
| }) | ||
| var buf bytes.Buffer | ||
| err := handleHook([]string{data}, &buf) | ||
| assert.NilError(t, err) | ||
|
|
||
| if tt.wantHint { | ||
| msg := unmarshalResponse(t, buf.Bytes()) | ||
| assert.Equal(t, msg.Template, composeLogsHint) | ||
| } else { | ||
| assert.Equal(t, buf.String(), "") | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func marshalHookData(t *testing.T, data hooks.Request) string { | ||
| t.Helper() | ||
| b, err := json.Marshal(data) | ||
| assert.NilError(t, err) | ||
| return string(b) | ||
| } | ||
|
|
||
| func unmarshalResponse(t *testing.T, data []byte) hooks.Response { | ||
| t.Helper() | ||
| var msg hooks.Response | ||
| err := json.Unmarshal(data, &msg) | ||
| assert.NilError(t, err) | ||
| return msg | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unclear to me why we provide
docker logshint inside compose. Maybe just because we have no place in docker/cli for this and compose used to be installed by 99% users?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because Compose is in charge of this hint globally, it could trigger the hint display to any other cli plugins too.