metrics.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /*
  2. Copyright 2020 Docker, Inc.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package metrics
  14. import (
  15. "strings"
  16. flag "github.com/spf13/pflag"
  17. "github.com/docker/compose-cli/utils"
  18. )
  19. var managementCommands = []string{
  20. "app",
  21. "assemble",
  22. "builder",
  23. "buildx",
  24. "ecs",
  25. "ecs compose",
  26. "cluster",
  27. "compose",
  28. "config",
  29. "container",
  30. "context",
  31. // We add "context create" as a management command to be able to catch
  32. // calls to "context create aci"
  33. "context create",
  34. "help",
  35. "image",
  36. // Adding "login" as a management command so that the system can catch
  37. // commands like `docker login azure`
  38. "login",
  39. "manifest",
  40. "network",
  41. "node",
  42. "plugin",
  43. "registry",
  44. "secret",
  45. "service",
  46. "stack",
  47. "swarm",
  48. "system",
  49. "template",
  50. "trust",
  51. "volume",
  52. }
  53. // managementSubCommands holds a list of allowed subcommands of a management
  54. // command. For example we want to send an event for "docker login azure" but
  55. // we don't wat to send the name of the registry when the user does a
  56. // "docker login my-registry", we only want to send "login"
  57. var managementSubCommands = map[string][]string{
  58. "login": {
  59. "azure",
  60. },
  61. "context create": {
  62. "aci",
  63. },
  64. }
  65. const (
  66. scanCommand = "scan"
  67. )
  68. // Track sends the tracking analytics to Docker Desktop
  69. func Track(context string, args []string, flags *flag.FlagSet, status string) {
  70. command := getCommand(args, flags)
  71. if command != "" {
  72. c := NewClient()
  73. c.Send(Command{
  74. Command: command,
  75. Context: context,
  76. Source: CLISource,
  77. Status: status,
  78. })
  79. }
  80. }
  81. func getCommand(args []string, flags *flag.FlagSet) string {
  82. command := ""
  83. strippedArgs := stripFlags(args, flags)
  84. if len(strippedArgs) != 0 {
  85. command = strippedArgs[0]
  86. if command == scanCommand {
  87. return getScanCommand(args)
  88. }
  89. for {
  90. if utils.StringContains(managementCommands, command) {
  91. if sub := getSubCommand(command, strippedArgs[1:]); sub != "" {
  92. command += " " + sub
  93. strippedArgs = strippedArgs[1:]
  94. continue
  95. }
  96. }
  97. break
  98. }
  99. }
  100. return command
  101. }
  102. func getScanCommand(args []string) string {
  103. command := args[0]
  104. if utils.StringContains(args, "--auth") {
  105. return command + " auth"
  106. }
  107. if utils.StringContains(args, "--version") {
  108. return command + " version"
  109. }
  110. return command
  111. }
  112. func getSubCommand(command string, args []string) string {
  113. if len(args) == 0 {
  114. return ""
  115. }
  116. if val, ok := managementSubCommands[command]; ok {
  117. if utils.StringContains(val, args[0]) {
  118. return args[0]
  119. }
  120. return ""
  121. }
  122. if isArg(args[0]) {
  123. return args[0]
  124. }
  125. return ""
  126. }
  127. func stripFlags(args []string, flags *flag.FlagSet) []string {
  128. commands := []string{}
  129. for len(args) > 0 {
  130. s := args[0]
  131. args = args[1:]
  132. if s == "--" {
  133. return commands
  134. }
  135. if flagArg(s, flags) {
  136. if len(args) <= 1 {
  137. return commands
  138. }
  139. args = args[1:]
  140. }
  141. if isArg(s) {
  142. commands = append(commands, s)
  143. }
  144. }
  145. return commands
  146. }
  147. func flagArg(s string, flags *flag.FlagSet) bool {
  148. return strings.HasPrefix(s, "--") && !strings.Contains(s, "=") && !hasNoOptDefVal(s[2:], flags) ||
  149. strings.HasPrefix(s, "-") && !strings.Contains(s, "=") && len(s) == 2 && !shortHasNoOptDefVal(s[1:], flags)
  150. }
  151. func isArg(s string) bool {
  152. return s != "" && !strings.HasPrefix(s, "-")
  153. }
  154. func hasNoOptDefVal(name string, fs *flag.FlagSet) bool {
  155. flag := fs.Lookup(name)
  156. if flag == nil {
  157. return false
  158. }
  159. return flag.NoOptDefVal != ""
  160. }
  161. func shortHasNoOptDefVal(name string, fs *flag.FlagSet) bool {
  162. flag := fs.ShorthandLookup(name[:1])
  163. if flag == nil {
  164. return false
  165. }
  166. return flag.NoOptDefVal != ""
  167. }