test: fix the integrtion tests for apply-config

They got broken after refactoring.

Also use this PR to test things before the release.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
Andrey Smirnov 2024-07-05 19:21:59 +04:00
parent 076f3c4f20
commit 2512ef435f
No known key found for this signature in database
GPG Key ID: FE042E3D4085A811
9 changed files with 268 additions and 54 deletions

View File

@ -504,9 +504,9 @@ FROM scratch AS talosctl
ARG TARGETARCH
COPY --from=talosctl-all /talosctl-linux-${TARGETARCH} /talosctl
ARG TAG
ENV VERSION ${TAG}
ENV VERSION=${TAG}
LABEL "alpha.talos.dev/version"="${VERSION}"
LABEL org.opencontainers.image.source https://github.com/siderolabs/talos
LABEL org.opencontainers.image.source=https://github.com/siderolabs/talos
ENTRYPOINT ["/talosctl"]
# The kernel target is the linux kernel.
@ -755,7 +755,7 @@ COPY --from=initramfs-archive /initramfs.xz /initramfs-${TARGETARCH}.xz
FROM scratch AS talos
COPY --from=rootfs / /
LABEL org.opencontainers.image.source https://github.com/siderolabs/talos
LABEL org.opencontainers.image.source=https://github.com/siderolabs/talos
ENTRYPOINT ["/sbin/init"]
# The installer target generates an image that can be used to install Talos to
@ -793,7 +793,7 @@ FROM install-artifacts-${INSTALLER_ARCH} AS install-artifacts
FROM alpine:3.18.4 AS installer-image
ARG SOURCE_DATE_EPOCH
ENV SOURCE_DATE_EPOCH ${SOURCE_DATE_EPOCH}
ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}
RUN apk add --no-cache --update --no-scripts \
bash \
binutils-aarch64 \
@ -813,7 +813,7 @@ RUN apk add --no-cache --update --no-scripts \
xz \
zstd
ARG TARGETARCH
ENV TARGETARCH ${TARGETARCH}
ENV TARGETARCH=${TARGETARCH}
COPY --from=installer-build /installer /bin/installer
COPY --chmod=0644 hack/extra-modules.conf /etc/modules.d/10-extra-modules.conf
COPY --from=pkg-grub / /
@ -827,9 +827,9 @@ RUN find /bin /etc /lib /usr /sbin | grep -Ev '/etc/hosts|/etc/resolv.conf' \
FROM scratch AS installer-image-squashed
COPY --from=installer-image / /
ARG TAG
ENV VERSION ${TAG}
ENV VERSION=${TAG}
LABEL "alpha.talos.dev/version"="${VERSION}"
LABEL org.opencontainers.image.source https://github.com/siderolabs/talos
LABEL org.opencontainers.image.source=https://github.com/siderolabs/talos
ENTRYPOINT ["/bin/installer"]
FROM installer-image-squashed AS installer
@ -841,7 +841,7 @@ ENTRYPOINT ["/bin/imager"]
FROM imager AS iso-amd64-build
ARG SOURCE_DATE_EPOCH
ENV SOURCE_DATE_EPOCH ${SOURCE_DATE_EPOCH}
ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}
RUN /bin/installer \
iso \
--arch amd64 \
@ -849,7 +849,7 @@ RUN /bin/installer \
FROM imager AS iso-arm64-build
ARG SOURCE_DATE_EPOCH
ENV SOURCE_DATE_EPOCH ${SOURCE_DATE_EPOCH}
ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}
RUN /bin/installer \
iso \
--arch arm64 \
@ -868,7 +868,7 @@ FROM base AS unit-tests-runner
RUN unlink /etc/ssl
COPY --from=rootfs / /
ARG TESTPKGS
ENV PLATFORM container
ENV PLATFORM=container
ARG GO_LDFLAGS
RUN --security=insecure --mount=type=cache,id=testspace,target=/tmp --mount=type=cache,target=/.cache go test -failfast -v \
-ldflags "${GO_LDFLAGS}" \
@ -882,8 +882,8 @@ FROM base AS unit-tests-race
RUN unlink /etc/ssl
COPY --from=rootfs / /
ARG TESTPKGS
ENV PLATFORM container
ENV CGO_ENABLED 1
ENV PLATFORM=container
ENV CGO_ENABLED=1
ARG GO_LDFLAGS
RUN --security=insecure --mount=type=cache,id=testspace,target=/tmp --mount=type=cache,target=/.cache go test -v \
-ldflags "${GO_LDFLAGS}" \

2
go.mod
View File

@ -77,7 +77,6 @@ require (
github.com/gizak/termui/v3 v3.1.0
github.com/godbus/dbus/v5 v5.1.0
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.6.0
github.com/google/go-containerregistry v0.19.2
github.com/google/go-tpm v0.9.1
github.com/google/nftables v0.2.0
@ -249,6 +248,7 @@ require (
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/websocket v1.5.1 // indirect

View File

@ -8,7 +8,6 @@ import (
"archive/tar"
"bufio"
"bytes"
stdcmp "cmp"
"compress/gzip"
"context"
"encoding/json"
@ -28,7 +27,6 @@ import (
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"github.com/cosi-project/runtime/pkg/state/protobuf/server"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"github.com/gopacket/gopacket/afpacket"
multierror "github.com/hashicorp/go-multierror"
@ -78,11 +76,10 @@ import (
timeapi "github.com/siderolabs/talos/pkg/machinery/api/time"
clientconfig "github.com/siderolabs/talos/pkg/machinery/client/config"
"github.com/siderolabs/talos/pkg/machinery/config"
docscfg "github.com/siderolabs/talos/pkg/machinery/config/config"
"github.com/siderolabs/talos/pkg/machinery/config/configdiff"
"github.com/siderolabs/talos/pkg/machinery/config/configloader"
"github.com/siderolabs/talos/pkg/machinery/config/generate/secrets"
machinetype "github.com/siderolabs/talos/pkg/machinery/config/machine"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
etcdresource "github.com/siderolabs/talos/pkg/machinery/resources/etcd"
@ -221,7 +218,10 @@ func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfig
}
if in.DryRun {
details := generateDiff(s.Controller.Runtime(), cfgProvider)
details, err := generateDiff(s.Controller.Runtime(), cfgProvider)
if err != nil {
return nil, fmt.Errorf("failed to generate diff: %w", err)
}
return &machine.ApplyConfigurationResponse{
Messages: []*machine.ApplyConfiguration{
@ -295,33 +295,17 @@ func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfig
}, nil
}
func generateDiff(r runtime.Runtime, provider config.Provider) string {
var cfg *v1alpha1.Config
if r.Config() != nil {
cfg = r.ConfigContainer().RawV1Alpha1()
func generateDiff(r runtime.Runtime, provider config.Provider) (string, error) {
documentsDiff, err := configdiff.DiffToString(r.ConfigContainer(), provider)
if err != nil {
return "", err
}
v1alpha1Diff := cmp.Diff(cfg, provider.RawV1Alpha1(), cmp.AllowUnexported(v1alpha1.InstallDiskSizeMatcher{}))
if v1alpha1Diff == "" {
v1alpha1Diff = "No changes."
}
origDocs := slices.DeleteFunc(r.ConfigContainer().Documents(), func(doc docscfg.Document) bool { return doc.Kind() == v1alpha1.Version })
newDocs := slices.DeleteFunc(provider.Documents(), func(doc docscfg.Document) bool { return doc.Kind() == v1alpha1.Version })
slices.SortStableFunc(origDocs, func(a, b docscfg.Document) int { return stdcmp.Compare(a.Kind(), b.Kind()) })
slices.SortStableFunc(newDocs, func(a, b docscfg.Document) int { return stdcmp.Compare(a.Kind(), b.Kind()) })
documentsDiff := cmp.Diff(origDocs, newDocs)
if documentsDiff == "" {
documentsDiff = "No changes."
}
return fmt.Sprintf(`Config diff:
%s
Documents diff:
%s`, v1alpha1Diff, documentsDiff)
return "Config diff:\n\n" + documentsDiff, nil
}
// GenerateConfiguration implements the machine.MachineServer interface.

View File

@ -16,13 +16,13 @@ import (
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/google/go-cmp/cmp"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/system"
"github.com/siderolabs/talos/internal/app/machined/pkg/system/services"
"github.com/siderolabs/talos/pkg/machinery/config"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/config/configdiff"
"github.com/siderolabs/talos/pkg/machinery/config/container"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
"github.com/siderolabs/talos/pkg/machinery/resources/k8s"
)
@ -165,9 +165,12 @@ func (r *Runtime) CanApplyImmediate(cfg config.Provider) error {
}
if !reflect.DeepEqual(currentConfig, newConfig) {
diff := cmp.Diff(currentConfig, newConfig, cmp.AllowUnexported(v1alpha1.InstallDiskSizeMatcher{}))
diff, err := configdiff.DiffToString(container.NewV1Alpha1(currentConfig), container.NewV1Alpha1(newConfig))
if err != nil {
return fmt.Errorf("error calculating diff: %w", err)
}
return fmt.Errorf("this config change can't be applied in immediate mode\ndiff: %s", diff)
return fmt.Errorf("this config change can't be applied in immediate mode\ndiff:\n%s", diff)
}
return nil

View File

@ -98,7 +98,7 @@ func (suite *ApplyConfigSuite) TestApply() {
nodeCtx := client.WithNode(suite.ctx, node)
provider, err := suite.ReadConfigFromNode(nodeCtx)
suite.Assert().NoErrorf(err, "failed to read existing config from node %q: %w", node, err)
suite.Assert().NoErrorf(err, "failed to read existing config from node %q", node)
cfgDataOut := suite.PatchV1Alpha1Config(provider, func(cfg *v1alpha1.Config) {
if cfg.MachineConfig.MachineSysctls == nil {
@ -118,7 +118,7 @@ func (suite *ApplyConfigSuite) TestApply() {
)
if err != nil {
// It is expected that the connection will EOF here, so just log the error
suite.Assert().NoErrorf(err, "failed to apply configuration (node %q): %w", node, err)
suite.Assert().NoErrorf(err, "failed to apply configuration (node %q)", node)
}
return nil
@ -138,7 +138,7 @@ func (suite *ApplyConfigSuite) TestApply() {
return nil
},
), "failed to read updated configuration from node %q: %w", node, err,
), "failed to read updated configuration from node %q", node,
)
suite.Assert().Equal(
@ -177,14 +177,14 @@ func (suite *ApplyConfigSuite) TestApplyWithoutReboot() {
Mode: mode,
},
)
suite.Require().NoError(err, "failed to apply deferred configuration (node %q): %w", node)
suite.Require().NoError(err, "failed to apply deferred configuration (node %q)", node)
// Verify configuration change
var newProvider config.Provider
newProvider, err = suite.ReadConfigFromNode(nodeCtx)
suite.Require().NoError(err, "failed to read updated configuration from node %q: %w", node)
suite.Require().NoError(err, "failed to read updated configuration from node %q", node)
if mode == machineapi.ApplyConfigurationRequest_AUTO {
suite.Assert().Equal(
@ -206,7 +206,7 @@ func (suite *ApplyConfigSuite) TestApplyWithoutReboot() {
Mode: mode,
},
)
suite.Require().NoError(err, "failed to apply deferred configuration (node %q): %w", node)
suite.Require().NoError(err, "failed to apply deferred configuration (node %q)", node)
}
}
@ -303,7 +303,7 @@ func (suite *ApplyConfigSuite) TestApplyConfigRotateEncryptionSecrets() {
)
if err != nil {
// It is expected that the connection will EOF here, so just log the error
suite.Assert().Errorf(err, "failed to apply configuration (node %q): %w", node, err)
suite.T().Logf("failed to apply configuration (node %q): %s", node, err)
}
return nil
@ -315,7 +315,7 @@ func (suite *ApplyConfigSuite) TestApplyConfigRotateEncryptionSecrets() {
// Verify configuration change
var newProvider config.Provider
suite.Require().Errorf(
suite.Require().NoError(
retry.Constant(time.Minute, retry.WithUnits(time.Second)).Retry(
func() error {
newProvider, err = suite.ReadConfigFromNode(nodeCtx)
@ -325,7 +325,7 @@ func (suite *ApplyConfigSuite) TestApplyConfigRotateEncryptionSecrets() {
return nil
},
), "failed to read updated configuration from node %q: %w", node, err,
), "failed to read updated configuration from node %q", node,
)
e := newProvider.Machine().SystemDiskEncryption().Get(constants.EphemeralPartitionLabel)
@ -504,10 +504,10 @@ func (suite *ApplyConfigSuite) TestApplyTry() {
TryModeTimeout: durationpb.New(time.Second * 1),
},
)
suite.Assert().NoErrorf(err, "failed to apply configuration (node %q): %s", node, err)
suite.Assert().NoErrorf(err, "failed to apply configuration (node %q)", node)
provider, err = getMachineConfig(nodeCtx)
suite.Require().NoErrorf(err, "failed to read existing config from node %q: %w", node, err)
suite.Require().NoErrorf(err, "failed to read existing config from node %q", node)
suite.Assert().NotNil(provider.Config().Machine().Network())
suite.Assert().NotNil(provider.Config().Machine().Network().Devices())

View File

@ -0,0 +1,129 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package configdiff provides a way to compare two config trees.
package configdiff
import (
"fmt"
"io"
"strings"
"github.com/fatih/color"
"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
"github.com/siderolabs/talos/pkg/machinery/config"
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
)
// Diff outputs (optionally) colorized diff between two machine configurations.
//
// One of the resources might be nil.
func Diff(w io.Writer, oldCfg, newCfg config.Encoder) error {
var (
oldYaml, newYaml []byte
err error
)
if oldCfg != nil {
oldYaml, err = oldCfg.EncodeBytes(encoder.WithComments(encoder.CommentsDisabled))
if err != nil {
return err
}
}
if newCfg != nil {
newYaml, err = newCfg.EncodeBytes(encoder.WithComments(encoder.CommentsDisabled))
if err != nil {
return err
}
}
edits := myers.ComputeEdits(span.URIFromPath("a"), string(oldYaml), string(newYaml))
diff := gotextdiff.ToUnified("a", "b", string(oldYaml), edits)
outputDiff(w, diff, true)
return nil
}
// DiffToString returns a string representation of the diff between two machine configurations.
func DiffToString(oldCfg, newCfg config.Encoder) (string, error) {
var sb strings.Builder
err := Diff(&sb, oldCfg, newCfg)
return sb.String(), err
}
//nolint:gocyclo
func outputDiff(w io.Writer, u gotextdiff.Unified, noColor bool) {
if len(u.Hunks) == 0 {
return
}
bold := color.New(color.Bold)
cyan := color.New(color.FgCyan)
red := color.New(color.FgRed)
green := color.New(color.FgGreen)
if noColor {
bold.DisableColor()
cyan.DisableColor()
red.DisableColor()
green.DisableColor()
}
bold.Fprintf(w, "--- %s\n", u.From) //nolint:errcheck
bold.Fprintf(w, "+++ %s\n", u.To) //nolint:errcheck
for _, hunk := range u.Hunks {
fromCount, toCount := 0, 0
for _, l := range hunk.Lines {
switch l.Kind { //nolint:exhaustive
case gotextdiff.Delete:
fromCount++
case gotextdiff.Insert:
toCount++
default:
fromCount++
toCount++
}
}
cyan.Fprintf(w, "@@") //nolint:errcheck
if fromCount > 1 {
cyan.Fprintf(w, " -%d,%d", hunk.FromLine, fromCount) //nolint:errcheck
} else {
cyan.Fprintf(w, " -%d", hunk.FromLine) //nolint:errcheck
}
if toCount > 1 {
cyan.Fprintf(w, " +%d,%d", hunk.ToLine, toCount) //nolint:errcheck
} else {
cyan.Fprintf(w, " +%d", hunk.ToLine) //nolint:errcheck
}
cyan.Printf(" @@\n") //nolint:errcheck
for _, l := range hunk.Lines {
switch l.Kind { //nolint:exhaustive
case gotextdiff.Delete:
red.Fprintf(w, "-%s", l.Content) //nolint:errcheck
case gotextdiff.Insert:
green.Fprintf(w, "+%s", l.Content) //nolint:errcheck
default:
fmt.Fprintf(w, " %s", l.Content)
}
if !strings.HasSuffix(l.Content, "\n") {
red.Fprintf(w, "\n\\ No newline at end of file\n") //nolint:errcheck
}
}
}
}

View File

@ -0,0 +1,84 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package configdiff_test
import (
"net/url"
"testing"
"github.com/siderolabs/gen/xtesting/must"
"github.com/stretchr/testify/require"
"github.com/siderolabs/talos/pkg/machinery/config/config"
"github.com/siderolabs/talos/pkg/machinery/config/configdiff"
"github.com/siderolabs/talos/pkg/machinery/config/container"
"github.com/siderolabs/talos/pkg/machinery/config/types/meta"
"github.com/siderolabs/talos/pkg/machinery/config/types/siderolink"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
)
func TestDiffString(t *testing.T) {
t.Parallel()
v1alpha1Cfg := &v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{
MachineType: "controlplane",
MachineToken: "foo",
},
}
v1alpha1CfgOther := v1alpha1Cfg.DeepCopy()
v1alpha1CfgOther.MachineConfig.MachineType = "worker"
siderolinkConfig := siderolink.NewConfigV1Alpha1()
siderolinkConfig.APIUrlConfig = meta.URL{
URL: must.Value(url.Parse("https://example.com"))(t),
}
for _, test := range []struct {
name string
oldCfg []config.Document
newCfg []config.Document
want string
}{
{
name: "empty",
oldCfg: nil,
newCfg: nil,
want: "",
},
{
name: "same",
oldCfg: []config.Document{v1alpha1Cfg},
newCfg: []config.Document{v1alpha1Cfg},
want: "",
},
{
name: "new doc",
oldCfg: []config.Document{v1alpha1Cfg},
newCfg: []config.Document{v1alpha1Cfg, siderolinkConfig},
want: "--- a\n+++ b\n@@ -4,3 +4,7 token: foo\n certSANs: []\n cluster: null\n+---\n+apiVersion: v1alpha1\n+kind: SideroLinkConfig\n+apiUrl: https://example.com\n",
},
{
name: "updated field",
oldCfg: []config.Document{v1alpha1Cfg, siderolinkConfig},
newCfg: []config.Document{v1alpha1CfgOther, siderolinkConfig},
want: "--- a\n+++ b\n@@ -1,6 +1,6 version: v1alpha1\n machine:\n- type: controlplane\n+ type: worker\n token: foo\n certSANs: []\n cluster: null\n",
},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
oldCfg := must.Value(container.New(test.oldCfg...))(t)
newCfg := must.Value(container.New(test.newCfg...))(t)
got, err := configdiff.DiffToString(oldCfg, newCfg)
require.NoError(t, err)
require.Equal(t, test.want, got)
})
}
}

View File

@ -13,8 +13,10 @@ require (
github.com/dustin/go-humanize v1.0.1
github.com/emicklei/dot v1.6.2
github.com/evanphx/json-patch v5.9.0+incompatible
github.com/fatih/color v1.17.0
github.com/ghodss/yaml v1.0.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hexops/gotextdiff v1.0.3
github.com/jsimonetti/rtnetlink/v2 v2.0.2
github.com/mdlayher/ethtool v0.1.0
github.com/opencontainers/runtime-spec v1.2.0
@ -47,6 +49,8 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.5.1 // indirect

View File

@ -33,6 +33,8 @@ github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls=
github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA=
github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@ -52,6 +54,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsimonetti/rtnetlink/v2 v2.0.2 h1:ZKlbCujrIpp4/u3V2Ka0oxlf4BCkt6ojkvpy3nZoCBY=
@ -60,6 +64,11 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdlayher/ethtool v0.1.0 h1:XAWHsmKhyPOo42qq/yTPb0eFBGUKKTR1rE0dVrWVQ0Y=
github.com/mdlayher/ethtool v0.1.0/go.mod h1:fBMLn2UhfRGtcH5ZFjr+6GUiHEjZsItFD7fSn7jbZVQ=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
@ -150,6 +159,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=