2020-06-22 09:55:28 +02:00
|
|
|
/*
|
|
|
|
Copyright 2020 Docker, Inc.
|
|
|
|
|
|
|
|
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 metrics
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
flag "github.com/spf13/pflag"
|
|
|
|
)
|
|
|
|
|
|
|
|
var managementCommands = []string{
|
|
|
|
"app",
|
|
|
|
"assemble",
|
|
|
|
"builder",
|
|
|
|
"buildx",
|
2020-07-01 14:57:08 +02:00
|
|
|
"ecs",
|
2020-07-03 16:57:07 +02:00
|
|
|
"ecs compose",
|
2020-06-22 09:55:28 +02:00
|
|
|
"cluster",
|
|
|
|
"compose",
|
|
|
|
"config",
|
|
|
|
"container",
|
|
|
|
"context",
|
2020-07-03 16:57:07 +02:00
|
|
|
// We add "context create" as a management command to be able to catch
|
|
|
|
// calls to "context create aci"
|
|
|
|
"context create",
|
2020-06-22 09:55:28 +02:00
|
|
|
"help",
|
|
|
|
"image",
|
2020-07-03 16:57:07 +02:00
|
|
|
// Adding "login" as a management command so that the system can catch
|
|
|
|
// commands like `docker login azure`
|
|
|
|
"login",
|
2020-06-22 09:55:28 +02:00
|
|
|
"manifest",
|
|
|
|
"network",
|
|
|
|
"node",
|
|
|
|
"plugin",
|
|
|
|
"registry",
|
|
|
|
"secret",
|
|
|
|
"service",
|
|
|
|
"stack",
|
|
|
|
"swarm",
|
|
|
|
"system",
|
|
|
|
"template",
|
|
|
|
"trust",
|
|
|
|
"volume",
|
|
|
|
}
|
|
|
|
|
2020-07-03 16:57:07 +02:00
|
|
|
// managementSubCommands holds a list of allowed subcommands of a management
|
|
|
|
// command. For example we want to send an event for "docker login azure" but
|
|
|
|
// we don't wat to send the name of the registry when the user does a
|
|
|
|
// "docker login my-registry", we only want to send "login"
|
|
|
|
var managementSubCommands = map[string][]string{
|
|
|
|
"login": {
|
|
|
|
"azure",
|
|
|
|
},
|
|
|
|
"context create": {
|
|
|
|
"aci",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2020-07-03 10:55:28 +02:00
|
|
|
const (
|
|
|
|
scanCommand = "scan"
|
|
|
|
)
|
|
|
|
|
2020-06-22 09:55:28 +02:00
|
|
|
// Track sends the tracking analytics to Docker Desktop
|
|
|
|
func Track(context string, args []string, flags *flag.FlagSet) {
|
2020-07-03 12:17:14 +02:00
|
|
|
wasIn := make(chan bool)
|
|
|
|
|
2020-06-22 09:55:28 +02:00
|
|
|
// Fire and forget, we don't want to slow down the user waiting for DD
|
|
|
|
// metrics endpoint to respond. We could lose some events but that's ok.
|
|
|
|
go func() {
|
|
|
|
defer func() {
|
|
|
|
_ = recover()
|
|
|
|
}()
|
2020-07-03 12:17:14 +02:00
|
|
|
|
|
|
|
wasIn <- true
|
|
|
|
|
2020-06-22 09:55:28 +02:00
|
|
|
command := getCommand(args, flags)
|
|
|
|
if command != "" {
|
|
|
|
c := NewClient()
|
|
|
|
c.Send(Command{
|
|
|
|
Command: command,
|
|
|
|
Context: context,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}()
|
2020-07-03 12:17:14 +02:00
|
|
|
<-wasIn
|
2020-06-22 09:55:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func getCommand(args []string, flags *flag.FlagSet) string {
|
|
|
|
command := ""
|
2020-07-03 10:55:28 +02:00
|
|
|
strippedArgs := stripFlags(args, flags)
|
|
|
|
|
|
|
|
if len(strippedArgs) != 0 {
|
|
|
|
command = strippedArgs[0]
|
|
|
|
|
|
|
|
if command == scanCommand {
|
|
|
|
return getScanCommand(args)
|
|
|
|
}
|
2020-06-22 09:55:28 +02:00
|
|
|
|
2020-07-01 14:57:08 +02:00
|
|
|
for {
|
2020-07-03 16:57:07 +02:00
|
|
|
if contains(managementCommands, command) {
|
|
|
|
if sub := getSubCommand(command, strippedArgs[1:]); sub != "" {
|
2020-07-01 14:57:08 +02:00
|
|
|
command += " " + sub
|
2020-07-03 10:55:28 +02:00
|
|
|
strippedArgs = strippedArgs[1:]
|
2020-07-01 14:57:08 +02:00
|
|
|
continue
|
|
|
|
}
|
2020-06-22 09:55:28 +02:00
|
|
|
}
|
2020-07-01 14:57:08 +02:00
|
|
|
break
|
2020-06-22 09:55:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return command
|
|
|
|
}
|
|
|
|
|
2020-07-03 10:55:28 +02:00
|
|
|
func getScanCommand(args []string) string {
|
|
|
|
command := args[0]
|
|
|
|
|
|
|
|
if contains(args, "--auth") {
|
|
|
|
return command + " auth"
|
|
|
|
}
|
|
|
|
|
|
|
|
if contains(args, "--version") {
|
|
|
|
return command + " version"
|
|
|
|
}
|
|
|
|
|
|
|
|
return command
|
|
|
|
}
|
|
|
|
|
2020-07-03 16:57:07 +02:00
|
|
|
func getSubCommand(command string, args []string) string {
|
|
|
|
if len(args) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if val, ok := managementSubCommands[command]; ok {
|
|
|
|
if contains(val, args[0]) {
|
|
|
|
return args[0]
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if isArg(args[0]) {
|
2020-06-22 09:55:28 +02:00
|
|
|
return args[0]
|
|
|
|
}
|
2020-07-03 16:57:07 +02:00
|
|
|
|
2020-06-22 09:55:28 +02:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func contains(array []string, needle string) bool {
|
|
|
|
for _, val := range array {
|
|
|
|
if val == needle {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func stripFlags(args []string, flags *flag.FlagSet) []string {
|
|
|
|
commands := []string{}
|
|
|
|
|
|
|
|
for len(args) > 0 {
|
|
|
|
s := args[0]
|
|
|
|
args = args[1:]
|
|
|
|
|
|
|
|
if s == "--" {
|
|
|
|
return commands
|
|
|
|
}
|
|
|
|
|
|
|
|
if flagArg(s, flags) {
|
|
|
|
if len(args) <= 1 {
|
|
|
|
return commands
|
|
|
|
}
|
|
|
|
args = args[1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
if isArg(s) {
|
|
|
|
commands = append(commands, s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return commands
|
|
|
|
}
|
|
|
|
|
|
|
|
func flagArg(s string, flags *flag.FlagSet) bool {
|
|
|
|
return strings.HasPrefix(s, "--") && !strings.Contains(s, "=") && !hasNoOptDefVal(s[2:], flags) ||
|
|
|
|
strings.HasPrefix(s, "-") && !strings.Contains(s, "=") && len(s) == 2 && !shortHasNoOptDefVal(s[1:], flags)
|
|
|
|
}
|
|
|
|
|
|
|
|
func isArg(s string) bool {
|
|
|
|
return s != "" && !strings.HasPrefix(s, "-")
|
|
|
|
}
|
|
|
|
|
|
|
|
func hasNoOptDefVal(name string, fs *flag.FlagSet) bool {
|
|
|
|
flag := fs.Lookup(name)
|
|
|
|
if flag == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return flag.NoOptDefVal != ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func shortHasNoOptDefVal(name string, fs *flag.FlagSet) bool {
|
|
|
|
flag := fs.ShorthandLookup(name[:1])
|
|
|
|
if flag == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return flag.NoOptDefVal != ""
|
|
|
|
}
|