-
Notifications
You must be signed in to change notification settings - Fork 253
feat: display docker scout hints on build and pull #2252
Changes from 7 commits
635db3c
62c2e00
90058ec
1ceb12a
fac770a
8371dab
9a34334
eb0c44c
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,68 @@ | ||
/* | ||
Copyright 2023 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 mobycli | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/docker/compose-cli/api/config" | ||
) | ||
|
||
const ( | ||
cliHintsEnvVarName = "DOCKER_CLI_HINTS" | ||
cliHintsDefaultBehaviour = true | ||
|
||
cliHintsPluginName = "-x-cli-hints" | ||
cliHintsEnabledName = "enabled" | ||
cliHintsEnabled = "true" | ||
cliHintsDisabled = "false" | ||
) | ||
|
||
func CliHintsEnabled() bool { | ||
if envValue, ok := os.LookupEnv(cliHintsEnvVarName); ok { | ||
if enabled, err := parseCliHintFlag(envValue); err == nil { | ||
return enabled | ||
} | ||
} | ||
|
||
conf, err := config.LoadFile(config.Dir()) | ||
if err != nil { | ||
// can't read the config file, use the default behaviour | ||
return cliHintsDefaultBehaviour | ||
} | ||
if cliHintsPluginConfig, ok := conf.Plugins[cliHintsPluginName]; ok { | ||
if cliHintsValue, ok := cliHintsPluginConfig[cliHintsEnabledName]; ok { | ||
if cliHints, err := parseCliHintFlag(cliHintsValue); err == nil { | ||
return cliHints | ||
} | ||
} | ||
} | ||
|
||
return cliHintsDefaultBehaviour | ||
} | ||
|
||
func parseCliHintFlag(value string) (bool, error) { | ||
switch value { | ||
case cliHintsEnabled: | ||
return true, nil | ||
case cliHintsDisabled: | ||
return false, nil | ||
default: | ||
return cliHintsDefaultBehaviour, fmt.Errorf("could not parse CLI hints enabled flag") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
/* | ||
Copyright 2023 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 mobycli | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/docker/compose-cli/api/config" | ||
|
||
"gotest.tools/v3/assert" | ||
) | ||
|
||
func TestCliHintsEnabled(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
setup func() | ||
expected bool | ||
}{ | ||
{ | ||
"enabled by default", | ||
func() {}, | ||
true, | ||
}, | ||
{ | ||
"enabled from environment variable", | ||
func() { | ||
t.Setenv(cliHintsEnvVarName, "true") | ||
}, | ||
true, | ||
}, | ||
{ | ||
"disabled from environment variable", | ||
func() { | ||
t.Setenv(cliHintsEnvVarName, "false") | ||
}, | ||
false, | ||
}, | ||
{ | ||
"unsupported value", | ||
func() { | ||
t.Setenv(cliHintsEnvVarName, "maybe") | ||
}, | ||
true, | ||
}, | ||
{ | ||
"enabled in config file", | ||
func() { | ||
d := testConfigDir(t) | ||
writeSampleConfig(t, d, configEnabled) | ||
}, | ||
true, | ||
}, | ||
{ | ||
"plugin defined in config file but no enabled entry", | ||
func() { | ||
d := testConfigDir(t) | ||
writeSampleConfig(t, d, configPartial) | ||
}, | ||
true, | ||
}, | ||
|
||
{ | ||
"unsupported value", | ||
func() { | ||
d := testConfigDir(t) | ||
writeSampleConfig(t, d, configOnce) | ||
}, | ||
true, | ||
}, | ||
{ | ||
"disabled in config file", | ||
func() { | ||
d := testConfigDir(t) | ||
writeSampleConfig(t, d, configDisabled) | ||
}, | ||
false, | ||
}, | ||
{ | ||
"enabled in config file but disabled by env var", | ||
func() { | ||
d := testConfigDir(t) | ||
writeSampleConfig(t, d, configEnabled) | ||
t.Setenv(cliHintsEnvVarName, "false") | ||
}, | ||
false, | ||
}, | ||
{ | ||
"disabled in config file but enabled by env var", | ||
func() { | ||
d := testConfigDir(t) | ||
writeSampleConfig(t, d, configDisabled) | ||
t.Setenv(cliHintsEnvVarName, "true") | ||
}, | ||
true, | ||
}, | ||
} | ||
|
||
for _, testCase := range testCases { | ||
tc := testCase | ||
t.Run(tc.name, func(t *testing.T) { | ||
tc.setup() | ||
assert.Equal(t, CliHintsEnabled(), tc.expected) | ||
}) | ||
} | ||
} | ||
|
||
func testConfigDir(t *testing.T) string { | ||
dir := config.Dir() | ||
d, _ := os.MkdirTemp("", "") | ||
config.WithDir(d) | ||
t.Cleanup(func() { | ||
_ = os.RemoveAll(d) | ||
config.WithDir(dir) | ||
}) | ||
return d | ||
} | ||
|
||
func writeSampleConfig(t *testing.T, d string, conf []byte) { | ||
err := os.WriteFile(filepath.Join(d, config.ConfigFileName), conf, 0644) | ||
assert.NilError(t, err) | ||
} | ||
|
||
var configEnabled = []byte(`{ | ||
"plugins": { | ||
"-x-cli-hints": { | ||
"enabled": "true" | ||
} | ||
} | ||
}`) | ||
|
||
var configDisabled = []byte(`{ | ||
"plugins": { | ||
"-x-cli-hints": { | ||
"enabled": "false" | ||
} | ||
} | ||
}`) | ||
|
||
var configPartial = []byte(`{ | ||
"plugins": { | ||
"-x-cli-hints": { | ||
} | ||
} | ||
}`) | ||
|
||
var configOnce = []byte(`{ | ||
"plugins": { | ||
"-x-cli-hints": { | ||
"enabled": "maybe" | ||
} | ||
} | ||
}`) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* | ||
Copyright 2023 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 mobycli | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"strings" | ||
|
||
"github.com/docker/compose/v2/pkg/utils" | ||
|
||
"github.com/fatih/color" | ||
) | ||
|
||
func displayScoutQuickViewSuggestMsgOnPull(args []string) { | ||
image := pulledImageFromArgs(args) | ||
displayScoutQuickViewSuggestMsg(image) | ||
} | ||
|
||
func displayScoutQuickViewSuggestMsgOnBuild(args []string) { | ||
// only display the hint in the main case, build command and not buildx build, no output flag, no progress flag, no push flag | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as previous comment, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure to understand. |
||
if utils.StringContains(args, "--output") || utils.StringContains(args, "-o") || | ||
utils.StringContains(args, "--progress") || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add the env var 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
utils.StringContains(args, "--push") { | ||
return | ||
} | ||
displayScoutQuickViewSuggestMsg("") | ||
} | ||
|
||
func displayScoutQuickViewSuggestMsg(image string) { | ||
if !CliHintsEnabled() { | ||
return | ||
} | ||
if len(image) > 0 { | ||
image = " " + image | ||
} | ||
out := os.Stderr | ||
b := color.New(color.Bold) | ||
_, _ = fmt.Fprintln(out) | ||
_, _ = b.Fprintln(out, "What's Next?") | ||
_, _ = fmt.Fprintf(out, " View summary of image vulnerabilities and recommendations → %s", color.CyanString("docker scout quickview%s", image)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For a while we've talked about using "analysis" vs "vulnerabilities", I feel like in this case, this might still be a better choice of word? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 I don't know, analysis is really more vague than "vulnerabilities and recommendations". Especially that's what will be displayed by the proposed command. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK for sure, I just know there's been a lot of discussion around trying to stop using "vulnerabilities", but if others have already seen this, then maybe it's fine as is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know, I think I'm not aware of those discussions and it's still what we are displaying in the |
||
_, _ = fmt.Fprintln(out) | ||
} | ||
|
||
func pulledImageFromArgs(args []string) string { | ||
var image string | ||
var pull bool | ||
for _, a := range args { | ||
if a == "pull" { | ||
pull = true | ||
continue | ||
} | ||
if pull && !strings.HasPrefix(a, "-") { | ||
image = a | ||
break | ||
} | ||
} | ||
return image | ||
} |
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.
build
command redirects tobuildx build
since Docker 23 and I don't see in themetrics.HasQuietFlag
anything checking the BuildKit progress quiet mode.