diff options
-rw-r--r-- | cmd/podman/play/kube.go | 15 | ||||
-rw-r--r-- | docs/source/markdown/podman-play-kube.1.md | 4 | ||||
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | go.sum | 3 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/play.go | 37 | ||||
-rw-r--r-- | pkg/api/server/register_play.go | 6 | ||||
-rw-r--r-- | pkg/bindings/play/types.go | 2 | ||||
-rw-r--r-- | pkg/bindings/play/types_kube_options.go | 16 | ||||
-rw-r--r-- | pkg/domain/entities/play.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/abi/play.go | 10 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/play.go | 3 | ||||
-rw-r--r-- | pkg/rootless/rootless.go | 4 | ||||
-rw-r--r-- | pkg/rootless/rootless_test.go | 57 | ||||
-rw-r--r-- | test/e2e/play_kube_test.go | 13 | ||||
-rw-r--r-- | vendor/github.com/onsi/ginkgo/CHANGELOG.md | 5 | ||||
-rw-r--r-- | vendor/github.com/onsi/ginkgo/config/config.go | 2 | ||||
-rw-r--r-- | vendor/github.com/onsi/ginkgo/ginkgo/run_command.go | 2 | ||||
-rw-r--r-- | vendor/github.com/onsi/ginkgo/types/deprecation_support.go | 54 | ||||
-rw-r--r-- | vendor/modules.txt | 2 |
19 files changed, 217 insertions, 22 deletions
diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index 30d6d86f0..fe382bdfb 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -2,6 +2,7 @@ package pods import ( "fmt" + "net" "os" "github.com/containers/common/pkg/auth" @@ -27,6 +28,7 @@ type playKubeOptionsWrapper struct { } var ( + macs []string // https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/ defaultSeccompRoot = "/var/lib/kubelet/seccomp" kubeOptions = playKubeOptionsWrapper{} @@ -61,6 +63,10 @@ func init() { flags.StringVar(&kubeOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") _ = kubeCmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) + staticMACFlagName := "mac-address" + flags.StringSliceVar(&macs, staticMACFlagName, nil, "Static MAC addresses to assign to the pods") + _ = kubeCmd.RegisterFlagCompletionFunc(staticMACFlagName, completion.AutocompleteNone) + networkFlagName := "network" flags.StringVar(&kubeOptions.Network, networkFlagName, "", "Connect pod to CNI network(s)") _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag) @@ -128,6 +134,15 @@ func kube(cmd *cobra.Command, args []string) error { if yamlfile == "-" { yamlfile = "/dev/stdin" } + + for _, mac := range macs { + m, err := net.ParseMAC(mac) + if err != nil { + return err + } + kubeOptions.StaticMACs = append(kubeOptions.StaticMACs, m) + } + report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), yamlfile, kubeOptions.PlayKubeOptions) if err != nil { return err diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md index 1074c27f8..ab2019139 100644 --- a/docs/source/markdown/podman-play-kube.1.md +++ b/docs/source/markdown/podman-play-kube.1.md @@ -70,6 +70,10 @@ Assign a static ip address to the pod. This option can be specified several time Set logging driver for all created containers. +#### **\-\-mac-address**=*MAC address* + +Assign a static mac address to the pod. This option can be specified several times when play kube creates more than one pod. + #### **\-\-network**=*networks*, **\-\-net** A comma-separated list of the names of CNI networks the pod should join. @@ -42,7 +42,7 @@ require ( github.com/mattn/go-colorable v0.1.8 // indirect github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 github.com/mrunalp/fileutils v0.5.0 - github.com/onsi/ginkgo v1.16.1 + github.com/onsi/ginkgo v1.16.2 github.com/onsi/gomega v1.11.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 @@ -611,8 +611,9 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= -github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54= github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.2 h1:HFB2fbVIlhIfCfOW81bZFbiC/RvnpXSdhbF2/DJr134= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go index 96f572a8b..90332924c 100644 --- a/pkg/api/handlers/libpod/play.go +++ b/pkg/api/handlers/libpod/play.go @@ -21,11 +21,12 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - Network string `schema:"network"` - TLSVerify bool `schema:"tlsVerify"` - LogDriver string `schema:"logDriver"` - Start bool `schema:"start"` - StaticIPs []string `schema:"staticIPs"` + Network string `schema:"network"` + TLSVerify bool `schema:"tlsVerify"` + LogDriver string `schema:"logDriver"` + Start bool `schema:"start"` + StaticIPs []string `schema:"staticIPs"` + StaticMACs []string `schema:"staticMACs"` }{ TLSVerify: true, Start: true, @@ -48,6 +49,17 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { staticIPs = append(staticIPs, ip) } + staticMACs := make([]net.HardwareAddr, 0, len(query.StaticMACs)) + for _, macString := range query.StaticMACs { + mac, err := net.ParseMAC(macString) + if err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + err) + return + } + staticMACs = append(staticMACs, mac) + } + // Fetch the K8s YAML file from the body, and copy it to a temp file. tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml") if err != nil { @@ -78,13 +90,14 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { containerEngine := abi.ContainerEngine{Libpod: runtime} options := entities.PlayKubeOptions{ - Authfile: authfile, - Username: username, - Password: password, - Network: query.Network, - Quiet: true, - LogDriver: query.LogDriver, - StaticIPs: staticIPs, + Authfile: authfile, + Username: username, + Password: password, + Network: query.Network, + Quiet: true, + LogDriver: query.LogDriver, + StaticIPs: staticIPs, + StaticMACs: staticMACs, } if _, found := r.URL.Query()["tlsVerify"]; found { options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) diff --git a/pkg/api/server/register_play.go b/pkg/api/server/register_play.go index da37abb70..c51301aa8 100644 --- a/pkg/api/server/register_play.go +++ b/pkg/api/server/register_play.go @@ -40,6 +40,12 @@ func (s *APIServer) registerPlayHandlers(r *mux.Router) error { // description: Static IPs used for the pods. // items: // type: string + // - in: query + // name: staticMACs + // type: array + // description: Static MACs used for the pods. + // items: + // type: string // - in: body // name: request // description: Kubernetes YAML file. diff --git a/pkg/bindings/play/types.go b/pkg/bindings/play/types.go index 6598ec3c2..52a72c7b6 100644 --- a/pkg/bindings/play/types.go +++ b/pkg/bindings/play/types.go @@ -27,6 +27,8 @@ type KubeOptions struct { SeccompProfileRoot *string // StaticIPs - Static IP address used by the pod(s). StaticIPs *[]net.IP + // StaticMACs - Static MAC address used by the pod(s). + StaticMACs *[]net.HardwareAddr // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. ConfigMaps *[]string // LogDriver for the container. For example: journald diff --git a/pkg/bindings/play/types_kube_options.go b/pkg/bindings/play/types_kube_options.go index a1786f553..4cc7d6f21 100644 --- a/pkg/bindings/play/types_kube_options.go +++ b/pkg/bindings/play/types_kube_options.go @@ -181,6 +181,22 @@ func (o *KubeOptions) GetStaticIPs() []net.IP { return *o.StaticIPs } +// WithStaticMACs +func (o *KubeOptions) WithStaticMACs(value []net.HardwareAddr) *KubeOptions { + v := &value + o.StaticMACs = v + return o +} + +// GetStaticMACs +func (o *KubeOptions) GetStaticMACs() []net.HardwareAddr { + var staticMACs []net.HardwareAddr + if o.StaticMACs == nil { + return staticMACs + } + return *o.StaticMACs +} + // WithConfigMaps func (o *KubeOptions) WithConfigMaps(value []string) *KubeOptions { v := &value diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go index c69bb0867..89dfc08e9 100644 --- a/pkg/domain/entities/play.go +++ b/pkg/domain/entities/play.go @@ -30,6 +30,8 @@ type PlayKubeOptions struct { SeccompProfileRoot string // StaticIPs - Static IP address used by the pod(s). StaticIPs []net.IP + // StaticMACs - Static MAC address used by the pod(s). + StaticMACs []net.HardwareAddr // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. ConfigMaps []string // LogDriver for the container. For example: journald diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index d235c9ed8..64e7f208c 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -198,11 +198,17 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY } if len(options.StaticIPs) > *ipIndex { p.StaticIP = &options.StaticIPs[*ipIndex] - *ipIndex++ } else if len(options.StaticIPs) > 0 { - // only warn if the user has set at least one ip ip + // only warn if the user has set at least one ip logrus.Warn("No more static ips left using a random one") } + if len(options.StaticMACs) > *ipIndex { + p.StaticMAC = &options.StaticMACs[*ipIndex] + } else if len(options.StaticIPs) > 0 { + // only warn if the user has set at least one mac + logrus.Warn("No more static macs left using a random one") + } + *ipIndex++ // Create the Pod pod, err := generate.MakePod(p, ic.Libpod) diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go index e52e1a1f7..e66ff0308 100644 --- a/pkg/domain/infra/tunnel/play.go +++ b/pkg/domain/infra/tunnel/play.go @@ -11,7 +11,8 @@ import ( func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, opts entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { options := new(play.KubeOptions).WithAuthfile(opts.Authfile).WithUsername(opts.Username).WithPassword(opts.Password) options.WithCertDir(opts.CertDir).WithQuiet(opts.Quiet).WithSignaturePolicy(opts.SignaturePolicy).WithConfigMaps(opts.ConfigMaps) - options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot).WithStaticIPs(opts.StaticIPs) + options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot) + options.WithStaticIPs(opts.StaticIPs).WithStaticMACs(opts.StaticMACs) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { options.WithSkipTLSVerify(s == types.OptionalBoolTrue) diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go index 0b9d719a9..93b4e2e9f 100644 --- a/pkg/rootless/rootless.go +++ b/pkg/rootless/rootless.go @@ -137,7 +137,7 @@ func GetAvailableGids() (int64, error) { // It assumes availableMappings is sorted by ID. func findIDInMappings(id int64, availableMappings []user.IDMap) *user.IDMap { i := sort.Search(len(availableMappings), func(i int) bool { - return availableMappings[i].ID >= id + return availableMappings[i].ID <= id }) if i < 0 || i >= len(availableMappings) { return nil @@ -157,7 +157,7 @@ func MaybeSplitMappings(mappings []spec.LinuxIDMapping, availableMappings []user overflow.Size = 0 consumed := 0 sort.Slice(availableMappings, func(i, j int) bool { - return availableMappings[i].ID < availableMappings[j].ID + return availableMappings[i].ID > availableMappings[j].ID }) for { cur := overflow diff --git a/pkg/rootless/rootless_test.go b/pkg/rootless/rootless_test.go index ef574099c..fe9b23cdf 100644 --- a/pkg/rootless/rootless_test.go +++ b/pkg/rootless/rootless_test.go @@ -98,4 +98,61 @@ func TestMaybeSplitMappings(t *testing.T) { if !reflect.DeepEqual(newMappings, desiredMappings) { t.Fatal("wrong mappings generated") } + + mappings = []spec.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 0, + Size: 4, + }, + } + desiredMappings = []spec.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 0, + Size: 1, + }, + { + ContainerID: 1, + HostID: 1, + Size: 1, + }, + { + ContainerID: 2, + HostID: 2, + Size: 1, + }, + { + ContainerID: 3, + HostID: 3, + Size: 1, + }, + } + availableMappings = []user.IDMap{ + { + ID: 0, + ParentID: 0, + Count: 1, + }, + { + ID: 1, + ParentID: 1, + Count: 1, + }, + { + ID: 2, + ParentID: 2, + Count: 1, + }, + { + ID: 3, + ParentID: 3, + Count: 1, + }, + } + + newMappings = MaybeSplitMappings(mappings, availableMappings) + if !reflect.DeepEqual(newMappings, desiredMappings) { + t.Fatal("wrong mappings generated") + } } diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index f89da4c05..836fbe1ee 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -1717,7 +1717,7 @@ spec: } }) - It("podman play kube --ip", func() { + It("podman play kube --ip and --mac-address", func() { var i, numReplicas int32 numReplicas = 3 deployment := getDeployment(withReplicas(numReplicas)) @@ -1735,6 +1735,10 @@ spec: for _, ip := range ips { playArgs = append(playArgs, "--ip", ip) } + macs := []string{"e8:d8:82:c9:80:40", "e8:d8:82:c9:80:50", "e8:d8:82:c9:80:60"} + for _, mac := range macs { + playArgs = append(playArgs, "--mac-address", mac) + } kube := podmanTest.Podman(append(playArgs, kubeYaml)) kube.WaitWithDefaultTimeout() @@ -1747,6 +1751,13 @@ spec: Expect(inspect.ExitCode()).To(Equal(0)) Expect(inspect.OutputToString()).To(Equal(ips[i])) } + + for i = 0; i < numReplicas; i++ { + inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&podNames[i]), "--format", "{{ .NetworkSettings.Networks." + net + ".MacAddress }}"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + Expect(inspect.OutputToString()).To(Equal(macs[i])) + } }) It("podman play kube test with network portbindings", func() { diff --git a/vendor/github.com/onsi/ginkgo/CHANGELOG.md b/vendor/github.com/onsi/ginkgo/CHANGELOG.md index 4e0afc291..50631e4a9 100644 --- a/vendor/github.com/onsi/ginkgo/CHANGELOG.md +++ b/vendor/github.com/onsi/ginkgo/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.16.2 + +### Fixes +- Deprecations can be suppressed by setting an `ACK_GINKGO_DEPRECATIONS=<semver>` environment variable. + ## 1.16.1 ### Fixes diff --git a/vendor/github.com/onsi/ginkgo/config/config.go b/vendor/github.com/onsi/ginkgo/config/config.go index 5f4a4c26e..ab8863d75 100644 --- a/vendor/github.com/onsi/ginkgo/config/config.go +++ b/vendor/github.com/onsi/ginkgo/config/config.go @@ -20,7 +20,7 @@ import ( "fmt" ) -const VERSION = "1.16.1" +const VERSION = "1.16.2" type GinkgoConfigType struct { RandomSeed int64 diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/run_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/run_command.go index 47b586d93..c7f80d143 100644 --- a/vendor/github.com/onsi/ginkgo/ginkgo/run_command.go +++ b/vendor/github.com/onsi/ginkgo/ginkgo/run_command.go @@ -61,6 +61,7 @@ func (r *SpecRunner) RunSpecs(args []string, additionalArgs []string) { deprecationTracker.TrackDeprecation(types.Deprecation{ Message: "--stream is deprecated and will be removed in Ginkgo 2.0", DocLink: "removed--stream", + Version: "1.16.0", }) } @@ -68,6 +69,7 @@ func (r *SpecRunner) RunSpecs(args []string, additionalArgs []string) { deprecationTracker.TrackDeprecation(types.Deprecation{ Message: "--notify is deprecated and will be removed in Ginkgo 2.0", DocLink: "removed--notify", + Version: "1.16.0", }) } diff --git a/vendor/github.com/onsi/ginkgo/types/deprecation_support.go b/vendor/github.com/onsi/ginkgo/types/deprecation_support.go index 7f7a9aeb8..71420f597 100644 --- a/vendor/github.com/onsi/ginkgo/types/deprecation_support.go +++ b/vendor/github.com/onsi/ginkgo/types/deprecation_support.go @@ -1,12 +1,19 @@ package types import ( + "os" + "strconv" + "strings" + "unicode" + + "github.com/onsi/ginkgo/config" "github.com/onsi/ginkgo/formatter" ) type Deprecation struct { Message string DocLink string + Version string } type deprecations struct{} @@ -17,6 +24,7 @@ func (d deprecations) CustomReporter() Deprecation { return Deprecation{ Message: "You are using a custom reporter. Support for custom reporters will likely be removed in V2. Most users were using them to generate junit or teamcity reports and this functionality will be merged into the core reporter. In addition, Ginkgo 2.0 will support emitting a JSON-formatted report that users can then manipulate to generate custom reports.\n\n{{red}}{{bold}}If this change will be impactful to you please leave a comment on {{cyan}}{{underline}}https://github.com/onsi/ginkgo/issues/711{{/}}", DocLink: "removed-custom-reporters", + Version: "1.16.0", } } @@ -24,6 +32,7 @@ func (d deprecations) V1Reporter() Deprecation { return Deprecation{ Message: "You are using a V1 Ginkgo Reporter. Please update your custom reporter to the new V2 Reporter interface.", DocLink: "changed-reporter-interface", + Version: "1.16.0", } } @@ -31,6 +40,7 @@ func (d deprecations) Async() Deprecation { return Deprecation{ Message: "You are passing a Done channel to a test node to test asynchronous behavior. This is deprecated in Ginkgo V2. Your test will run synchronously and the timeout will be ignored.", DocLink: "removed-async-testing", + Version: "1.16.0", } } @@ -38,6 +48,7 @@ func (d deprecations) Measure() Deprecation { return Deprecation{ Message: "Measure is deprecated in Ginkgo V2", DocLink: "removed-measure", + Version: "1.16.0", } } @@ -45,12 +56,14 @@ func (d deprecations) Convert() Deprecation { return Deprecation{ Message: "The convert command is deprecated in Ginkgo V2", DocLink: "removed-ginkgo-convert", + Version: "1.16.0", } } func (d deprecations) Blur() Deprecation { return Deprecation{ Message: "The blur command is deprecated in Ginkgo V2. Use 'ginkgo unfocus' instead.", + Version: "1.16.0", } } @@ -65,6 +78,15 @@ func NewDeprecationTracker() *DeprecationTracker { } func (d *DeprecationTracker) TrackDeprecation(deprecation Deprecation, cl ...CodeLocation) { + ackVersion := os.Getenv("ACK_GINKGO_DEPRECATIONS") + if deprecation.Version != "" && ackVersion != "" { + ack := ParseSemVer(ackVersion) + version := ParseSemVer(deprecation.Version) + if ack.GreaterThanOrEqualTo(version) { + return + } + } + if len(cl) == 1 { d.deprecations[deprecation] = append(d.deprecations[deprecation], cl[0]) } else { @@ -92,5 +114,37 @@ func (d *DeprecationTracker) DeprecationsReport() string { out += formatter.Fi(2, "{{gray}}%s{{/}}\n", location) } } + out += formatter.F("\n{{gray}}To silence deprecations that can be silenced set the following environment variable:{{/}}\n") + out += formatter.Fi(1, "{{gray}}ACK_GINKGO_DEPRECATIONS=%s{{/}}\n", config.VERSION) + return out +} + +type SemVer struct { + Major int + Minor int + Patch int +} + +func (s SemVer) GreaterThanOrEqualTo(o SemVer) bool { + return (s.Major > o.Major) || + (s.Major == o.Major && s.Minor > o.Minor) || + (s.Major == o.Major && s.Minor == o.Minor && s.Patch >= o.Patch) +} + +func ParseSemVer(semver string) SemVer { + out := SemVer{} + semver = strings.TrimFunc(semver, func(r rune) bool { + return !(unicode.IsNumber(r) || r == '.') + }) + components := strings.Split(semver, ".") + if len(components) > 0 { + out.Major, _ = strconv.Atoi(components[0]) + } + if len(components) > 1 { + out.Minor, _ = strconv.Atoi(components[1]) + } + if len(components) > 2 { + out.Patch, _ = strconv.Atoi(components[2]) + } return out } diff --git a/vendor/modules.txt b/vendor/modules.txt index 859b067af..4f6410a6b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -447,7 +447,7 @@ github.com/nxadm/tail/ratelimiter github.com/nxadm/tail/util github.com/nxadm/tail/watch github.com/nxadm/tail/winfile -# github.com/onsi/ginkgo v1.16.1 +# github.com/onsi/ginkgo v1.16.2 github.com/onsi/ginkgo github.com/onsi/ginkgo/config github.com/onsi/ginkgo/extensions/table |